FAQ y troubleshooting
Esta página recopila las preguntas que tienden a aparecer cuando algo no se comporta como debería, o cuando el equipo necesita una decisión rápida sobre cómo proceder. Las respuestas están escritas para PMs, soporte y staff técnico que necesita resolver un caso concreto sin abrir el código.
¿Cómo se agrega un tenant nuevo?
Sección titulada «¿Cómo se agrega un tenant nuevo?»El provisioning lo maneja EMA Vault. La operación es un POST a
/api/platform/tenants con header X-Platform-Key desde el worker
de Vault, y EMA Lab recibe los datos esenciales en emashared.tenants.
Si la fila correspondiente en tenant_aidbox_credentials no está
presente, el handler responde 503 hasta que Vault complete el setup
OAuth con Aidbox. El custom domain del tenant se configura
verificando que tenantMiddleware resuelva correctamente el slug
desde el subdominio.
El check-in está rechazando muestras con “barcode duplicate”
Sección titulada «El check-in está rechazando muestras con “barcode duplicate”»La causa habitual es que la estrategia de generación de barcode usó
un identificador que ya existió. La revisión obligada es la fila
correspondiente en barcode_strategy para el tenant y la
verificación de que el algoritmo respete el dígito Luhn:
// Generador correctoimport { generateAccessionId } from '@/lib/barcode';const id = generateAccessionId(tenant.barcode_strategy);// retorna {id}{Luhn}Si el conflicto persiste, conviene resetear el counter en
tenant_config o forzar nueva estrategia. En clientes con volumen
alto el counter puede llegar a sus topes y necesitar rotación
periódica.
El TM no puede validar resultados de cierta sección
Sección titulada «El TM no puede validar resultados de cierta sección»El sistema requiere autorización explícita por sección, registrada en
la tabla tech_section_auth (decisión L028). Sin esa fila, el
endpoint /api/validation/stage1 responde con 403:
SELECT * FROM emalab.tech_section_authWHERE tenant_id = '<id>' AND user_id = '<id>';-- columnas: section_id, validation_level (technical/clinical), is_activeLa solución la ejecuta el admin desde Settings → Users → <user> → Tab Secciones, marcando el checkbox de las secciones autorizadas y
seleccionando el nivel correspondiente (technical o clinical).
El QR de un reporte no verifica
Sección titulada «El QR de un reporte no verifica»Hay cuatro causas comunes que conviene chequear en orden. La primera
es que el verify code haya expirado: los códigos viven 90 días
post-emisión por defecto, y al re-firmar el DR se emite uno nuevo. La
segunda es que el DR haya sido enmendado (status=amended); en
ese caso el verify code anterior se invalida y el reporte vigente es
el nuevo. La tercera es un slug incorrecto en el URL: el formato
exacto es /portal/verify/:code?tenant={slug} y cualquier
variación produce error. La cuarta es el rate limit del endpoint
público (treinta consultas por minuto): si un usuario hizo demasiados
intentos en el mismo minuto, queda temporalmente bloqueado.
El diagnóstico definitivo lo da GET /api/portal/verify/:code?tenant={slug}
que retorna un JSON con valid: true/false y, cuando es falso, la
razón específica.
Stewardship CLSI M39 dice “no hay suficientes isolates”
Sección titulada «Stewardship CLSI M39 dice “no hay suficientes isolates”»La guía CLSI M39 requiere un mínimo de treinta aislamientos por
combinación organismo-antibiótico para producir un reporte
estadísticamente significativo. Si el laboratorio tiene volumen menor
en ese trimestre, el motor excluye la combinación afectada del
reporte. Las opciones son ampliar el periodo de agregación
configurando period_months a seis o doce meses, o esperar
acumulación de casos en los trimestres siguientes.
Hemocultivo positivo no genera Task crítica
Sección titulada «Hemocultivo positivo no genera Task crítica»La causa probable es que critical_value_source no marcó la
Observation como crítica (decisión L047). La verificación rápida es:
SELECT * FROM emalab.critical_value_sourceWHERE observation_fhir_id = '<id>';Si la consulta no retorna filas, el trigger no llegó a ejecutarse. El
re-trigger manual se hace con POST /api/micro/cultures/:id/escalate-critical
y razón explícita; queda registrado en el audit como fuente manual.
La importación molecular falla con “parser not registered”
Sección titulada «La importación molecular falla con “parser not registered”»El formato del archivo no matchea ningún parser registrado. La lista
de parsers activos está disponible en GET /api/molecular/parsers.
Si el vendor del archivo es uno nuevo, la solución pasa por agregar
un parser en src/lib/molecular/parsers/<vendor>.ts y registrarlo en
parser-registry.ts. Si el formato sí existe pero la detección
falla, lo más común es que el vendor haya cambiado el header del
archivo en una actualización del software del equipo: hay que
ajustar la función match() del parser correspondiente.
El cron de stewardship no corre
Sección titulada «El cron de stewardship no corre»Tres condiciones deben cumplirse simultáneamente. Primero, el
trigger debe estar declarado en wrangler.toml con
crons = ["0 3 1 1,4,7,10 *"]. Segundo, el cron debe estar activado
en el dashboard de Cloudflare después del deploy. Tercero, los logs
del Worker tienen que mostrar [cron] stewardship-quarterly en cada
ejecución programada. Si el flag dry_run quedó activo (decisión
L072), el reporte se queda en draft sin escribir FHIR; el admin debe
re-correrlo con dry_run=false para emitir el reporte real.
Aparece “Tenant no resuelto” al loguearse
Sección titulada «Aparece “Tenant no resuelto” al loguearse»Cuatro causas explican casi todos los casos:
| Causa | Diagnóstico |
|---|---|
Usuario sin fila en user_roles con product=‘lab’ | SELECT * FROM emashared.user_roles WHERE user_id AND product='lab' |
tenant_id en localStorage no matchea | Limpiar localStorage del navegador |
tenants.is_active = false | Activar tenant en emashared.tenants |
| Subdominio incorrecto | Verificar URL coincide con slug del tenant |
La solución estándar consiste en que el admin del tenant ejecute
INSERT INTO emashared.user_roles (user_id, tenant_id, product, role) VALUES (..., 'lab', 'lab_tech') con los valores correspondientes.
Una TM ve órdenes de un cliente externo que no le corresponde
Sección titulada «Una TM ve órdenes de un cliente externo que no le corresponde»Esto es un incidente crítico de seguridad y debe escalarse a infosec
inmediatamente. La causa probable es que el client isolation
middleware no se ejecutó, o que user_client_access está vacío para
el usuario (lo que significa, por convención, que ve todos los
clientes del tenant). La verificación es:
SELECT * FROM emashared.user_client_accessWHERE user_id = '<tm-uuid>' AND tenant_id = '<tenant>';Si el query retorna cero filas, el usuario tiene acceso a todos los clientes por diseño y conviene confirmar con el admin si esa era la intención. Si retorna filas pero el TM ve órdenes fuera de esa lista, es un bug del middleware y hay que reportar.
El Hub on-premise no se conecta
Sección titulada «El Hub on-premise no se conecta»Las cuatro causas más frecuentes son las siguientes. La primera es
que el token Hub haya expirado: se renueva con
POST /api/hub/renew-token y el admin actualiza el archivo del
contenedor Docker. La segunda es que el firewall corporativo del site
bloquee el outbound; conviene probar con ping desde dentro del
contenedor a la URL del Worker para descartar bloqueos de red. La
tercera es que el CONNECTION_MODE configurado no encaje con el
firewall: algunos laboratorios bloquean SSE y conviene cambiar a
modo polling (ADR-017). La cuarta es que el Hub Docker esté
desactualizado, lo que se resuelve haciendo pull de la última imagen
ghcr.io/emahealth/emalab-hub:0.2.0. El diagnóstico arranca con
docker logs emalab-hub | tail -100.
El conector HL7v2 inbound rechaza con “API key inválida”
Sección titulada «El conector HL7v2 inbound rechaza con “API key inválida”»Las API keys inbound se comparan con hash SHA-256 en tiempo constante
(decisión L089), no en plain text. Eso elimina vectores de timing
attack pero hace que cualquier discrepancia produzca 401 sin pista
del problema. Las causas habituales son que el cliente esté enviando
una key vieja después de una rotación, que el admin haya rotado pero
no notificó al cliente, o que el header X-API-Key venga con
espacios o caracteres extra. La rotación se hace con
POST /api/connectors/hl7v2/rotate-key y la nueva key debe enviarse
al cliente externo por canal seguro —1Password, Bitwarden o
equivalente— nunca por email plano.
El reporte XLSX viene vacío
Sección titulada «El reporte XLSX viene vacío»El writer XLSX usa fflate y OOXML manual. Si el motor (engine puro)
retorna cero filas, el sheet sale vacío. La verificación rápida es
agregar dry_run=true al endpoint correspondiente para ver cuántas
filas el motor produciría sin escribir el archivo.
Comportamiento ante caída de Aidbox
Sección titulada «Comportamiento ante caída de Aidbox»Cuando Aidbox queda inaccesible, la SPA sigue operativa para los
módulos no FHIR: settings, billing, caja y audit funcionan con
normalidad. Las páginas que requieren FHIR —worklists, results,
reports— responden 503. Las escrituras a audit_logs,
cash_movements y qc_lots siguen ocurriendo, igual que el cron de
retry-pending. El Hub on-premise sigue acumulando resultados en su
buffer local hasta que Aidbox vuelve a estar disponible. Cuando el
servicio se restablece, el retry sweep cada quince minutos re-emite
los DetectedIssue pendientes y connector_retry_queue procesa los
mensajes diferidos.
Comportamiento ante caída de Supabase
Sección titulada «Comportamiento ante caída de Supabase»Aquí la degradación es severa. La autenticación falla por completo, así que no se pueden crear sesiones nuevas. Las sesiones existentes con JWT válido pueden seguir leyendo FHIR (Aidbox no depende de Supabase para validar el token), pero los worklists, billing, audit y las tasks de scheduling dejan de operar. Supabase es dependencia crítica y conviene comunicar a los usuarios cuando ocurre un incidente.
Comportamiento ante caída de emadte
Sección titulada «Comportamiento ante caída de emadte»Las invoices quedan en estado draft. El callback de facturación pendiente se
acumula en cola y, cuando emadte se restablece, el retry asíncrono
procesa lo pendiente. La operación clínica no se ve afectada: los
pacientes reciben atención y los reportes se entregan. Solo se
demora la facturación electrónica.