Plataforma integral que automatiza el análisis clínico nocturno, la ingesta de resultados de laboratorio mediante un código de tracking, el procesamiento de PDFs y la normalización de datos médicos mediante modelos de lenguaje (LLMs), reduciendo el riesgo de eventos adversos no detectados en residencias de adultos mayores.
Cuatro pipelines de IA trabajan de forma automatizada, desde la detección de cambios clínicos hasta la extracción de datos de laboratorio y el procesamiento de mensajes clínicos por WhatsApp.
El análisis no es el de un asistente clínico genérico ni de un hospital de agudos: está contextualizado, vía prompt de dominio y reglas, a una residencia geriátrica de larga estancia. Sabe que atiende a residentes internados —gerontes o adultos jóvenes con discapacidades neuromotrices—, no a pacientes ambulatorios. El objetivo no es curar ni dar de alta, sino cuidar y mejorar la calidad de vida, la dignidad y la funcionalidad, con un enfoque centrado en la persona, acompañando a la familia y frecuentemente paliativo. Por eso la polifarmacia se asume como la norma del contexto: la IA no alerta por la cantidad de fármacos, sino que enfoca en la deprescripción de medicación inapropiada (criterios Beers/STOPP). Y entiende los roles propios del contexto: Psicología como acompañamiento paliativo (no terapéutico), Trabajo Social en lo familiar, de recursos y legal (no la dimensión emocional), y los signos vitales como control diario protocolar de enfermería.
Cada noche, el sistema construye un contexto clínico completo de cada paciente que tuvo cambios durante el día, lo procesa con GPT-5 mediante function calling y genera un informe estructurado con alertas priorizadas, sugerencias por especialidad y previsiones clínicas.
Cada 3 minutos, Celery lee la casilla (el filtro es la base de datos, no el flag leído), persiste PDFs, extrae el código de tracking y el DNI con IA/OCR y valida código + DNI antes de cargar. El laboratorio (proveedor externo) sigue usando su email de siempre: con un simple código de tracking el pedido se vincula con su resultado, integrándolo con mínima fricción. Recupera errores de OCR validando por DNI, avisa al laboratorio ante error y confirma cuando carga OK; monitor, reproceso y relectura disponibles.
Un pipeline nocturno mapea automáticamente los nombres de componentes de laboratorio de distintos proveedores contra un catálogo canónico unificado, resolviendo sinónimos y ambigüedades con un LLM.
Django captura los mensajes crudos y un procesador IA los convierte en registros según el grupo: enfermería → reporte; farmacia → indicación médica (transcribiendo fotos/PDF y adjuntando el archivo); recepciones → ingreso de stock. El texto se normaliza sin inventar, el paciente se resuelve contra el padrón y la imagen solo se guarda si se identifica. Funciona como router con acuse de recibo: clasifica y deriva, y en el canal de recepciones responde al grupo —confirma cuando la carga fue OK y alerta cuando falla—, aunque todavía no conversa.
25 modelos clínicos monitoreados vía signals. Cada modificación encola automáticamente al paciente para análisis nocturno. Solo se procesan pacientes con datos nuevos — sin desperdicio de recursos.
El sistema elige automáticamente entre GPT-5 y GPT-5-mini según la complejidad del paciente (caídas, úlceras por presión, deterioro cognitivo/GDS, psicotrópicos), optimizando la relación costo/beneficio.
Monitoreo en tiempo real del consumo de API con alertas visuales y hard limit mensual configurable. El sistema se detiene automáticamente si se alcanza el límite de gasto.
Cada pieza del análisis (resumen, alertas, sugerencias, preguntas, previsiones, referencias) tiene pulgar arriba/abajo. Los votos del equipo se agregan por hash de contenido en un sistema de pesos, y cada noche una IA resume qué se valora bien o mal: ese resumen se inyecta como contexto en el análisis siguiente. El sistema mejora con el uso, sin reentrenar el modelo (esquema human-in-the-loop).
Cifras realistas basadas en el diseño del sistema y benchmarks del sector salud. No representan datos reales de producción.
| KPI | Antes | Después | Mejora |
|---|---|---|---|
| Revisión nocturna (20 pacientes) | 90 min | 8 min | −91% |
| Procesamiento por PDF de laboratorio | 10 min | 45 seg | −92% |
| Recepción de resultados por email | Manual | IMAP cada 3 min | Automatizada |
| Normalización de estudios (lote de 50) | 3 h (manual) | 4 min | −98% |
| KPI | Antes | Después | Mejora |
|---|---|---|---|
| Detección de interacciones medicamentosas | ~40% | ~92% | +52 pp |
| Alertas tempranas (<24 h) de deterioro | ~35% | ~78% | +43 pp |
| Completitud de datos de laboratorio | ~70% | ~97% | +27 pp |
| Indicador | Valor anual estimado |
|---|---|
| Reducción de horas de enfermería en revisión documental | ~450 horas/año |
| Ahorro en costos operativos | ~$6,750 USD/año |
| Costo operativo del sistema de IA | ~$420 USD/año |
| ROI estimado | ~16x |
Un vistazo a las decisiones de ingeniería, patrones y trade-offs detrás del sistema de IA.
| Componente | Tecnología | Justificación |
|---|---|---|
| Backend | Django 2.2 + DRF | ORM maduro, admin automático, ecosistema de signals para encolado. DRF expone API REST para frontend Vue.js e integraciones externas. |
| Async tasks | Celery + Redis | Desacopla llamadas bloqueantes a OpenAI e IMAP del ciclo request/response HTTP. Redis como broker por latencia mínima. |
| Scheduler | Celery Beat | Horarios precisos (22:00 análisis, IMAP laboratorio cada 3 min, 03:30 canonicalización) en BD, modificables sin deploy. |
| Base de datos | PostgreSQL | JSONField para resultados de lab y alertas, transacciones ACID, constraints unique en la cola. |
| WebSocket | Django Channels | Notificaciones en tiempo real al dashboard cuando un análisis termina, sin polling. |
| LLM cloud | OpenAI GPT-5 / GPT-5-mini | Function calling nativo permite que el modelo "lea" datos de la BD sin enviarlos todos en el prompt. JSON mode garantiza salida parseable. |
| Visión multimodal | OpenAI GPT-5.5 | Lee fotos y PDF (rasterizados a imagen): extrae artículos de recepciones y transcribe indicaciones médicas recibidas por WhatsApp. |
| Bridge WhatsApp | Node.js + whatsapp-web.js | Proceso supervisado en loopback que captura mensajes (texto, foto y PDF) de los grupos autorizados, valida la sesión por QR desde Django y reenvía a la API interna. |
| LLM local | Ollama | Alternativa offline para procesamiento de PDFs en entornos con restricciones de conectividad o privacidad. |
| OCR | PyMuPDF + pdfplumber + Tesseract | Tres capas: layout, tablas, imagen. Cada PDF puede requerir una combinación distinta. |
| Server | gunicorn + daphne + nginx | gunicorn para HTTP, daphne para WebSocket, nginx como proxy reverso y terminación SSL. |
El sistema no analiza todos los pacientes cada noche — solo aquellos cuyos datos clínicos cambiaron durante el día. Esto se logra con un patrón de cola reactiva basado en Django signals. En una residencia de 30 camas, típicamente 12-18 pacientes tienen cambios en un día, no 30.
# 25 modelos clínicos monitoreados — cada post_save encola al paciente
@receiver(post_save, sender=MedicacionCronica)
@receiver(post_save, sender=IndicacionMedica)
@receiver(post_save, sender=UlceraPorPresion)
@receiver(post_save, sender=Caida)
@receiver(post_save, sender=ControlEnfermeria)
# ... 20 modelos más ...
def encolar_paciente_para_analisis(sender, instance, **kwargs):
hc = obtener_historia_clinica(instance)
ahora = timezone.localtime()
hora_corte = ConfiguracionIA.get_solo().hora_corte_analisis
if ahora.hour < hora_corte:
fecha = ahora.date() # esta noche
else:
fecha = ahora.date() + timedelta(days=1) # noche siguiente
PacienteColaAnalisis.objects.get_or_create(
historia_clinica=hc,
fecha_programada=fecha,
defaults={'procesado': False}
)
En lugar de enviar todos los datos clínicos en el prompt, el sistema expone 12 herramientas que el modelo invoca bajo demanda. Cada tool ejecuta queries parametrizadas contra PostgreSQL. Límite de 5 rondas para evitar costos descontrolados. La respuesta final se exige en response_format={"type": "json_object"}.
tools = [
{"name": "obtener_medicacion_activa", # filtra anulada=False
"description": "Lista medicación crónica activa del paciente"},
{"name": "obtener_alertas_clinicas", # capacidad predictiva
"description": "Tendencias de caídas, UPP, polifarmacia, criterios Beers"},
{"name": "verificar_interacciones_medicamentosas",
"description": "Detecta duplicidades e interacciones graves"},
{"name": "obtener_laboratorios_pendientes",
"description": "Resultados recientes y pendientes de revisión"},
{"name": "obtener_evoluciones_recientes",
"description": "Notas de evolución de los últimos 7 días"},
{"name": "obtener_signos_vitales_recientes",
"description": "Últimos registros de FC, FR, TA, Tº, SpO₂"},
{"name": "obtener_historial_upp",
"description": "Evolución de una úlcera por presión específica"},
{"name": "consultar_datos_rango_fechas",
"description": "Query genérica por tipo de dato y período"},
# ... 4 herramientas más
]
| Error | Estrategia | Reintentos |
|---|---|---|
| Rate limit (429) | Exponential backoff: 1s → 2s → 4s → 8s → 16s | Hasta 5 |
| Timeout / conexión | Exponential backoff con jitter aleatorio | Hasta 3 |
| Quota agotada | Se detecta por mensaje de error, no se reintenta. El sistema frena hasta el mes siguiente. | 0 |
| JSON inválido del LLM | Se intenta reparar (truncamiento, escaping). Si falla, se marca como fallido. | 1 |
| Intento >30 min en proceso | Tarea de limpieza (cada 10 min) lo libera para reintento automático. | Automático |
sort_keys), tools idénticas y agrupación por modelo.reasoning_effort): las tareas mecánicas de WhatsApp (normalización, extracción, clasificación, resolución de paciente) usan el nivel mínimo para recortar tokens de salida; el análisis clínico usa más donde aporta. Editable desde el admin.Cada pieza que genera el análisis —resumen, cada alerta, cada sugerencia por área, cada pregunta, previsiones y referencias— tiene pulgar arriba / pulgar abajo, tanto en el detalle del análisis como en la pestaña "Estado actual" de la historia clínica. Los votos se agregan por un hash del contenido: la misma sugerencia valorada por varios profesionales o repetida en varios residentes converge a un único puntaje (sistema de pesos). Cada noche, antes del análisis, una IA resume las piezas mejor y peor valoradas de los últimos 30 días y ese resumen se inyecta como contexto en el prompt del análisis siguiente —después del prompt de sistema, para preservar el prompt caching—. Es un esquema human-in-the-loop de bajo costo (en el espíritu de RLHF, pero con pesos y prompting): el sistema mejora con el uso sin reentrenar el modelo. Un monitor de administración grafica la distribución de valoraciones por tipo y especialidad, los rankings mejor/peor y una previsualización exacta del contexto que se inyectará esa noche.
Equipo clínico vota cada pieza (👍/👎) · detalle + "Estado actual" → PiezaValoradaIA (peso acumulado por hash de contenido) → 21:30 · IA resume las piezas mejor/peor valoradas (últimos 30 días) → se inyecta como contexto (system) en el análisis de las 22:00 → el generador se auto-calibra sin reentrenar · monitor + preview
El pipeline de laboratorio no carga datos clínicos hasta validar el código de tracking + DNI. La integración con el laboratorio (proveedor externo) es de mínima fricción: usa el medio que ya tiene —un simple email— y un código de tracking vincula cada pedido con su resultado. El filtro de ingesta es la base de datos (se procesa si el correo no está guardado, sin depender del flag leído); los remitentes son configurables; el código normaliza puntos de miles y se recupera ante errores de OCR validando por DNI. Ante error avisa al laboratorio y al cargar OK envía confirmación. Hay monitor, reproceso desde el PDF guardado y relectura desde la casilla. Para líderes técnicos demuestra idempotencia y control de errores; para administradores, trazabilidad y menos trabajo manual.
IMAP FROM laboratorio (filtro real = DB: procesar si NO está guardado)
→ EmailLaboratorio + AdjuntoLaboratorioEmail (hash, estado, intentos)
→ IA/OCR extrae el código de tracking y el DNI (normaliza miles)
→ resolver la evaluación de laboratorio por el código de tracking
└ si no existe: recuperar variante validada por DNI
→ validar paciente por DNI
→ OK: ArchivoLaboratorio + resultados JSON + confirmación por email
→ Error definitivo: auditar + notificar al laboratorio
Monitor · reproceso (PDF guardado) · relectura (casilla)
Un bridge Node.js (whatsapp-web.js) supervisado por supervisord escucha solo los grupos habilitados y reenvía a una API interna con token compartido (incluye PDF, no solo fotos). Django persiste el mensaje crudo con dedupe por message_id. Antes de interpretar, se limpia el ruido del mensaje: se eliminan menciones (@número) y emojis sociales (🙏, 👍…), y los emojis con sentido clínico (🌡️→fiebre, 💊→medicación) se traducen a palabras, para no confundir ni alterar el dato clínico. El texto se normaliza con LLM sin inventar (preserva números, dosis y medicamentos, con validación posterior). El tipo de registro lo decide el grupo: enfermería → reporte; farmacia → indicación médica (transcribe la imagen/PDF y adjunta el archivo a la indicación); recepciones → ingreso de stock. La imagen solo se persiste si se identifica al paciente; lo dudoso se retiene con TTL para revisión. El sistema enruta y deriva, y en el canal de recepciones escribe de vuelta al grupo: confirmación con el detalle de artículos cuando la recepción se carga OK, y alerta con la foto cuando algo falla (anti-bucle con marcador invisible). No es conversacional todavía.
WhatsApp grupo autorizado (texto · foto · PDF)
→ bridge Node (whatsapp-web.js) supervisado · 127.0.0.1:3001
→ POST /api/whatsapp/messages/ · token compartido
→ WhatsappMessage (raw_payload, media a _pending/ hasta resolver paciente)
│
▼
normalizar texto (LLM anti-alucinación + validación) · resolver paciente
│
┌──────────────┼───────────────────────────┐
nursing pharmacy invoices
reporte indicación médica recepción
(transcribe foto/PDF + (visión: paciente
adjunta archivo) + artículos + stock)
│
▼
persistir media solo si hay paciente · trazabilidad al mensaje
router con acuse: confirma recepción OK / alerta error · no conversa
dudosos → revisión humana (needs_review)
El bridge Node nunca toca la BD: solo captura, filtra por grupos activos y reenvía. Django administra la sesión (QR de vinculación expuesto en /whatsapp/qr/ solo a usuarios staff), heartbeat y descubrimiento de grupos no configurados.
Cambios de turno multi-paciente: un solo mensaje de cambio de guardia de enfermería suele traer la novedad de varios residentes (un renglón por paciente). El sistema extrae la novedad de cada paciente y crea el registro de cada uno, e ignora el encabezado de guardia, la firma y las observaciones generales no atadas a un paciente. Los pacientes que no puede resolver de forma automática (apodos o nombres ambiguos) no se pierden: el mensaje queda marcado como multi-paciente y pasa a una pantalla de revisión por paciente donde el personal confirma cada uno.
| Escenario | Pacientes/día | Costo mensual est. | Requiere |
|---|---|---|---|
| 1 residencia (30 camas) | ~15 | ~$35 USD | — |
| 5 residencias (150 camas) | ~75 | ~$175 USD | — |
| Red de 20 (600 camas) | ~300 | ~$700 USD | Aumentar hard limit + considerar sharding de cola |
| 50+ residencias | ~750+ | ~$1,750+ USD | Múltiples workers Celery + rate limiting + posible batch processing |
┌──────────────────────────────────────────────────────────┐
│ CAMBIO EN MODELO CLÍNICO │
│ (medicación, signos vitales, UPP, caídas, laboratorio) │
└──────────────────────┬───────────────────────────────────┘
│ post_save signal
▼
┌──────────────────────────────────────────────────────────┐
│ PacienteColaAnalisis (cola nocturna) │
│ ¿Cambio antes de las 2 AM? → esta noche │
│ ¿Cambio después? → noche siguiente │
└──────────────────────┬───────────────────────────────────┘
│ Celery Beat 22:00
▼
┌──────────────────────────────────────────────────────────┐
│ construir_contexto_paciente(hc) │
│ • Medicación crónica • Signos vitales • UPP / caídas │
│ • Dispositivos • Evoluciones • Laboratorios │
│ • Kinesiología • Nutrición • Psicología • Cardio │
│ → Datos anonimizados antes del envío │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ decidir_modelo(complejidad) │
│ ¿Caídas, UPP activas, deterioro cognitivo? │
│ → gpt-5 sino → gpt-5-mini │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ OpenAI API — Function Calling (hasta 5 rondas) │
│ 12 herramientas: labs pendientes, evols, medicación, │
│ alertas predictivas, historial UPP, contactos, │
│ interacciones, signos vitales, datos cardio, etc. │
│ response_format: JSON estructurado │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ AnalisisNocturno (persistencia) │
│ • Resumen general • Alertas prioritarias │
│ • Sugerencias por área • Previsiones • Referencias │
│ • Modelo usado • Tokens consumidos • Fecha │
└──────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Dashboard profesional al iniciar turno │
└──────────────────────────────────────────────────────────┘
Pipeline completo: datos → signals → cola → Celery → LLM → informe → dashboard.
Tarjetas visuales con los 6 KPIs principales comparando el antes y después de la IA.
Proceso end-to-end: detección de cambio → encolado → contexto → LLM → resultado.
Panel con resumen, alertas prioritarias, sugerencias por área y metadata del modelo.
Barras agrupadas: revisión nocturna, procesamiento PDF, normalización de estudios.
Django · Celery · Redis · PostgreSQL · OpenAI GPT-5 (texto) + GPT-5.5 (visión) · Ollama · Node.js + whatsapp-web.js · PyMuPDF · Tesseract OCR