================================================================================
  PLAN DE EVOLUCIÓN: BOT WHATSAPP  →  ERP MULTI-MÓDULO
  Proyecto: Panel de Laboratorio + WhatsApp Bot Manager
  Fecha: Abril 2026
================================================================================

────────────────────────────────────────────────────────────────────────────────
1. ESTADO ACTUAL — QUÉ TENEMOS
────────────────────────────────────────────────────────────────────────────────

MÓDULOS YA OPERATIVOS:
  ✔ WhatsApp Bot         – respuestas automáticas, menús, plantillas Meta
  ✔ Conversaciones       – bandeja de entrada, operadores asignados
  ✔ Mensajes Programados – campañas por fecha/hora
  ✔ Pacientes            – ficha clínica, historial
  ✔ Domicilios           – agenda de servicios a domicilio con flujo de estados
  ✔ Enfermeras/Personal  – portal propio, agenda diaria
  ✔ Órdenes Médicas      – PDF adjunto, autorización
  ✔ Formularios          – builder drag-drop, firma digital, enlace público
  ✔ Reportes             – ingresos, rendimiento de enfermeros, pagos
  ✔ Actividad/Auditoría  – log de cambios por usuario
  ✔ Usuarios & Roles     – RBAC completo (roles + role_modules)
  ✔ Configuración        – tarifas, laboratorio, WhatsApp

ARQUITECTURA ACTUAL:
  • Todos los archivos PHP en la raíz del proyecto (lab_*.php, index.php…)
  • API separada en /api/ y /api/lab/
  • Clases en /classes/ y /services/
  • Base de datos: 34 tablas, una sola BD "usite_whatsapp_bot"
  • RBAC: tabla roles + role_modules + SYSTEM_MODULES en config.php
  • Sesiones PHP nativas (login en admin_users)

PROBLEMA PRINCIPAL PARA CRECER:
  • Sin sistema de enrutamiento → cada módulo es un archivo suelto en raíz
  • SYSTEM_MODULES hardcodeado en config.php (hay que editarlo cada vez)
  • No hay separación de carpetas por módulo (todo mezclado)
  • Navegación duplicada en cada .php del módulo lab
  • Sin un punto de entrada único (todo camino directo al .php)


────────────────────────────────────────────────────────────────────────────────
2. VISIÓN TARGET — ERP MULTI-MÓDULO
────────────────────────────────────────────────────────────────────────────────

CONCEPTO:
  Un sistema central con un panel unificado donde cada "módulo" es un paquete
  autocontenido. El core gestiona: autenticación, routing, menú lateral,
  RBAC, notificaciones y eventos. Los módulos se enchufan al core sin
  modificar archivos existentes.

MÓDULOS PLANIFICADOS (oleadas):

  OLEADA 0 – Ya en producción (refactorizar para que usen la nueva estructura)
    - whatsapp_bot      → Bot + Conversaciones + Plantillas
    - lab_domicilios    → Agenda de domicilios
    - lab_pacientes     → Pacientes y fichas
    - lab_formularios   → Builder de formularios
    - lab_reportes      → Reportes e indicadores
    - lab_ordenes       → Órdenes médicas
    - lab_enfermeras    → Gestión de personal clínico
    - sistema           → Usuarios, Roles, Configuración

  OLEADA 1 – Nuevos módulos solicitados
    - turnero           → Sistema de turnos presenciales en recepción

  OLEADA 2 – Módulos futuros probables
    - registro_exams    → Registro directo de exámenes de laboratorio
                          (sin domicilio: paciente llega a la sede)
    - facturacion       → Facturación electrónica / DIAN (Colombia)
    - inventario        → Reactivos, insumos, stock mínimo
    - citas             → Agenda de citas con calendario visual
    - resultados        → Entrega digital de resultados (portal paciente)
    - crm_pacientes     → Campañas, seguimientos, cohortes


────────────────────────────────────────────────────────────────────────────────
3. NUEVA ESTRUCTURA DE CARPETAS
────────────────────────────────────────────────────────────────────────────────

/
├── core/                          ← Núcleo del ERP (NO tocar por módulos)
│   ├── App.php                    ← Bootstrap, registro de módulos
│   ├── Router.php                 ← Enrutador simple (mapea URL→módulo/acción)
│   ├── Auth.php                   ← Login/logout/sesión (extraído de config.php)
│   ├── Rbac.php                   ← hasModule(), requireModule(), permisos
│   ├── ModuleRegistry.php         ← Catálogo dinámico de módulos instalados
│   ├── Layout.php                 ← Renderiza sidebar, navbar, footer
│   └── Helpers.php                ← Funciones globales (esc, jsonOk, etc.)
│
├── modules/                       ← Un subdirectorio por módulo
│   ├── whatsapp_bot/
│   │   ├── module.php             ← Descriptor: nombre, slug, icono, permisos
│   │   ├── views/                 ← Las páginas (ex lab_*.php migradas)
│   │   └── api/                   ← Endpoints REST propios del módulo
│   │
│   ├── domicilios/
│   │   ├── module.php
│   │   ├── views/
│   │   └── api/
│   │
│   ├── turnero/                   ← NUEVO ★ (Oleada 1)
│   │   ├── module.php
│   │   ├── views/
│   │   │   ├── dashboard.php         ← Panel administrador (resumen del día)
│   │   │   ├── kiosko.php            ← Pantalla táctil tomar turno (sin login)
│   │   │   ├── display.php           ← Pantalla TV cola general (sin login)
│   │   │   ├── recepcion.php         ← Puesto recepcionista (llamar, solicitud, lugar)
│   │   │   ├── lugar.php             ← Puesto de servicio: llama turno, firma, atiende
│   │   │   └── configuracion.php     ← Lugares, exámenes, prioridades, sesiones
│   │   └── api/
│   │       ├── create_turno.php         ← Genera nuevo turno (kiosko)
│   │       ├── llamar_turno.php         ← Llama siguiente de una cola
│   │       ├── create_solicitud.php     ← Crea solicitud con exámenes, pago y lugar destino
│   │       ├── cambiar_estado.php       ← Avanza estado del turno
│   │       ├── get_cola.php             ← Cola de un lugar o recepción
│   │       ├── sse_turno.php            ← Server-Sent Events para pantallas TV
│   │       ├── send_consentimiento.php  ← Envía enlace de consentimiento por WhatsApp
│   │       └── get_consentimientos.php  ← Estado de firmas del turno
│   │
│   ├── registro_exams/            ← PENDIENTE — Oleada 2
│   │   ├── module.php
│   │   ├── views/
│   │   │   ├── registrar.php
│   │   │   ├── lista.php
│   │   │   └── detalle.php
│   │   └── api/
│   │       ├── save_examen.php
│   │       ├── get_examenes.php
│   │       ├── cambiar_estado.php
│   │       └── imprimir_etiqueta.php
│   │
│   └── sistema/
│       ├── module.php
│       ├── views/
│       │   ├── usuarios.php       ← lab_usuarios.php migrado
│       │   └── configuracion.php
│       └── api/
│
├── shared/                        ← Componentes reutilizables entre módulos
│   ├── components/
│   │   ├── sidebar.php            ← Menú lateral dinámico (según módulos del rol)
│   │   ├── navbar.php
│   │   ├── page_header.php
│   │   └── modal_confirm.php
│   └── js/
│       ├── erp-core.js            ← fetch wrapper, toasts, eventos globales
│       └── erp-table.js           ← Tablas con filtro, paginación, export
│
├── classes/                       ← (ya existe, mantener)
├── services/                      ← (ya existe, mantener)
├── api/                           ← API global (ya existe, mantener para bot)
├── config/
│   └── config.php                 ← Simplificado: SOLO BD y constantes base
│
├── public/                        ← (opcional futuro) assets compilados
│   └── assets/
│
└── index.php                      ← Punto de entrada único → instancia App.php


NOTA SOBRE MIGRACIÓN SIN ROMPER NADA:
  Los archivos lab_*.php actuales permanecen en raíz mientras se migran.
  Se crea un alias: cada modules/X/views/Y.php incluye el legacy si aún no
  se ha reescrito. No hay "big bang rewrite".


────────────────────────────────────────────────────────────────────────────────
4. SISTEMA DE ROLES Y PERMISOS (RBAC EXPANDIDO)
────────────────────────────────────────────────────────────────────────────────

MODELO ACTUAL:
  roles (id, name, slug, color, is_system)
    └── role_modules (role_id, module_slug)   ← solo read/write implícito

MODELO PROPUESTO — RBAC con acciones:

  ┌─────────────────────────────────────────────────────────────┐
  │  roles                                                       │
  │    id | name | slug | description | color | is_system       │
  └──────────────────┬──────────────────────────────────────────┘
                     │ 1:N
  ┌──────────────────▼──────────────────────────────────────────┐
  │  role_permissions  (reemplaza role_modules con más granularidad)
  │    id | role_id | module_slug | can_view | can_create        │
  │    can_edit | can_delete | can_export | extra_json           │
  └─────────────────────────────────────────────────────────────┘

  extra_json ejemplos:
    turnero    → {"puede_llamar": true, "puede_reasignar": false}
    reportes   → {"rango_max_dias": 90}
    whatsapp   → {"puede_broadcast": true}

ROLES DEL SISTEMA A DEFINIR:

  Slug              Nombre              Descripción
  ─────────────────────────────────────────────────────────────────
  superadmin        Super Admin         Acceso total, configura el sistema
  admin             Administrador       Acceso total sin configuración técnica
  recepcionista     Recepcionista       Turnero + Pacientes (Oleada 1)
  bacteriologo      Bacteriólogo        Registro exámenes + Resultados (Oleada 2)
  enfermero         Enfermero           Portal domicilios (ya existe)
  supervisor        Supervisor          Reportes + sin acceso a config
  operador_bot      Operador WhatsApp   Solo bandeja de conversaciones
  readonly          Solo lectura        Ver dashboards sin editar

  REGLA: is_system=1 en superadmin y admin → no se pueden borrar.
  Los demás roles son personalizables por el cliente.

FLUJO DE VERIFICACIÓN EN UNA VISTA:
  1. ¿Está logueado?                   → sino → login
  2. ¿El rol tiene module_slug?        → sino → 403 sin módulo
  3. ¿La acción requiere can_edit?     → verificar permiso específico
  4. ¿Hay restricción extra_json?      → verificar campo relevante

HELPER PROPUESTO (core/Rbac.php):
  hasModule($slug)                     → bool (ya existe en helpers)
  canDo($slug, $action)                → bool  (nuevo, $action = view/create/edit/delete/export)
  requireAction($slug, $action)        → lanza 403 si no tiene permiso


────────────────────────────────────────────────────────────────────────────────
5. MÓDULO TURNERO — DISEÑO DETALLADO
────────────────────────────────────────────────────────────────────────────────

PROPÓSITO:
  Diseñar, desarrollar e implementar un Sistema de Turnero Inteligente
  integrado con gestión de pacientes, clasificación por prioridades,
  flujo de atención por áreas (Recepción y Toma de Muestras) y envío
  automatizado de consentimientos informados mediante WhatsApp.

  El sistema optimiza la atención, mejora la trazabilidad del paciente
  y garantiza el cumplimiento legal mediante la gestión digital de
  consentimientos.

──── 5.1 PANTALLAS / INTERFACES ────────────────────────────────────────────

  A) Kiosko              (tablet/touch, sin login)
       → el paciente selecciona su tipo de prioridad y obtiene su turno

  B) Pantalla TV / Display  (pantalla grande, sin login, una por lugar)
       → muestra la cola del lugar y el turno siendo llamado en tiempo real
       → param ?lugar_id=X para que cada sala tenga su propia pantalla

  C) Puesto Recepción     (login requerido)
       → llama turnos de la cola general
       → vincula paciente, registra exámenes, recibe pago
       → crea la Solicitud Interna y asigna el turno a un Lugar

  D) Puesto Lugar / Estación de Servicio  (login requerido, genérico)
       → llama turnos de su propia cola (los asignados a él por recepción)
       → muestra exámenes requeridos y estado de consentimientos
       • el paciente firma directamente en la pantalla del puesto (o en su celular)
       → realiza el servicio y finaliza el turno
       → La misma vista sirve para: Toma de Muestras 1, Toma de Muestras 2,
         Rayos X, Ultrasonido, o cualquier área que se configure

  E) Panel Admin Turnero  (login requerido)
       → configura Lugares, exámenes, prioridades, sesiones

──── 5.2 SISTEMA DE PRIORIDADES ────────────────────────────────────────────

  Código  Tipo de paciente          Orden motor de cola
  ──────────────────────────────────────────────────────
  A       Niños                     1 (máxima prioridad)
  B       Embarazadas               2
  C       Adulto mayor              3
  D       Discapacidad              4
  E       Paciente general          5
  F       Muestras pendientes       6 (menor prioridad)

  Regla: dentro del mismo código se respeta el orden de llegada (FIFO).
  El motor de cola mezcla todas las filas usando el peso de prioridad + timestamp.

──── 5.3 FLUJO POR ÁREAS ────────────────────────────────────────────────────

  ÁREA 1 — RECEPCIÓN
  ═══════════════════
  Estados: espera → en_recepcion → finalizado_recepcion

  1. Kiosko/recepcionista genera turno (código + prioridad)
  2. Pantalla TV muestra la cola
  3. Recepcionista llama siguiente turno → estado "en_recepcion"
     → (opcional) Mensaje WhatsApp al paciente con su código de turno
  4. Se vincula el turno con un número de ventanilla
  5. Se registran:
       • Datos personales del paciente (buscar/crear en lab_pacientes)
       • Exámenes requeridos (lista de exam_tipos)
  6. Sistema consulta si los exámenes requieren consentimientos
  7. Si hay consentimientos pendientes:
       → Se genera enlace único y seguro por paciente
       → Se envía por WhatsApp usando WhatsAppService + plantilla aprobada Meta
     Si no se requieren → pasa directamente a área de muestras
  8. Turno avanza → "esperando_consentimiento" o "en_espera_muestra"

  ÁREA 2 — TOMA DE MUESTRAS
  ══════════════════════════
  Estados: en_espera_muestra → en_muestra → finalizado

  1. Bacteriólogo/auxiliar llama siguiente de la cola de muestras
  2. Sistema BLOQUEA la atención si no existen consentimientos firmados
     → muestra alerta visual; no permite avanzar el estado
  3. Si todos los consentimientos están firmados:
       → Estado "en_muestra"
       → Se validan los exámenes registrados en recepción
  4. Al terminar la toma: estado "finalizado"

──── 5.4 CONSENTIMIENTOS INFORMADOS ─────────────────────────────────────────

  INTEGRACIÓN CON MÓDULO FORMULARIOS (REUTILIZAR lo que ya existe):
  • Los consentimientos son Formularios del módulo lab_formularios/
  • Se marca en la configuración del formulario si es de tipo "consentimiento"
    usando un campo nuevo: tipo ENUM(formulario, consentimiento)
  • Enlace público ya existe en el módulo → se reutiliza tal cual
  • Firma digital ya implementada → se reutiliza (canvas + checkbox legal)

  CATÁLOGO DE TIPOS DE EXAMEN (exam_tipos):
  • Listado maestro de exámenes que ofrece el laboratorio (código + nombre)
  • Se crea en FASE 3 (Oleada 1) para dar soporte al turnero
  • La misma tabla se usa en Oleada 2 (registro_exams) sin cambios
  • Administrado desde la vista Configuración > Exámenes y Consentimientos

  RELACIÓN EXAMEN ↔ CONSENTIMIENTO (exam_tipo_consentimientos):
  • Un consentimiento (formulario) puede cubrir VARIOS tipos de examen
  • Un examen puede estar en un solo consentimiento o en ninguno
  • Se configura UNA VEZ en el panel de administración, no en cada turno
  • Ejemplo:
      "Toma de muestra de sangre"  →  consentimiento id=3 ("Consentimiento hemograma")
      "Glucosa en ayunas"          →  consentimiento id=3
      "Cultivo de orina"           →  consentimiento id=5 ("Consentimiento urocultivo")
      "Presión arterial"           →  (sin consentimiento)

  FLUJO DE CONSENTIMIENTOS:
  1. Recepción selecciona exámenes del turno (de la lista exam_tipos)
  2. Sistema busca en exam_tipo_consentimientos qué formularios aplican
     para los exámenes seleccionados → deduplica si varios exámenes
     comparten el mismo consentimiento → lista mínima de consentimientos
  3. Por cada consentimiento pendiente:
       • Se genera un token único (UUID) → link = /ver_formulario.php?token=XXX
       • Se registra en turnero_consentimientos con estado "pendiente"
  4. (Opcional) Recepción envía enlace por WhatsApp para pre-firma
     mientras el paciente espera en cola del Lugar
  5. En el Lugar (segunda llamada), el paciente puede firmar:
       OPCIÓN A: Hace clic en el enlace de WhatsApp en su celular
       OPCIÓN B: El auxiliar muestra la pantalla del puesto al paciente
                 y firma directamente ahí (mismo ver_formulario_enviado.php)
  6. Al firmar → estado "firmado" en turnero_consentimientos
  7. Vista del Lugar muestra ✓ verde / ✗ rojo en tiempo real (polling 5s)
     BLOQUEO total si alguno pendiente → no se puede iniciar el servicio

  MOMENTO DE LA FIRMA:
  • Preferiblemente antes de la segunda llamada (via WhatsApp, mientras espera)
  • Alternativamente EN la estación del Lugar (tablet del puesto)
  • NUNCA después de iniciado el servicio

  EVIDENCIA REGISTRADA POR FIRMA:
    • Fecha y hora exacta
    • IP del firmante
    • User-Agent (dispositivo)
    • Versión del formulario/consentimiento al momento de firmar

──── 5.5 TABLAS BD NUEVAS ────────────────────────────────────────────────────

  exam_tipos                   ← catálogo maestro de exámenes del laboratorio
    id, codigo VARCHAR(20),    ← ej. "HEM", "GLU", "URIN"
    nombre VARCHAR(150),       ← ej. "Hemograma completo"
    categoria VARCHAR(80),     ← ej. "Hematología", "Química"
    activo TINYINT(1)
    NOTA: Esta tabla es compartida con Oleada 2 (registro_exams).
          En Oleada 2 se le agregan columnas precio_base, requiere_ayunas, etc.

  exam_tipo_consentimientos    ← relación M:N examen ↔ consentimiento
    id,
    exam_tipo_id  INT FK exam_tipos,
    formulario_id INT FK lab_formularios,  ← debe tener tipo='consentimiento'
    UNIQUE KEY (exam_tipo_id, formulario_id)
    NOTA: Si un examen no tiene registro aquí, no se exige consentimiento.
          Si varios exámenes del mismo turno apuntan al mismo formulario_id,
          se genera UN SOLO consentimiento (deduplicado en la lógica PHP).

  turnero_lugares              ← sub-módulo: estaciones/lugares de servicio
    id, nombre VARCHAR(100),   ← ej. "Toma de Muestras 1", "Rayos X"
    descripcion TEXT,
    activo TINYINT(1),
    sort_order INT             ← orden en la pantalla TV general
    NOTA: Recepción NO es un lugar; es el primer paso implícito del flujo.
          Cada lugar tiene su propia cola y su propia pantalla TV.

  turnero_prioridades          ← catálogo editable de códigos A-F
    id, codigo CHAR(1) UNIQUE, nombre VARCHAR(100),
    orden_peso INT,            ← número menor = mayor prioridad
    color VARCHAR(7),
    activo TINYINT(1)

  turnero_sesiones
    id, fecha DATE,
    abierto_por INT FK admin_users, cerrado_por INT FK admin_users,
    inicio_at DATETIME, fin_at DATETIME

  turnero_turnos
    id, sesion_id FK turnero_sesiones,
    numero INT,                ← correlativo dentro de la sesión
    codigo VARCHAR(10),        ← "A001", "B012"
    prioridad_id INT FK turnero_prioridades,
    paciente_nombre VARCHAR(150),   ← capturado en kiosko (opcional)
    paciente_cel VARCHAR(20),
    estado ENUM(
      espera,                  ← código asignado, en cola general
      en_recepcion,            ← recepcionista lo está procesando
      en_espera_lugar,         ← solicitud creada, en cola del Lugar
      en_servicio,             ← siendo atendido en el Lugar
      finalizado,              ← proceso completo
      ausente,                 ← no se presentó
      cancelado
    ),
    lugar_destino_id INT FK turnero_lugares NULL,  ← asignado por recepción
    creado_at DATETIME,
    llamado_recepcion_at DATETIME,
    inicio_recepcion_at DATETIME,
    fin_recepcion_at DATETIME,
    llamado_lugar_at DATETIME,
    inicio_lugar_at DATETIME,
    fin_lugar_at DATETIME,
    atendido_recepcion_por INT FK admin_users NULL,
    atendido_lugar_por INT FK admin_users NULL

  turnero_solicitudes          ← solicitud interna de agendamiento (crea recepción)
    id,
    turno_id INT FK turnero_turnos UNIQUE,  ← 1 solicitud por turno
    paciente_id INT FK lab_pacientes,       ← vinculado en recepción
    lugar_id INT FK turnero_lugares,        ← lugar de destino
    total_cobrado DECIMAL(10,2),
    metodo_pago ENUM(efectivo, transferencia, tarjeta, eps, cortesia),
    observaciones TEXT,
    creado_por INT FK admin_users,
    creado_at DATETIME

  turnero_examen_items         ← exámenes de la solicitud
    id, solicitud_id FK turnero_solicitudes,
    exam_tipo_id INT FK exam_tipos,
    creado_at DATETIME

  turnero_consentimientos      ← consentimientos del turno (deduplicados)
    id, turno_id FK turnero_turnos,
    formulario_id INT FK lab_formularios,
    token VARCHAR(64) UNIQUE,  ← enlace único de firma
    estado ENUM(pendiente, enviado, visto, firmado, rechazado),
    enviado_at DATETIME,
    firmado_at DATETIME,
    ip_firma VARCHAR(45),
    ua_firma VARCHAR(500),
    version_formulario INT     ← snapshot de la versión al momento de firma

  formularios.tipo             ← columna nueva en tabla existente lab_formularios
    ALTER TABLE lab_formularios ADD COLUMN tipo
      ENUM('formulario','consentimiento') DEFAULT 'formulario';

  DIAGRAMA DE RELACIONES CLAVE:

    lab_formularios (tipo='consentimiento')
           │ 1
           │
    exam_tipo_consentimientos ─────── exam_tipos
           (M:N)                         │ 1
                                         │ N
                              turnero_examen_items
                                         │ N
                                         │ 1
                              turnero_solicitudes ───── turnero_lugares
                                         │ 1
                                         │ 1
                              turnero_turnos
                                         │ 1
                                         │ N
                              turnero_consentimientos
           (un registro por formulario distinto requerido por el turno)

──── 5.6 INTEGRACIONES CON MÓDULOS EXISTENTES ──────────────────────────────

  • lab_pacientes   → buscar/crear paciente al crear la solicitud en recepción
  • lab_formularios → los consentimientos SON formularios del sistema
  • ver_formulario_enviado.php → página pública de firma (ya funcional)
                                  se abre también desde la pantalla del Lugar
  • WhatsAppService → envío del enlace de consentimiento (opcional, entre llamadas)
  • Plantillas Meta → crear plantilla aprobada "consentimiento_turno"
  • exam_tipos       → catálogo compartido con Oleada 2 (registro_exams)

──── 5.7 PANTALLA TV / DISPLAY ─────────────────────────────────────────────

  Hay VARIOS tipos de pantalla TV, todas sin login:

  TV RECEPCIÓN  (?display=recepcion)
    • Cola general (turnos en estado 'espera' ordenados por prioridad)
    • Turno actualmente en recepción (código grande + "DIRÍJASE A RECEPCIÓN")

  TV POR LUGAR  (?display=lugar&lugar_id=X)
    • Cola del Lugar X (turnos en 'en_espera_lugar' con ese lugar_destino_id)
    • Turno en servicio activo del Lugar X (código grande)
    • Cadaestación tiene su propia TV configurada con su lugar_id

  COMPORTAMIENTO COMÚN:
    • Actualización por SSE en tiempo real sin reload
    • Colores grandes por código de prioridad
    • Animación + sonido corto al llamar un turno


────────────────────────────────────────────────────────────────────────────────
6. MÓDULO REGISTRO DE EXÁMENES — DISEÑO DETALLADO  [OLEADA 2 — pendiente]
────────────────────────────────────────────────────────────────────────────────

CONCEPTO:
  Registrar exámenes de análisis clínico cuando el paciente llega a la sede
  (a diferencia de lab_domicilios que es a domicilio).
  Incluye: recepción de muestra, trazabilidad, estado de procesamiento,
  y eventualmente entrega de resultados.

TABLAS BD NUEVAS:

  exam_tipos
    id, codigo (ej. "HEM", "GLU"), nombre, categoria, precio_base,
    requiere_ayunas TINYINT, instrucciones TEXT, activo

  exam_ordenes (la "orden de trabajo" del laboratorio)
    id, paciente_id FK lab_pacientes,
    numero_orden VARCHAR(20) UNIQUE,   ← ej. "ORD-2026-00123"
    origen ENUM(presencial, domicilio, whatsapp, referido),
    medico_remitente VARCHAR(150),
    entidad_pagadora VARCHAR(150),
    tipo_pago ENUM(particular, eps, convenio),
    total_cobrado DECIMAL(10,2),
    estado ENUM(pendiente, en_proceso, parcial, completado, entregado, anulado),
    observaciones TEXT,
    recibido_por INT FK admin_users,
    creado_at, actualizado_at

  exam_items (los exámenes individuales dentro de una orden)
    id, orden_id FK exam_ordenes,
    tipo_id FK exam_tipos,
    estado ENUM(pendiente, muestra_tomada, en_analisis, resultado_listo, entregado),
    muestra_tipo VARCHAR(50),  ← sangre, orina, hisopado...
    muestra_recibida_at,
    resultado TEXT,
    resultado_pdf VARCHAR(300),
    procesado_por INT FK admin_users,
    entregado_at

  exam_etiquetas (para imprimir en los tubos)
    id, item_id FK exam_items,
    codigo_barras VARCHAR(50) UNIQUE,
    impreso_at, impreso_por INT FK admin_users

FLUJO:
  1. Recepcionista busca/crea paciente (reutiliza lab_pacientes)
  2. Crea orden → agrega exámenes de la lista exam_tipos
  3. Sistema genera número de orden y código de barras por tubo
  4. Se imprime etiqueta (ZPL o PDF)
  5. Bacteriólogo cambia estado → en_análisis → resultado_listo
  6. Administrador entrega resultados (descarga PDF / enlace portal)

RELACIÓN CON MÓDULOS EXISTENTES:
  • Paciente → usa lab_pacientes (ya existe)
  • Si viene de un domicilio → exam_ordenes.origen = 'domicilio',
    vincular con lab_domicilios (campo opcional domicilio_id)
  • Si tiene orden médica adjunta → vincular con lab_ordenes_medicas


────────────────────────────────────────────────────────────────────────────────
7. BASE DE DATOS — PLAN DE MIGRACIONES
────────────────────────────────────────────────────────────────────────────────

REGLA: todas las migraciones son ADITIVAS (ALTER ADD, CREATE TABLE).
       NUNCA DROP COLUMN ni RENAME en producción sin respaldo previo.

Migración 001 – RBAC expandido
  ALTER TABLE role_modules ADD COLUMN can_view    TINYINT(1) DEFAULT 1;
  ALTER TABLE role_modules ADD COLUMN can_create  TINYINT(1) DEFAULT 0;
  ALTER TABLE role_modules ADD COLUMN can_edit    TINYINT(1) DEFAULT 0;
  ALTER TABLE role_modules ADD COLUMN can_delete  TINYINT(1) DEFAULT 0;
  ALTER TABLE role_modules ADD COLUMN can_export  TINYINT(1) DEFAULT 0;
  ALTER TABLE role_modules ADD COLUMN extra_json  JSON NULL;
  RENAME TABLE role_modules TO role_permissions;  ← (o alias)

Migración 002 – Nuevos roles
  INSERT INTO roles (name, slug, description, color, is_system) VALUES
    ('Super Admin',   'superadmin',    'Acceso total al sistema',      '#dc3545', 1),
    ('Recepcionista', 'recepcionista', 'Turnero y recepción de muestras', '#198754', 0),
    ('Bacteriólogo',  'bacteriologo',  'Análisis y resultados',        '#0dcaf0', 0),
    ('Supervisor',    'supervisor',    'Solo reportes y consultas',    '#fd7e14', 0),
    ('Operador Bot',  'operador_bot',  'Gestión de conversaciones',    '#6f42c1', 0);

Migración 003 – Turnero  [Oleada 1]
  CREATE TABLE exam_tipos               (...)   ← catálogo maestro compartido con Oleada 2
  CREATE TABLE exam_tipo_consentimientos(...)   ← M:N: examen ↔ formulario consentimiento
  CREATE TABLE turnero_lugares          (...)   ← sub-módulo: estaciones configurables
  CREATE TABLE turnero_prioridades     (...)   ← catálogo A-F configurable
  CREATE TABLE turnero_sesiones        (...)   ← sesión diaria de atención
  CREATE TABLE turnero_turnos          (...)   ← turno con lugar_destino_id
  CREATE TABLE turnero_solicitudes     (...)   ← solicitud interna de agendamiento
  CREATE TABLE turnero_examen_items    (...)   ← exámenes de la solicitud
  CREATE TABLE turnero_consentimientos (...)   ← firma digital por consentimiento
  ALTER TABLE lab_formularios ADD COLUMN tipo ENUM('formulario','consentimiento') DEFAULT 'formulario'

Migración 004 – SYSTEM_MODULES dinámica (mover de config.php a BD)  [Oleada 1]
  CREATE TABLE system_modules (
    slug        VARCHAR(100) PK,
    name        VARCHAR(150),
    icon        VARCHAR(50),    ← "fas fa-vials"
    category    VARCHAR(50),    ← "lab", "bot", "sistema", "clinico"
    route       VARCHAR(200),   ← URL base del módulo
    is_active   TINYINT(1),
    sort_order  INT,
    created_at  TIMESTAMP
  )

  Poblar con los módulos actuales + turnero.
  registro_exams se agrega en Migración 005 (Oleada 2).
  config.php mantiene el array como fallback hasta que la BD esté lista.

Migración 005 – Registro de exámenes  [Oleada 2]
  -- exam_tipos ya existe desde Migración 003; solo agregar columnas:
  ALTER TABLE exam_tipos ADD COLUMN precio_base DECIMAL(10,2) NULL;
  ALTER TABLE exam_tipos ADD COLUMN requiere_ayunas TINYINT(1) DEFAULT 0;
  ALTER TABLE exam_tipos ADD COLUMN instrucciones TEXT NULL;
  CREATE TABLE exam_ordenes  (...)
  CREATE TABLE exam_items    (...)
  CREATE TABLE exam_etiquetas(...)


────────────────────────────────────────────────────────────────────────────────
8. ROUTER Y PUNTO DE ENTRADA
────────────────────────────────────────────────────────────────────────────────

OPCIÓN RECOMENDADA (sin framework, compatible con estructura actual):

  index.php  →  incluye core/App.php
  App.php    →  lee $_GET['m'] (módulo) y $_GET['v'] (vista)
              →  verifica sesión + permisos via Rbac.php
              →  incluye modules/{m}/views/{v}.php
              →  si no existe → 404 amigable

  URLS LIMPIAS con .htaccess:
    /turnero/dashboard       →  ?m=turnero&v=dashboard
    /turnero/display         →  ?m=turnero&v=display   (sin login)
    /lab/domicilios          →  ?m=domicilios&v=index
    /lab/pacientes           →  ?m=lab_pacientes&v=index

  .htaccess ya existe en el proyecto → solo agregar RewriteRules.

  COMPATIBILIDAD:
    Los lab_*.php en raíz siguen funcionando directamente.
    El router añade una capa adicional, no reemplaza lo existente.
    Migración gradual: mover módulos uno a uno al nuevo sistema.


────────────────────────────────────────────────────────────────────────────────
9. LAYOUT Y NAVEGACIÓN UNIFICADA
────────────────────────────────────────────────────────────────────────────────

PROBLEMA ACTUAL:
  Cada lab_*.php repite el mismo sidebar a mano (>50 líneas duplicadas).
  Si se agrega un módulo hay que editar todos los archivos.

SOLUCIÓN:
  shared/components/sidebar.php  →  se genera dinámicamente desde:
    1. La tabla system_modules (activos)
    2. Filtrado por los módulos que tiene el rol del usuario logueado
    3. Agrupados por "category" (Bot, Laboratorio, Clínico, Sistema)

  Cada layout nueva vista incluye:
    <?php include APP_ROOT . '/shared/components/sidebar.php'; ?>

  El sidebar detecta la URL activa y resalta el elemento correspondiente.

GRUPOS DEL MENÚ LATERAL:
  🤖 WhatsApp
      Conversaciones | Bot & Menús | Plantillas | Programados
  🏥 Laboratorio
      Dashboard | Domicilios | Pacientes | Enfermeras | Órdenes
  🧪 Clínico (Oleada 2)
      Registro Exámenes | Resultados
  🎟️ Turnero (Oleada 1)
      Dashboard | Recepción | Toma de Muestras | Kiosko | Configuración
  📊 Reportes
      Ingresos | Rendimiento | Exportar
  ⚙️ Sistema
      Usuarios & Roles | Configuración | Actividad


────────────────────────────────────────────────────────────────────────────────
10. PLAN DE IMPLEMENTACIÓN — FASES
────────────────────────────────────────────────────────────────────────────────

── BASE DEL SISTEMA ─────────────────────────────────────────────────────────

FASE 0 – Preparación (1-2 días) [SIN impacto en producción] ✅ COMPLETADA
  ✓ Crear carpetas: core/, modules/, shared/
  ✓ Extraer Auth.php y Rbac.php desde config.php y _helpers.php
  ✓ Crear shared/components/sidebar.php unificado
  ✓ Migración 001: expandir role_permissions (columnas can_*)
  ✓ Migración 002: insertar nuevos roles
  ✓ Agregar nuevos slugs a SYSTEM_MODULES en config.php

FASE 1 – Core: Router y estructura modular (1 semana) ✅ COMPLETADA
  ✓ Crear core/Router.php y core/App.php
  ✓ Crear core/Layout.php, core/Helpers.php (core/Rbac.php ya existía de FASE 0)
  ✓ Punto de entrada erp.php (coexiste con index.php legacy)
  ✓ Activar .htaccess con URLs limpias (/modulo/vista → erp.php?m=&v=)
  ✓ Mover módulos existentes a modules/ (module.php + views/index.php stubs para 11 módulos)

FASE 2 – SYSTEM_MODULES dinámica (2 días) ✅ COMPLETADA
  ✓ Migración 004: tabla system_modules (con INSERT IGNORE, oleada, sort_order)
  ✓ core/ModuleRegistry.php: lee BD → module.php → SYSTEM_MODULES (fallback en cascada)
  ✓ Sidebar lee desde ModuleRegistry en lugar de config.php (dinámico por categoría)
  ✓ Interfaz /erp.php?m=usuarios&v=modulos (toggle activo + sort_order)

── MÓDULOS NUEVOS ────────────────────────────────────────────────────────────

FASE 3 – Turnero MVP — Oleada 1

  FASE 3.1 – Base de datos y configuración (1 día) ✅ COMPLETADA
  ──────────────────────────────────────────────────
  ✓ Migración 003: crear tablas
    CREATE exam_tipos                  ← catálogo maestro (compartido con Oleada 2)
    CREATE exam_tipo_consentimientos   ← M:N: examen↔formulario
    CREATE turnero_lugares             ← estaciones de servicio configurables
    CREATE turnero_prioridades (insertar datos A-F por defecto)
    CREATE turnero_sesiones
    CREATE turnero_turnos              ← con lugar_destino_id
    CREATE turnero_solicitudes         ← solicitud interna de agendamiento
    CREATE turnero_examen_items        ← FK solicitud_id
    CREATE turnero_consentimientos
    ALTER lab_formularios ADD tipo ENUM(formulario, consentimiento)
  ✓ modules/turnero/module.php (descriptor)
  ✓ Registrar slug 'turnero' en system_modules
  ✓ Asignar módulo turnero a roles recepcionista y bacteriologo

  ✅ FASE 3.2 – Motor de cola y API core (1 día)  [COMPLETADA]
  ──────────────────────────────────────────────
  ✓ api/create_turno.php
      • Crea/abre sesión del día si no existe
      • Genera número correlativo + código (ej. "A001")  con FOR UPDATE
      • Admite paciente_nombre + paciente_cel (opcional)
      • Retorna código, número y posición en cola
  ✓ api/llamar_turno.php
      • Recibe area (recepcion|lugar) + lugar_id
      • Motor de prioridades: SELECT menor orden_peso → menor creado_at
      • Área lugar: solo turnos en estado en_espera_lugar para ese lugar
      • Retorna turno llamado o null si cola vacía
  ✓ api/cambiar_estado.php
      • Avanza estado con mapa de transiciones explícito
      • Bloquea en_espera_lugar→en_servicio si hay consentimientos pendientes (422)
      • Registra timestamps llamado_at / inicio_at / fin_at por área
  ✓ api/get_cola.php
      • Cola actual por área (recepcion | lugar + lugar_id)
      • Turno activo + listado + estadísticas de sesión
  ✓ api/sse_turno.php
      • SSE: emite 'cola_update' cuando cambia sse_ping_at en la sesión
      • ALTER TABLE IF NOT EXISTS para sse_ping_at (idempotente)
      • Keep-alive ": ping" cada 15 s, forzar refresh cada 30 s
      • Compatible con pantalla TV sin necesidad de polling activo

  ✅ FASE 3.3 – Pantallas sin login (1 día)  [COMPLETADA]
  ────────────────────────────────────────
  ✓ views/kiosko.php
      • Pantalla táctil fullscreen 3 pasos: prioridad → datos → ticket
      • Botones A-F cargados de BD (icono + nombre + descripción + color)
      • Campo opcional: nombre y celular con validación de formato
      • Muestra código + posición en cola; auto-reinicio tras 30 s
      • Sin login; fetch POST a create_turno.php
  ✓ views/display.php
      • Pantalla TV fullscreen, sin login (HTML puro, sin <?php)
      • ?display=recepcion → cola general
      • ?display=lugar&lugar_id=X → cola del Lugar X
      • SSE EventSource a sse_turno.php con reconexión exponencial
      • Código activo gigante con color de prioridad + lista de espera
      • Web Audio API para beep al llamar — sin archivos externos
      • Stats footer: en espera / atendidos / total / tiempo promedio

  ✅ FASE 3.4 – Puesto de Recepción (1 día)  [COMPLETADA]
  ────────────────────────────────────────
  ✓ views/recepcion.php
      • Requiere login + módulo turnero; layout 2 columnas: cola | ficha
      • Cola izquierda: turnos 'espera' con color de prioridad, polling 8 s
      • Botón "Llamar siguiente" → llama_turno.php area=recepcion
      • Ficha derecha al llamar:
        - Nombre del kiosko pre-cargado en buscador
        - Buscador de paciente: GET /api/lab/get_pacientes.php?busqueda=
        - Selector de lugar destino + checkboxes de exámenes por categoría
        - Campo cobro + forma de pago + observaciones
        - Sección consentimientos (aparece tras guardar si aplican)
        - Botón "Pasar a lugar" → cambiar_estado en_espera_lugar
        - Botón "Ausente"
  ✓ api/create_solicitud.php
      • POST, requireTurnero(); validaciones completas
      • DELETE + INSERT (permite re-guardar para correcciones)
      • Inserta turnero_solicitudes + N turnero_examen_items en transacción
      • Devuelve solicitud + consentimientos_requeridos (estado actual de c/u)
  ✓ api/send_consentimiento.php
      • Deduplica formularios: N exámenes → M formularios distintos (M ≤ N)
      • Genera UUID por cada formulario; INSERT IGNORE para no duplicar
      • No reenvía consentimientos ya firmados o rechazados
      • Intenta sendTemplateMessage('consentimiento_turno'); fallback texto plano
      • Normaliza celular (+57 por defecto Colombia)
      • Registra enviado_at; retorna lista con enlace_firma por c/u
      • Retorna lista de consentimientos enviados

  ✅ FASE 3.5 – Puesto Lugar / Estación de Servicio (1 día)  [COMPLETADA]
  ────────────────────────────────────────────────────────
  ✓ views/lugar.php  (?lugar_id=X)
      • Genérica: funciona para cualquier lugar (Muestras, Rayos X, etc.)
      • Overlay selector si no viene lugar_id en URL; botón "Cambiar lugar"
      • Requiere login (isUserLoggedIn)
      • Cola izquierda: turnos 'en_espera_lugar' del lugar, polling 7 s
      • Botón "Llamar siguiente" → llamar_turno.php area=lugar
      • Ficha derecha al llamar (carga vía get_consentimientos.php?incluir_solicitud=1):
        - Datos del paciente (nombre, doc, fecha nac, celular)
        - Exámenes como pills coloreadas
        - Consentimientos con estado visual (firmado/enviado/visto/pendiente)
        - BLOQUEO: "Iniciar atención" disabled + banner rojo si hay pendientes
        - Botón "Reenviar WhatsApp" → send_consentimiento.php
        - Botón "Firmar aquí" → abre ver_formulario_enviado.php en modal/iframe
        - "Iniciar atención" → cambiar_estado en_servicio (validado en API)
        - "Finalizar" → estado finalizado
        - "Regresar a cola" → estado en_espera_lugar
        - "Ausente" → estado ausente
      • Polling de consentimientos cada 5 s mientras hay turno activo
  ✓ api/get_consentimientos.php
      • GET público dentro del módulo (ruta protegida por la vista)
      • ?turno_id=X → retorna arreglo de consentimientos con nombre del formulario
      • ?incluir_solicitud=1 → también retorna solicitud + paciente + exámenes

  FASE 3.6 – Firma digital (reutilizar formularios) (0.5 días) ✅ COMPLETADA
  ──────────────────────────────────────────────────────────────
  ✅ Marcar formularios de consentimiento en la interfaz de admin:
    lab_formularios → campo tipo = 'consentimiento'
  ✅ Al firmar ver_formulario_enviado.php:
      • Detectar si el token corresponde a un turno (turnero_consentimientos)
      • Actualizar turnero_consentimientos.estado = 'firmado'
      • Registrar ip_firma, ua_firma, firmado_at, version_formulario
      • Guardar firma_svg (base64 PNG) en turnero_consentimientos
      • Notificar SSE → lugar.php/recepcion.php actualizan en tiempo real
  ✅ Vista lugar.php tiene botón "Firmar aquí" → abre el mismo
    ver_formulario_enviado.php en un modal (iframe), con canvas de firma
    para firma física en la tablet del puesto
  ✅ lugar.php detecta la firma en tiempo real (polling 5s) +
    postMessage instantáneo desde el iframe al firmarse

  FASE 3.7 – Panel Admin Turnero + configuración (0.5 días) ✅ COMPLETADA
  ───────────────────────────────────────────────────────────
  ✅ views/configuracion.php — 4 pestañas:

    TAB 1: Lugares ✅
      • CRUD de turnero_lugares (nombre, descripción, activo, orden)
      • Cada lugar muestra su URL de display → botón copia rápida
      • URLs de recepción y kiosko también accesibles
      • API: save_lugar.php (GET/POST crear/actualizar/eliminar con guardia FK)

    TAB 2: Exámenes y Consentimientos ✅
      • CRUD de exam_tipos (código, nombre, categoría, activo)
      • Por cada exam_tipo: selector de consentimiento (formularios tipo='consentimiento')
        → Guarda/actualiza en exam_tipo_consentimientos (deduplicado)
      • Vista inversa: por consentimiento → lista de exámenes vinculados
      • API: save_ex_tipo.php (GET/POST; eliminación bloqueada si hay solicitudes)

    TAB 3: Prioridades ✅
      • Edición de nombre, color, orden, activo (sin eliminación)
      • Picker de color sincronizado con campo hexadecimal
      • API: save_prioridad.php (validación de formato #RRGGBB)

    TAB 4: Sesión y WhatsApp ✅
      • Abrir / cerrar / reabrir sesión del día con un clic
      • Historial de sesiones (últimas 30)
      • Configuración de plantilla WhatsApp (nombre + código idioma)
      • API: sesion_turno.php (acciones: abrir, cerrar, reabrir, config_wa)

  ✅ views/dashboard.php (admin)
      • Resumen del día: turnos atendidos, tiempo promedio, pendientes
      • KPI cards + barras por prioridad + tarjetas por lugar
      • Tabla detalle de todos los turnos con búsqueda local
      • Consentimientos del día (desglose por estado)
      • Selector de fecha (consulta histórica)
      • Auto-refresh cada 30s (solo si fecha = hoy)
      • API: get_dashboard.php (consultas agregadas por sesión)
      • Exportar CSV: export_csv.php (BOM UTF-8, 24 columnas, listo para Excel)

FASE 4 – Oleada 2 (Registro de Exámenes + futuros)

  FASE 4.1 – Base de datos (migration 005) ✅ COMPLETADA
  ─────────────────────────────────────────────────────
  ✓ migrations/005_registro_exams.sql
      ALTER exam_tipos → precio_base, requiere_ayuno, horas_ayuno, instrucciones
      CREATE exam_ordenes     (código EX-YYYYMMDD-NNNN, prioridad, estado, FK paciente)
      CREATE exam_items       (estado por ítem, notas_tecnico)
      CREATE exam_muestras    (trazabilidad: tipo, código_barras, estado)
      CREATE exam_resultados  (campo, valor, unidad, referencia, es_anormal)
      UPDATE system_modules SET is_active=1 WHERE slug='registro_exams'
      INSERT role_module_access para bacteriólogo y recepcionista
  ✓ run_005_registro_exams.php (runner idempotente)

  FASE 4.2 – Módulo descriptor y helpers ✅ COMPLETADA
  ─────────────────────────────────────────────────────
  ✓ modules/registro_exams/module.php
  ✓ modules/registro_exams/api/_helpers.php
      requireExams(), generarCodigoOrden(), jsonOk(), jsonError(), db()

  FASE 4.3 – Lista del día ✅ COMPLETADA
  ─────────────────────────────────────────────────────
  ✓ modules/registro_exams/views/lista.php
      KPI cards (total/pendientes/en_proceso/completas)
      Filtros: fecha + estado + búsqueda libre
      Auto-refresh 45 s; paginación
  ✓ modules/registro_exams/api/get_ordenes.php
      GET ?fecha=&estado=&busqueda=&page=&limit=
      Incluye edad calculada, contadores de ítems y muestras

  FASE 4.4 – Nueva orden ✅ COMPLETADA
  ─────────────────────────────────────────────────────
  ✓ modules/registro_exams/views/nueva_orden.php
      Buscador de paciente (reutiliza GET /api/lab/get_pacientes.php)
      Catálogo de exámenes en checkbox agrupados por categoría
      Filtro de búsqueda en catálogo; badge de ayuno y precio
      Pre-carga paciente si viene ?solicitud_id= del turnero
  ✓ modules/registro_exams/api/save_orden.php
      POST crear/editar; genera código EX-YYYYMMDD-NNNN
      Sync de ítems: inserta nuevos, elimina pendientes removidos
      Redirige a orden creada al guardar

  FASE 4.5 – Detalle de orden + resultados ✅ COMPLETADA
  ─────────────────────────────────────────────────────
  ✓ modules/registro_exams/views/orden.php
      Cards: paciente, datos médicos (prioridad + estado badges)
      Tabla de muestras + modal "Registrar muestra" (tipo + barcode + estado)
      Lista de ítems con estado individual + dropdown cambio de estado
      Modal de resultados: tabla editable (campo/valor/unidad/referencia/anormal)
      Botón "Marcar entregada"; auto-cierre de orden cuando todos listos
  ✓ modules/registro_exams/api/get_orden.php
      GET ?id= → orden + ítems + resultados + muestras
  ✓ modules/registro_exams/api/save_resultado.php
      POST reemplaza resultados de un ítem; auto-marca resultado_listo
      Auto-cierra orden a 'completa' si todos los ítems tienen resultado
  ✓ modules/registro_exams/api/cambiar_estado_item.php
      POST tipo=item|muestra|orden; crea muestras nuevas (tipo=muestra, id=null)
      Al crear muestra: pasa ítems pendientes a muestra_tomada + orden a en_proceso

  FASE 4.6 – Etiqueta imprimible ✅ COMPLETADA
  ─────────────────────────────────────────────────────
  ✓ modules/registro_exams/views/etiqueta.php
      Página autónoma A6 landscape (sin sidebar, sin Layout::open)
      Código de barras visual (fuente Libre Barcode 128 via Google Fonts)
      Datos: paciente, doc, edad, EPS, prioridad, lista de exámenes
      Aviso de ayuno si algún ítem lo requiere
      window.print() automático al cargar (delay 800 ms para fuente)

  □ Facturación, Inventario, CRM, Portal Paciente…
  □ API pública con tokens (para integraciones)
  □ App móvil (portal enfermero actual → Progressive Web App)


────────────────────────────────────────────────────────────────────────────────
11. CONVENCIONES TÉCNICAS A SEGUIR
────────────────────────────────────────────────────────────────────────────────

NOMENCLATURA:
  • Tablas BD:    snake_case, prefijadas por módulo (turnero_, exam_, lab_)
  • Archivos PHP: snake_case (save_turno.php, get_examenes.php)
  • Clases PHP:   PascalCase (TurneroService.php, ExamOrder.php)
  • Slugs:        siempre lowercase con guiones (turnero, registro-exams)
  • API endpoints: REST-ish, verbos en el nombre (get_, save_, delete_)

SEGURIDAD:
  • Toda API: requireMethod() + verificar sesión + verificar permiso
  • Parámetros: siempre sanitizar y tipificar antes de usar en SQL
  • Queries: siempre PDO preparado (ya implementado en Database.php)
  • Pantallas sin login (kiosko, display): NUNCA exponer datos sensibles,
    solo número de turno y servicio.
  • Subida de archivos: misma lógica de /upload.php (validar MIME + ext)
  • CSRF: para formularios de mutación, incluir token en sesión

ESTILO DE CÓDIGO:
  • Frontend: Bootstrap 5 + Font Awesome 6 (ya en uso, mantener)
  • Sin jQuery nuevo; usar fetch() nativo (ya en uso)
  • Toasts de notificación: usar patrón ya existente en index.php
  • Responsive: mobile-first (portal enfermero se usa desde celular)

API RESPONSE FORMAT (ya establecido, mantener):
  Éxito:  { "ok": true,  "data": {...}, "message": "..." }
  Error:  { "ok": false, "error": "...", "code": 4XX }


────────────────────────────────────────────────────────────────────────────────
12. DECISIONES CONFIRMADAS
────────────────────────────────────────────────────────────────────────────────

  ✔ 1. Sede única — No se requiere sede_id en las tablas.

  ✔ 2. Facturación electrónica DIAN — No se maneja en esta fase.
        Queda pendiente para una oleada futura.

  ✔ 3. Portal de resultados para pacientes — No se requiere aún.
        La entrega de resultados es solo gestión interna por ahora.

  ✔ 4. App móvil / PWA — No se usará PWA.
        Solo se aplica diseño responsive a: pantalla TV (display) y kiosko.
        El resto del sistema funciona como web normal desde PC.

  ✔ 5. Multi-tenant — Instalación para un solo cliente.
        Sin aislamiento multi-tenant en la BD.

  ✔ 6. Consentimientos por examen — Se configura UNA VEZ en el panel admin.
        Relación M:N: exam_tipos ↔ lab_formularios (exam_tipo_consentimientos).
        El recepcionista no los marca manualmente; el sistema los calcula
        automáticamente según los exámenes del turno.


────────────────────────────────────────────────────────────────────────────────
12B. DECISIONES PENDIENTES DE CONFIRMAR
────────────────────────────────────────────────────────────────────────────────

  1. ¿Los exámenes integran con algún analizador automático (interfaz LIS)
     o el resultado se digita manual?
     (Impacta diseño de Oleada 2 — registro_exams)

  2. ¿El turno se puede tomar ANTES de llegar (turno virtual por WhatsApp)?
     (Conectaría el módulo Turnero con el Bot de WhatsApp)

  3. ¿Cuántas ventanillas de Recepción y cuántas estaciones de servicio
     (Toma de Muestras, Rayos X, etc.) se configurarán inicialmente?
     (Ayuda a diseñar la pantalla TV y el layout de recepción)

  4. ¿Debe bloquearse TOTALMENTE la atención si hay un consentimiento pendiente,
     o solo mostrar advertencia y dejar que el auxiliar decida continuar?


────────────────────────────────────────────────────────────────────────────────
13. RESUMEN EJECUTIVO
────────────────────────────────────────────────────────────────────────────────

  Lo que TENEMOS es una base sólida:
    ✓ BD bien estructurada con 34 tablas
    ✓ RBAC funcional (roles + módulos)
    ✓ API consistente con helpers reutilizables
    ✓ Autenticación robusta con bcrypt
    ✓ Integración WhatsApp activa
    ✓ Módulos de laboratorio maduros

  Lo que HAY QUE CONSTRUIR para el ERP:
    → Estructura de carpetas por módulo (core/ + modules/)      ✓ hecho
    → Router centralizado y sidebar dinámico                    ✓ hecho
    → RBAC con granularidad de acciones (can_view/create/edit/delete/export)
    → Módulo Turnero — Oleada 1 (5-7 días)
         FASE 3.1 – BD y configuración (turnero_lugares + solicitudes + exam_tipos)
         FASE 3.2 – Motor de cola + API core (create_solicitud, llamar, SSE)
         FASE 3.3 – Pantallas sin login (kiosko + display TV por lugar)
         FASE 3.4 – Puesto de Recepción (solicitud + asignar lugar)
         FASE 3.5 – Puesto Lugar/Estación genérico (consentimientos + servicio)
         FASE 3.6 – Firma digital (reutilizar formularios, firmar en tablet)
         FASE 3.7 – Panel Admin + configuración (4 tabs)
    → SYSTEM_MODULES en BD (en lugar de hardcoded en config.php) ✓ hecho
    → Módulo Registro de Exámenes — Oleada 2 (5-7 días, pendiente)

  Estrategia: EVOLUCIÓN INCREMENTAL, no reescritura.
    El sistema sigue funcionando en producción mientras se construyen
    los nuevos módulos en paralelo. Solo se migra lo viejo al nuevo
    sistema cuando el nuevo está validado y estable.

================================================================================
  Documento preparado por GitHub Copilot — Abril 2026
  Próxima revisión: tras confirmar decisiones de la sección 12
================================================================================
