21 documentos en cuatro secciones, escritos sobre el comportamiento real del sistema —incluidos los casos que costaron diagnosticar esta semana. Manual de usuario (visible para todos): primeros pasos, recepción, toma de muestras, portal del enfermero y administración. Orientado a tareas concretas, no a describir pantallas. Documentación técnica: índice de módulos, turnero, formularios y firma digital, WhatsApp y bot, domicilios, webhook (migrado de WEBHOOK_ENDPOINTS.md) e inventario de endpoints. Arquitectura: visión general, enrutamiento y registro de módulos, roles y permisos, modelo de datos, integración con WhatsApp, y decisiones tomadas con su deuda técnica asociada. Operación: runbook de incidentes ordenado por síntoma, configuraciones críticas —incluido qué vive en Meta y no en la base— y despliegue. Se documentan explícitamente las trampas conocidas: role/role_id que hay que mantener sincronizados, las columnas can_* que el control de acceso no lee, las URL de plantilla que no se cambian desde el código, y las columnas históricas que quedaron en NULL sin forma de recuperarlas. README_DOCS.md apunta al módulo y explica cómo agregar páginas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
4.8 KiB
Markdown
105 lines
4.8 KiB
Markdown
# Módulo Turnero
|
|
|
|
El módulo más grande del sistema: 12 vistas y unos 70 endpoints. Gestiona la atención presencial completa.
|
|
|
|
## Vistas
|
|
|
|
| Vista | Para quién | Qué hace |
|
|
|---|---|---|
|
|
| `dashboard` | Admin, supervisor | Métricas del día, facturación, asistente LIA |
|
|
| `historial` | Admin, supervisor | Turnos de varios días, filtros, exportación |
|
|
| `bandeja` | Bacteriólogo, admin | Turnos del día con su detalle completo |
|
|
| `recepcion` | Recepcionista | Atención en el mostrador |
|
|
| `lugar` | Bacteriólogo | Estación de toma de muestras |
|
|
| `kiosko` | Público | El paciente saca su turno |
|
|
| `display_global` | Público | Pantalla de TV de la sala |
|
|
| `verificar_paciente` | Recepción | Consulta rápida de una ficha |
|
|
| `chat` | Recepción | Conversación de WhatsApp del turnero |
|
|
| `configuracion` | Admin | Lugares, dispositivos, plantillas, pantalla de TV |
|
|
|
|
`kiosko` y `display_global` son las **únicas rutas públicas** del sistema (`core/Router.php`): no hay quién inicie sesión en un televisor ni en el tótem de la entrada.
|
|
|
|
## Menú dinámico
|
|
|
|
`modules/turnero/module.php` no devuelve una lista fija: la arma según **el rol y la IP del equipo**.
|
|
|
|
```
|
|
recepcionista → chat, verificar paciente, TV, y su escritorio
|
|
bacteriólogo → bandeja y su estación
|
|
admin → todo
|
|
```
|
|
|
|
Si el equipo está en `turnero_dispositivos` (por IP o por cookie `turnero_token`), el usuario ve **solo su puesto**. Si no, los ve todos. Evita que alguien atienda desde el escritorio equivocado.
|
|
|
|
## Modelo de datos
|
|
|
|
```
|
|
turnero_sesiones un día de operación
|
|
└── turnero_turnos código, estado, tiempos, prioridad
|
|
├── turnero_solicitudes qué se hace y cuánto se cobró
|
|
│ ├── turnero_examen_items
|
|
│ └── turnero_muestras
|
|
├── turnero_consentimientos
|
|
└── turnero_comentarios
|
|
```
|
|
|
|
### Estados
|
|
|
|
```
|
|
espera → en_recepcion → en_espera_lugar → en_servicio → finalizado
|
|
ausente / cancelado
|
|
```
|
|
|
|
Solo `finalizado` cuenta como facturación real.
|
|
|
|
## Consentimientos
|
|
|
|
Se crean automáticamente desde **dos fuentes** que se acumulan:
|
|
|
|
| Fuente | Tabla |
|
|
|---|---|
|
|
| Por examen | `exam_tipo_consentimientos` |
|
|
| Por estación destino | `turnero_lugar_consentimientos` |
|
|
|
|
`get_consentimientos.php` los autocrea si faltan y calcula el estado de cada uno.
|
|
|
|
> En visitas de *solo entrega de muestras* no aplican los formularios por examen (no hay exámenes), pero **sí los de estación**. Por eso F-LAB-08 se exige igual.
|
|
|
|
## Tomas prolongadas
|
|
|
|
El formulario **F-LAB-28** cubre exámenes seriados. La lógica está repartida entre `ver_formulario_enviado.php` (render) y `modules/turnero/api/guardar_toma.php` (guardado).
|
|
|
|
- El esquema trae secciones para todos los exámenes posibles; se muestran solo las del examen marcado, mediante la `condicion` de cada separador.
|
|
- `_tomas_config` en `datos_respuestas` guarda qué ciclos se configuraron para ese paciente.
|
|
- Cada firma se guarda en su propio campo (`_tm00_f`, `_tm30_f`, …) junto con la hora y **la identidad de quien firmó**, resuelta en el servidor desde la sesión.
|
|
- Al firmar, el endpoint calcula cuándo toca la siguiente toma y actualiza `siguiente_toma_at`.
|
|
|
|
> La identidad se resuelve **en el servidor**, no se acepta del cliente: cada toma puede firmarla un profesional distinto y esa es la única fuente confiable. Las firmas anteriores al 3 de agosto de 2026 no tienen ese dato y no es recuperable.
|
|
|
|
## Muestras entre visitas
|
|
|
|
Una muestra que queda `pendiente` o `rechazada` reaparece cuando el paciente vuelve, con la bandera `es_pendiente_anterior` y los exámenes de la orden original.
|
|
|
|
Al recibirla, `turnero_muestras.recibida_en_turno_id` registra en qué turno se completó. Historial y bandeja usan ese dato para enlazar ambos turnos en los dos sentidos.
|
|
|
|
**El turno original no se modifica**: sigue finalizado. Solo se agrega la trazabilidad.
|
|
|
|
## LIA
|
|
|
|
`api/ai_chat.php` — asistente sobre Gemini Flash.
|
|
|
|
| Aspecto | Detalle |
|
|
|---|---|
|
|
| Contexto | Se arma en cada llamada con hasta 60 turnos del día y sus detalles |
|
|
| Historial | Los últimos intercambios se envían para que entienda preguntas de seguimiento |
|
|
| Tope de salida | `maxOutputTokens`; si Gemini corta, se avisa con `finishReason` |
|
|
| Presupuesto | `lab_config.lia_tokens_usados` contra `LIA_TOKENS_MAX` |
|
|
|
|
Se contabiliza `totalTokenCount`, que **incluye el contexto de entrada**. Como el contexto va completo en cada pregunta, el gasto por consulta es alto aunque la respuesta sea breve.
|
|
|
|
## Endpoints propios
|
|
|
|
Los del turnero usan `api/_helpers.php`, que provee `db()`, `inputJson()`, `jsonOk()`, `jsonError()`, `adminId()`, `requireTurnero()` y `notificarSSE()`.
|
|
|
|
`notificarSSE()` avisa a las pantallas conectadas para que se refresquen sin recargar.
|