# 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.
