Modelo de datos
El modelo de datos de EMA Clinic refleja la dualidad arquitectónica del producto: la información clínica vive como recursos FHIR canónicos en Aidbox, mientras que la configuración multi-tenancy, la operación de caja y las extensiones específicas de Chile viven en Supabase. Esta página documenta ambos lados —los schemas operacionales y los recursos FHIR utilizados— y cierra con las state machines que gobiernan los recursos con más estados visibles para el usuario.
Schemas Supabase
Sección titulada «Schemas Supabase»Clinic usa dos schemas dentro del mismo proyecto Supabase: emashared
para datos cross-product (compartidos con Lab, Vault, etc.) y
emaclinic para datos propios del producto.
emashared: la base multi-tenant
Sección titulada «emashared: la base multi-tenant»El schema emashared no pertenece exclusivamente a Clinic; lo
escriben Vault (al provisionar tenants) y lo leen todos los productos
del ecosistema. Las cinco tablas más relevantes para Clinic son:
| Tabla | Propósito |
|---|---|
tenants | Definición compartida del tenant: id, slug, country, is_active |
user_roles | Bridge entre user y tenant más rol; aplica RLS tenant_id = auth.tenant_id |
tenant_aidbox_credentials | Credenciales OAuth de Aidbox encriptadas con AES-GCM y encryption_key_version para rotación |
tenant_emi_access | Activación EMI por tenant; opera como gate del proxy |
users | Flag is_platform_admin que distingue staff de EMA Health |
emaclinic: lo propio del producto
Sección titulada «emaclinic: lo propio del producto»El schema emaclinic agrupa lo operacional —caja, audit, pricing,
scheduling, urgencia— en módulos identificados por número de
migración. La estructura se mantiene estable desde la migración 007:
Core (001): tenant_config # JSON {auth, dte, imed, urgencia, etc.} appointment_config # max_slots_per_day, etc. cash_registers # tenant_id, location_id, balance, date_opened/closed cash_movements # register_id, amount, payment_method, reference_id charge_item_config # tenant_id, charge_item_id, local_code, pricing_level
Audit (002): audit_logs # tenant_id, user_id, action, resource_type, changes JSON
Pricing (003): service_charge_items # tenant_id, charge_item_id, base_price_clp pricing_agreements # tenant_id, organization_id, agreement_type agreement_prices # agreement_id, charge_item_id, price_override invoice_encounter # FK bridge: Invoice (FHIR) ↔ Encounter (FHIR) integration_config # tenant_id, integration_type, url, credentials_encrypted integration_log # tenant_id, integration_id, status, request/response JSON
Scheduling (004): schedule_patterns # rrule (RRULE FREQ=DAILY;BYHOUR=...), max_daily_slots schedule_overrides # date, reason, slot_count_override scheduling_config # min/max advance days, cancellation window scheduling_exceptions # date_from/to, exception_type, resolution_status
Urgencia (S4, S8 partial): triage_esi_audit # F190 — triage_id, suggested_esi, final_esi, override_reason vital_signs_reference_ranges # per-tenant baseline by age_range
Patient sequence (007): patient_sequence # tenant_id, next_number INT (Patient.identifier generator)Todas las tablas con columna tenant_id aplican la misma política
de Row Level Security:
tenant_id = (select tenant_id from user_roles where user_id = auth.uid()).
Es la segunda capa de aislamiento entre tenants, complementaria al
ORGBAC que opera sobre Aidbox.
Diagrama ER del núcleo emaclinic
Sección titulada «Diagrama ER del núcleo emaclinic»El siguiente diagrama muestra las relaciones entre las tablas más
usadas. Los recursos FHIR Invoice y Encounter aparecen como nodos
externos porque viven en Aidbox, pero el bridge invoice_encounter
los relaciona via FK desde Supabase:
Recursos FHIR utilizados
Sección titulada «Recursos FHIR utilizados»Clinic trabaja con 29 recursos R4 distintos, todos con perfiles del
CL Core o del EMA IG aplicados según corresponda. El SDK emafhir
se encarga de inyectar meta.tag = urn:ema:tenant|<tenantId> en
cada recurso al momento de crearlo o actualizarlo, lo que sostiene
una de las tres capas de aislamiento (ORGBAC, RLS y tag).
Los recursos se agrupan en cuatro familias por dominio funcional.
Clínicos core
Sección titulada «Clínicos core»Los recursos clínicos básicos cubren paciente, encuentro, diagnóstico, observación y prescripción. Son los recursos que un médico toca en cada consulta:
| Recurso | Perfil | Campos clave |
|---|---|---|
Patient | emaig.cl/core#Patient | identifier (RUT), telecom, address, birthDate, gender, maritalStatus, managingOrganization |
Encounter | emaig.cl/core#Encounter | class (AMB/VR/HOSP), serviceType, subject, diagnosis, reasonReference, appointment |
Condition | emaig.cl/core#Condition | code (SNOMED), verificationStatus (provisional → confirmed), onsetDateTime |
Observation | emaig.cl/core#Observation | code (LOINC), value, referenceRange, status (final/amended) |
ServiceRequest | emaig.cl/core#ServiceRequest | code, subject, encounter, basedOn (Appointment), performer, specimen |
DiagnosticReport | emaig.cl/core#DiagnosticReport | code, subject, encounter, performer, result (Observation[]) |
Procedure | emaig.cl/core#Procedure | code, subject, encounter, status, performer |
AllergyIntolerance | FHIR R4 | code, patient, criticality, reaction[] |
MedicationRequest | emaig.cl/core#MedicationRequest | medication, subject, encounter, dosage, status, requester |
Medication | FHIR R4 | code (SNOMED), manufacturer |
Agendamiento
Sección titulada «Agendamiento»El bloque de agendamiento incluye los recursos que modelan
disponibilidad y reservas. La extensión más relevante respecto del
estándar FHIR es la integración con schedule_patterns (Supabase),
porque el Schedule FHIR no soporta RRULE de forma nativa:
| Recurso | Perfil | Notas |
|---|---|---|
Appointment | emaig.cl/core#Appointment | status (booked → fulfilled), participant[], slot multi-resource, basedOn |
Schedule | FHIR R4 | actor (Practitioner / Location / Device), planningHorizon |
Slot | FHIR R4 | schedule ref, status (free / busy), generado a partir de schedule_patterns |
HealthcareService | FHIR R4 | category, providedBy, location[], specialty, hoursOfOperation |
Facturación
Sección titulada «Facturación»Los recursos de facturación FHIR conviven con las tablas
service_charge_items, pricing_agreements y cash_movements en
Supabase. Las primeras representan el documento legal del cobro; las
segundas, la operación de caja:
| Recurso | Perfil | Notas |
|---|---|---|
Invoice | emaig.cl/core#Invoice | subject, lineItem (ChargeItem[]), status (draft → issued → balanced) |
ChargeItem | FHIR R4 | code, occurrence, performingOrganization, priceOverride |
ChargeItemDefinition | emaig.cl/core#ChargeItemDefinition | propertyGroup (precios por Coverage), applicability |
Coverage | FHIR R4 | beneficiary, payor, class (Fonasa-tramo o Isapre) |
DocumentReference | emaig.cl/core#DocumentReference | category (clinical-note / dte / scanned-order / consent / discharge-summary) |
SDC, workflow y otros
Sección titulada «SDC, workflow y otros»Los recursos restantes cubren cuestionarios estructurados (SDC), tasks con SLA, definiciones de planes de cuidado y la estructura institucional:
| Recurso | Perfil | Notas |
|---|---|---|
Questionnaire | FHIR R4 | item[] (preguntas y grupos), extension subQuestionnaire |
QuestionnaireResponse | FHIR R4 | questionnaire ref, item[] (respuestas) |
Task | emaig.cl/core#Task | code (ges-declaration / sdc-form / eno-notification), restriction.period (deadline) |
PlanDefinition | FHIR R4 | type (order-set / workflow-definition), action[] |
ActivityDefinition | FHIR R4 | code, topic, performer, resource (specimen type) |
A esto se suman los recursos que modelan estructura institucional y
comunicación: Organization, Location y Device para el
inventario físico; Practitioner y PractitionerRole para el staff;
Flag para alertas (GES, alergias críticas); Communication para
notificaciones y recordatorios; RelatedPerson para contactos del
paciente; Specimen para órdenes de laboratorio; y Consent para
consentimiento informado bajo Ley 20.584.
El puente entre user y Patient
Sección titulada «El puente entre user y Patient»Los recursos Patient y los registros de vault_users no se mapean
uno-a-uno por motivos de arquitectura: un mismo paciente puede ser
también staff (un médico que se atiende en su propia clínica), y un
mismo email puede tener un Patient en clínica A y otro distinto en
clínica B. El sistema separa los flujos de acceso para mantener la
distinción clara:
El staff entra con email y password —o el proveedor OAuth que
tenga habilitado su tenant. El JWT que recibe no incluye tenant_id:
es el tenantMiddleware el que lo resuelve consultando user_roles.
Si el usuario tiene roles en múltiples tenants, el frontend mantiene
el activo en localStorage y lo envía en cada request. El paciente
entra al portal por una vía completamente distinta, basada en RUT,
fecha de nacimiento y email; el sistema envía un OTP, valida y emite
un JWT con patient_id como claim. El patientAuthMiddleware
verifica ese token solo para rutas bajo /api/portal/*.
Motor de pricing en tres niveles
Sección titulada «Motor de pricing en tres niveles»El motor de pricing reconoce tres modos de pago distintos y los procesa con engines separados. La decisión de cuál aplica depende del tipo de Coverage del paciente y del tipo de cliente que está detrás (particular, aseguradora o convenio empresa):
El caso particular es el más simple: el sistema toma el
base_price_clp de service_charge_items. Para Fonasa el
copago-engine aplica el porcentaje correspondiente al tramo
(0%, 10% o 20% más diferencial); para Isapre se invoca al
imed-client que valida el bono físico o digital y aplica el monto
correspondiente. El caso convenio empresa no cobra al paciente: deja
un Invoice en estado draft que se factura al pagador
mensualmente.
State machines de los recursos principales
Sección titulada «State machines de los recursos principales»Cuatro recursos tienen estados visibles para el usuario y vale la pena documentar sus transiciones explícitamente. La cartografía siguiente muestra los caminos válidos para cada uno.
Encounter
Sección titulada «Encounter»El Encounter tiene tres estados activos más cancelación:
Appointment
Sección titulada «Appointment»El Appointment modela una reserva, con la particularidad de que
puede entrar al sistema como proposed (slot virtual generado por el
motor de scheduling) antes de pasar a booked:
Invoice
Sección titulada «Invoice»El Invoice tiene cuatro estados que reflejan el ciclo de vida del
documento legal:
Condition
Sección titulada «Condition»La Condition puede nacer como provisional (auto-detectada por el
motor clínico, por ejemplo cuando una Observation de presión
arterial supera 140) y solo pasa a confirmed cuando el médico la
valida explícitamente:
La Task modela tanto declaraciones GES como cuestionarios SDC
pendientes y notificaciones ENO. La transición a failed es
automática vía cron cuando vence restriction.period.end: