Documentación: diagramas, kiosko y chat, catálogos, respaldos y endpoints documentados

Diagramas en texto dentro de bloques de código, en vez de capturas: se editan
como texto, no pesan en el repositorio y no quedan desactualizados solos. Se
agregan el recorrido de un turno, quién firma cada consentimiento, el paso de
una muestra pendiente entre visitas y la línea de tiempo de las tomas seriadas.

Páginas nuevas del manual: kiosko y pantallas de TV (las dos que funcionan sin
nadie operándolas), y chat de WhatsApp, que explica por qué el bot deja de
responder cuando un operador toma la conversación y de dónde sale el límite de
24 horas.

Páginas técnicas nuevas: registro de exámenes, y los catálogos del laboratorio
agrupados en una sola página por compartir la misma forma. Operación suma
respaldos y recuperación, incluyendo qué datos históricos no son recuperables.

Los 10 endpoints que no tenían comentario de cabecera ahora lo tienen, así que
la tabla generada por {{endpoints}} queda completa: 83 de 83.

DOCUMENTACION_LAB.md y README_LAB.md quedan como puntero al módulo;
WEBHOOK_ENDPOINTS.md se elimina por estar ya migrado a la sección técnica.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-04 10:49:02 -05:00
co-authored by Claude Opus 5
parent 81c007c516
commit 71c9dfd424
21 changed files with 454 additions and 806 deletions
+5 -518
View File
@@ -1,522 +1,9 @@
# Documentación del Sistema de Laboratorio # Obsoleto
> **Sistema**: Laboratorio Clínico — Módulos de Agendamiento y Formularios Este documento quedó desactualizado y se conserva solo por historial de git.
> **Última actualización**: 27/03/2026
--- La documentación vigente está **dentro del sistema**, en el módulo Soporte:
## Tabla de contenidos /erp.php?m=soporte&v=documentacion
1. [Módulo de Agendamiento (Domicilios)](#1-módulo-de-agendamiento-domicilios) Ver `README_DOCS.md` para saber cómo se organiza y cómo agregar páginas.
- [¿Qué es?](#qué-es)
- [Archivos involucrados](#archivos-involucrados)
- [Base de datos](#base-de-datos)
- [Roles y permisos](#roles-y-permisos)
- [Flujo de estados](#flujo-de-estados)
- [Funcionalidades](#funcionalidades)
- [API Endpoints](#api-endpoints)
2. [Módulo de Formularios](#2-módulo-de-formularios)
- [¿Qué es?](#qué-es-1)
- [Archivos involucrados](#archivos-involucrados-1)
- [Base de datos](#base-de-datos-1)
- [Roles y permisos](#roles-y-permisos-1)
- [Flujo completo](#flujo-completo)
- [Tipos de campos del Builder](#tipos-de-campos-del-builder)
- [Firma digital](#firma-digital)
- [Enlace público y vigencia](#enlace-público-y-vigencia)
- [PDF y visualización del documento](#pdf-y-visualización-del-documento)
- [Sello de integridad SHA-256](#sello-de-integridad-sha-256)
- [Firma del profesional](#firma-del-profesional)
- [API Endpoints](#api-endpoints-1)
---
## 1. Módulo de Agendamiento (Domicilios)
### ¿Qué es?
El módulo de agendamiento gestiona los **servicios de toma de muestras a domicilio**. Permite crear, asignar, seguir y completar visitas médicas domiciliarias. Los administradores gestionan la agenda desde `lab_domicilios.php`; los enfermeros gestionan su propia agenda desde `enfermero_portal.php`.
---
### Archivos involucrados
| Archivo | Descripción |
|---|---|
| `lab_domicilios.php` | Vista principal del admin — tabla con filtros, detalle del domicilio, formularios recibidos, exportar CSV |
| `enfermero_portal.php` | Portal exclusivo del enfermero — su agenda personal del día, ordenada por hora, con tarjetas colapsables |
| `lab_enfermeras.php` | CRUD del personal de enfermería |
| `classes/lab/Domicilio.php` | Clase ORM — crear, editar, cambiar estado, estadísticas |
| `classes/lab/Enfermera.php` | Clase ORM — CRUD, agenda por enfermero, carga de trabajo |
| `classes/lab/Asignacion.php` | Clase ORM — asignar / reasignar / liberar enfermero a domicilio |
| `api/lab/get_domicilios.php` | GET — lista de domicilios con filtros y estadísticas del día |
| `api/lab/save_domicilio.php` | POST — crear, actualizar o cambiar estado |
| `api/lab/update_domicilio_enfermero.php` | POST — el enfermero avanza el estado desde su portal |
| `api/lab/my_agenda.php` | GET — agenda del enfermero actualmente autenticado |
| `api/lab/save_asignacion.php` | POST — asignar o reasignar enfermero |
| `api/lab/get_asignaciones.php` | GET — asignaciones por fecha |
| `api/lab/registrar_pago.php` | POST — registrar pago de un domicilio |
| `api/lab/save_servicio_extra.php` | POST — agregar servicio realizado durante la visita |
| `api/lab/get_notas_domicilio.php` | GET — notas clínicas y libres del domicilio |
| `api/lab/upload_nota_imagen.php` | POST — subir imagen adjunta a una nota |
| `api/lab/crear_desde_whatsapp.php` | GET/POST — crear paciente u orden desde una conversación de WhatsApp activa |
---
### Base de datos
#### Tabla `lab_domicilios`
Tabla principal. Cada fila es un servicio a domicilio.
| Campo | Tipo | Descripción |
|---|---|---|
| `id` | INT PK | Identificador único |
| `paciente_id` | INT FK | Paciente al que se le realiza el servicio |
| `orden_id` | INT FK NULL | Orden médica adjunta (opcional) |
| `direccion` | TEXT | Dirección completa de la visita |
| `ciudad` | VARCHAR(100) | Ciudad |
| `barrio` | VARCHAR(100) | Barrio |
| `indicaciones_dir` | TEXT | Referencias o indicaciones adicionales ("apto 302, tocar campanilla") |
| `fecha_programada` | DATE | Fecha de la visita |
| `hora_programada` | TIME | Hora de la visita |
| `tipo_servicio` | VARCHAR(100) | Tipo de servicio (toma de muestra, etc.) |
| `tipo_cliente` | ENUM | `particular` / `seguro` / `eps` |
| `examenes_solicitados` | TEXT | Lista de exámenes (cuando no hay orden médica) |
| `seguro_nombre` | VARCHAR | Nombre del seguro o EPS |
| `autorizacion` | VARCHAR | Número de autorización |
| `valor_domicilio` | DECIMAL | Valor del servicio |
| `valor_copago` | DECIMAL | Copago a cargo del cliente |
| `copago_laboratorio` | DECIMAL | Copago al laboratorio |
| `pago_estado` | ENUM | `pending` / `pagado` / `exento` |
| `pago_modo` | ENUM | `efectivo` / `transferencia` / `otro` |
| `pago_monto` | DECIMAL | Monto pagado |
| `pago_fecha` | DATETIME | Fecha del pago |
| `pago_notas` | TEXT | Notas sobre el pago |
| `estado` | ENUM | Ver [Flujo de estados](#flujo-de-estados) |
| `motivo_cancelacion` | TEXT | Motivo si fue cancelado (obligatorio) |
| `fecha_reprogramada` | DATE | Nueva fecha si fue reprogramado |
| `hora_llegada` | TIME | Registrada automáticamente al iniciar la visita |
| `hora_salida` | TIME | Registrada automáticamente al completar |
| `observaciones` | TEXT | Observaciones del resultado de la visita |
| `muestras_tomadas` | TEXT | Lista de muestras obtenidas |
| `notas_admin` | TEXT | Notas internas del equipo administrativo |
| `creado_por` | INT FK | Usuario que creó el registro |
#### Tabla `lab_enfermeras`
Personal de enfermería disponible para asignación.
Campos: `id`, `numero_documento`, `tipo_documento` (CC/CE/TI/PA), `nombre_completo`, `telefono`, `telefono_alt`, `email`, `zona`, `notas`, `is_active`.
#### Tabla `lab_asignaciones`
Asignación de enfermero a domicilio. Máximo un enfermero activo por domicilio (`UNIQUE KEY` en `domicilio_id`).
Campos: `id`, `domicilio_id`, `enfermera_id`, `asignada_por`, `estado` (`asignada` / `confirmada` / `liberada` / `completada`), `notas`.
#### Tabla `lab_servicios_extra`
Servicios realizados por el enfermero durante la visita, adicionales a la orden original.
Tipos disponibles: `inyeccion`, `cura`, `nebulizacion`, `toma_muestra`, `tension_arterial`, `glucometria`, `otro`.
Campos: `id`, `domicilio_id`, `descripcion`, `tipo`, `notas`, `requiere_pago`, `valor`, `realizado_por`.
#### Tabla `lab_domicilio_notas`
Notas registradas por el enfermero durante la visita.
Dos tipos:
- **`clinica`**: datos de la ficha clínica — antecedentes, medicamentos, acudiente (si el paciente es menor de edad).
- **`libre`**: nota libre con título, cuerpo de texto enriquecido e imagen adjunta.
Campos: `id`, `domicilio_id`, `enfermera_id`, `tipo`, `antecedentes`, `medicamentos`, `acudiente_nombre`, `acudiente_documento`, `titulo`, `cuerpo`, `imagen_path`.
---
### Roles y permisos
| Rol | Acceso |
|---|---|
| **Admin** | Crear, editar y ver todos los domicilios. Asignar/reasignar enfermeros. Registrar pagos. Exportar Excel. Ver informe completo con notas. Ver agenda de cualquier enfermero usando `?eid=X`. |
| **Enfermero** | Solo ve su propia agenda (`enfermero_portal.php`). Avanza el estado de sus domicilios asignados. Agrega servicios extra. Registra notas clínicas y libres. Visualiza órdenes médicas adjuntas. |
> **Redirección automática**: si el usuario autenticado tiene rol `enfermero`, `lab_domicilios.php` lo redirige inmediatamente a `enfermero_portal.php`.
Los roles se definen en `admin_users.role` (ENUM `admin` / `enfermero`) y `admin_users.enfermera_id` (FK a `lab_enfermeras`).
---
### Flujo de estados
```
[programado]
│ El enfermero confirma que realizará la visita
[confirmado]
│ El enfermero sale hacia el domicilio
[en_camino]
│ El enfermero llega → hora_llegada se registra automáticamente
[en_domicilio]
│ El enfermero finaliza → hora_salida se registra automáticamente
[completado]
Desde cualquier estado:
→ [cancelado] (requiere motivo_cancelacion como campo obligatorio)
→ [reprogramado] (requiere fecha_reprogramada)
```
**Transiciones permitidas al enfermero** (validadas en `update_domicilio_enfermero.php`):
| Estado actual | Estados posibles |
|---|---|
| `programado` | `confirmado` |
| `confirmado` | `en_camino`, `cancelado` |
| `en_camino` | `en_domicilio`, `cancelado` |
| `en_domicilio` | `completado`, `cancelado` |
El **admin** puede cambiar a cualquier estado directamente, incluyendo cancelar desde cualquier punto.
---
### Funcionalidades
- **Filtros**: por fecha, estado, enfermero asignado. Botón "Hoy" para filtro rápido.
- **Resumen del día**: conteo de domicilios por estado en la parte superior.
- **Panel de detalle**: al hacer clic en un domicilio se abre el panel lateral con toda la información, notas clínicas, notas libres e informe imprimible.
- **Asignar / Reasignar enfermero**: modal con lista del personal disponible.
- **Sin asignar**: badge con el conteo de domicilios que aún no tienen enfermero.
- **Registrar pago**: modal para marcar el cobro con modalidad y monto.
- **Servicios extra**: el enfermero los agrega desde su portal durante la visita.
- **Notas del enfermero**: ficha clínica con antecedentes, medicamentos, acudiente (si menor) y notas libres con imagen adjunta.
- **Informe de domicilio**: vista imprimible del domicilio con datos del paciente, ficha clínica y notas del enfermero.
- **Exportar CSV (Excel)**: exporta todos los domicilios filtrados, incluyendo las columnas de notas del enfermero (antecedentes, medicamentos, acudiente, notas libres).
- **Formularios recibidos**: pestaña dentro de `lab_domicilios.php` que muestra formularios enviados con filtro por plantilla y estado.
- **Portal del enfermero**: tarjetas colapsables ordenadas por hora, separadas en "Activos" y "Finalizados". Permite avanzar estados, agregar notas y ver órdenes.
---
### API Endpoints
| Endpoint | Método | Descripción |
|---|---|---|
| `api/lab/get_domicilios.php` | GET | Lista con filtros. `?id=X` para uno solo con detalle completo. |
| `api/lab/save_domicilio.php` | POST | Crear, editar o cambiar estado. `?solo_estado=true` para solo cambiar estado. |
| `api/lab/update_domicilio_enfermero.php` | POST | El enfermero avanza el estado de su domicilio. |
| `api/lab/my_agenda.php` | GET | Agenda del enfermero autenticado con servicios extra. |
| `api/lab/save_asignacion.php` | POST | Asignar o reasignar enfermero a domicilio. |
| `api/lab/get_asignaciones.php` | GET | Asignaciones por fecha. |
| `api/lab/registrar_pago.php` | POST | Registrar pago con monto y modalidad. |
| `api/lab/save_servicio_extra.php` | POST | Agregar servicio realizado durante la visita. |
| `api/lab/get_notas_domicilio.php` | GET | Notas del domicilio (clínicas y libres). |
| `api/lab/upload_nota_imagen.php` | POST | Subir imagen adjunta a una nota libre. |
| `api/lab/crear_desde_whatsapp.php` | GET/POST | Crear paciente u orden desde una conversación de WhatsApp activa. |
---
## 2. Módulo de Formularios
### ¿Qué es?
El módulo de formularios permite crear **plantillas de documentos** (consentimientos, historias clínicas, autorizaciones, encuestas) mediante un builder visual, enviarlas a los pacientes por WhatsApp y recopilar sus respuestas con firma digital. El documento firmado genera un **sello de integridad SHA-256** que puede verificarse públicamente.
---
### Archivos involucrados
| Archivo | Descripción |
|---|---|
| `lab_formularios.php` | Vista principal — lista de plantillas y registro de envíos |
| `lab_formulario_builder.php` | Editor visual drag & drop (ventana separada, solo admin) |
| `form_cliente.php` | Página pública — el paciente llena y firma sin iniciar sesión |
| `ver_formulario_enviado.php` | Vista del documento firmado — acceso por ID (admin/enfermero) o token (cliente) |
| `verificar_formulario.php` | Verificación pública de autenticidad por hash SHA-256 |
| `classes/lab/Formulario.php` | Clase ORM — CRUD de plantillas, crear envíos, guardar respuestas, generar hash |
| `api/lab/get_formularios.php` | GET — lista plantillas o envíos |
| `api/lab/save_formulario.php` | POST — crear, editar y eliminar plantillas (solo admin) |
| `api/lab/send_formulario.php` | POST — crear instancia de envío, devolver URL pública y mensaje WhatsApp |
| `api/lab/submit_formulario.php` | GET/POST — cargar el formulario por token / guardar la respuesta del cliente |
| `api/lab/firmar_profesional.php` | POST — guardar firma del profesional (requiere sesión activa) |
---
### Base de datos
#### Tabla `lab_formularios`
Plantillas de documentos creadas desde el builder.
| Campo | Tipo | Descripción |
|---|---|---|
| `id` | INT PK | Identificador único |
| `nombre` | VARCHAR(150) | Nombre de la plantilla |
| `descripcion` | TEXT | Descripción visible al cliente |
| `categoria` | ENUM | `consentimiento` / `historia_clinica` / `autorizacion` / `encuesta` / `otro` |
| `esquema` | LONGTEXT | JSON con el array de campos del formulario |
| `permite_firma` | TINYINT(1) | El formulario tiene sección de firma global |
| `requiere_firma` | TINYINT(1) | La firma global es obligatoria |
| `firma_modos` | VARCHAR(50) | `canvas`, `foto` o `canvas,foto` (separados por coma) |
| `version` | SMALLINT | Se incrementa automáticamente al editar el esquema |
| `is_active` | TINYINT(1) | Soft-delete |
| `creado_por` | INT FK | Usuario que creó la plantilla |
| `doc_encabezado` | VARCHAR | Override del nombre de empresa en el documento |
| `doc_subtitulo` | VARCHAR | Override del subtítulo en el documento |
| `doc_logo_base64` | LONGTEXT | Override del logo en el documento |
| `doc_color` | VARCHAR(20) | Override del color del encabezado del documento |
| `doc_pie_pagina` | TEXT | Override del pie de página |
#### Tabla `lab_form_envios`
Cada fila es una instancia enviada a un paciente.
| Campo | Tipo | Descripción |
|---|---|---|
| `id` | INT PK | Identificador único |
| `formulario_id` | INT FK | Plantilla enviada |
| `paciente_id` | INT FK NULL | Paciente asociado |
| `domicilio_id` | INT FK NULL | Domicilio asociado (opcional) |
| `token` | CHAR(64) UNIQUE | Token público de 64 caracteres hex (acceso sin sesión) |
| `datos_prefilled` | LONGTEXT | JSON con datos pre-llenados al enviar (incluye `__paciente.*`) |
| `datos_cliente` | LONGTEXT | JSON con las respuestas completadas por el cliente |
| `firma_svg` | LONGTEXT | Firma del paciente (PNG base64 — canvas o foto) |
| `ip_cliente` | VARCHAR(45) | IP del cliente al enviar el formulario |
| `user_agent` | VARCHAR(512) | Navegador del cliente |
| `estado` | ENUM | `pendiente` / `completado` / `firmado` / `expirado` |
| `enviado_por` | INT FK | Usuario que generó el enlace |
| `enviado_via` | ENUM | `whatsapp` / `email` / `link` |
| `expira_en` | DATETIME NULL | Siempre `NULL` — el enlace no expira |
| `completado_en` | DATETIME | Fecha y hora en que el cliente completó el formulario |
| `hash_verificacion` | CHAR(64) | Sello de integridad SHA-256 del documento |
---
### Roles y permisos
| Rol | Acceso |
|---|---|
| **Admin** | Crear, editar y eliminar plantillas desde el builder. Enviar formularios a cualquier paciente. Ver todos los envíos. Ver y descargar el PDF de cualquier formulario. |
| **Enfermero** | Enviar formularios existentes a sus pacientes. Ver solo sus propios envíos (`enviado_por = su user_id`). No puede crear ni editar plantillas. Puede firmar como profesional en los formularios que él mismo envió. |
| **Cliente (público)** | Accede a `form_cliente.php?t=TOKEN` sin ninguna autenticación. Llena y firma el formulario. Puede volver al mismo enlace en cualquier momento para ver el documento firmado y descargarlo como PDF. |
---
### Flujo completo
```
1. ADMIN crea la plantilla
├─ Abre lab_formulario_builder.php (se abre en ventana nueva)
├─ Arrastra campos al canvas y los configura
├─ Configura el diseño del documento (logo, color, encabezado, pie de página)
└─ Guarda → POST api/lab/save_formulario.php → lab_formularios
2. ADMIN o ENFERMERO envía el formulario
├─ lab_formularios.php → botón "Enviar" → modal
├─ Busca y selecciona el paciente
├─ Previsualiza los campos que llegarán pre-llenados
├─ Selecciona el canal: WhatsApp o "solo link"
└─ POST api/lab/send_formulario.php
├─ Genera token de 64 hex chars: bin2hex(random_bytes(32))
├─ Crea fila en lab_form_envios (estado=pendiente, expira_en=NULL)
└─ Devuelve URL pública y mensaje preformateado para WhatsApp
3. CLIENTE recibe el enlace (por WhatsApp u otro medio)
├─ Abre form_cliente.php?t=TOKEN
├─ GET api/lab/submit_formulario.php?t=TOKEN → carga datos del formulario
└─ Si ya fue firmado antes → muestra pantalla de solo lectura con link al PDF
4. CLIENTE llena el formulario
├─ Campos "linked" llegan pre-llenados con datos del paciente (readonly si tienen valor)
├─ Campos vacíos linked son editables para que el cliente los complete
├─ Campos firma_profesional muestran aviso "uso exclusivo del profesional"
└─ Dibuja su firma (canvas) o adjunta una foto de firma
5. CLIENTE envía
├─ POST api/lab/submit_formulario.php
├─ Se genera hash SHA-256 (contenido + firma + ID + token + timestamp)
├─ Estado → "firmado" (si hay firma) o "completado" (sin firma)
└─ Pantalla de éxito con hash visible y botón para descargar el PDF
6. PROFESIONAL firma (si el formulario lo requiere)
├─ Admin/Enfermero abre ver_formulario_enviado.php?id=X con sesión activa
├─ Aparece canvas de firma en la posición del campo firma_profesional
├─ Dibuja su firma y hace clic en "Guardar firma"
└─ POST api/lab/firmar_profesional.php → guarda campo_id_svg en datos_cliente
7. ADMIN/ENFERMERO revisa el resultado
├─ lab_formularios.php → pestaña "Envíos" → icono "Ver respuesta"
└─ ver_formulario_enviado.php?id=X → documento HTML imprimible
8. VERIFICACIÓN pública de integridad
└─ verificar_formulario.php?h=HASH_SHA256
├─ Busca en lab_form_envios.hash_verificacion
└─ Muestra: nombre del formulario, paciente, fecha, estado y si el sello es válido
```
---
### Tipos de campos del Builder
#### Campos de entrada
| Tipo | Descripción |
|---|---|
| `texto` | Campo de texto corto de una sola línea |
| `textarea` | Área de texto largo (varias líneas) |
| `numero` | Campo numérico |
| `fecha` | Selector de fecha |
| `hora` | Selector de hora |
| `select` | Lista desplegable con opciones configurables |
| `radio` | Selección única con opciones configurables |
| `checkbox` | Selección múltiple con opciones configurables |
| `lista_marcable` | Lista de ítems numerados con checkboxes |
#### Campos de firma
| Tipo | Descripción |
|---|---|
| `firma` | Firma del **paciente** — visible y editable en `form_cliente.php` |
| `firma_profesional` | Firma del **profesional** — bloqueada para el cliente; solo editable desde el panel admin/enfermero |
#### Campos de contenido
| Tipo | Descripción |
|---|---|
| `separador` | Separador visual o título de sección |
| `parrafo` | Bloque de texto estático (pre-formatado o flujo libre) |
| `parrafo_inline` | Párrafo con marcadores `{nombre_completo}`, `{telefono}`, etc. que se convierten en espacios editables si el valor está vacío |
#### Campos vinculados al paciente (`tipo: linked`)
Se auto-rellenan con los datos del paciente al momento de enviar. Si el valor existe → campo de solo lectura. Si está vacío → el cliente puede completarlo.
| `linked_key` | Dato que extrae |
|---|---|
| `nombre_completo` | Nombre completo del paciente |
| `numero_documento` | Número de documento |
| `tipo_documento` | Tipo de documento |
| `fecha_nacimiento` | Fecha de nacimiento |
| `telefono` | Teléfono |
| `email` | Correo electrónico |
| `eps` | EPS o aseguradora |
| `direccion` | Dirección |
---
### Firma digital
**Modos disponibles** (configurados en la plantilla mediante `firma_modos`):
| Modo | Funcionamiento |
|---|---|
| `canvas` | El cliente dibuja su firma con el dedo o el mouse. Se captura con `canvas.toDataURL('image/png')`. |
| `foto` | El cliente sube una imagen desde su cámara o galería (`<input accept="image/*" capture="environment">`). Se convierte a base64 con `FileReader`. |
Ambos modos pueden estar activos simultáneamente en la misma plantilla.
**Firma global vs. firma por campo:**
- Si el esquema **no incluye** campos tipo `firma`, se muestra una sección de firma global al pie del formulario.
- Si el esquema **incluye** campos `firma`, cada uno tiene su propio widget canvas independiente en la posición configurada dentro del formulario.
---
### Enlace público y vigencia
- **URL pública**: `form_cliente.php?t=TOKEN`
- **TOKEN**: 64 caracteres hexadecimales generados con `bin2hex(random_bytes(32))`.
- **Sin sesión**: el cliente no necesita crear cuenta ni iniciar sesión.
- **Sin vencimiento**: la columna `expira_en` existe en la tabla pero siempre es `NULL`. El enlace es permanente.
- **Bloqueo por estado**: si el formulario ya fue completado o firmado, el enlace muestra la pantalla de solo lectura. No permite modificar la respuesta.
- **Idempotencia**: si el cliente reintenta enviar (por error de red, por ejemplo), el sistema devuelve éxito con los datos ya guardados en lugar de crear un duplicado.
---
### PDF y visualización del documento
No se usa ninguna librería de generación de PDF en el backend. El documento es la página `ver_formulario_enviado.php` con estilos `@media print`. El usuario puede imprimirla o guardarla como PDF directamente desde el navegador.
**Contenido del documento impreso:**
- Encabezado con logo, nombre, subtítulo, datos de contacto y color corporativo
- Datos del paciente (nombre, documento, fecha de nacimiento, teléfono, EPS)
- Respuestas del formulario campo por campo, en el orden del esquema
- Imagen de la firma del paciente
- Firma del profesional (si fue completada)
- Sello SHA-256 con link para verificar autenticidad
- Pie de página con fecha de generación e ID del documento
**Formas de acceder al documento:**
| URL | Quién puede acceder |
|---|---|
| `ver_formulario_enviado.php?id=X` | Admin (cualquier formulario) o Enfermero (solo los que él envió). Requiere sesión. |
| `ver_formulario_enviado.php?t=TOKEN` | Cliente u cualquier persona con el enlace. Sin sesión. Solo si el estado es `firmado` o `completado`. |
---
### Sello de integridad SHA-256
Al guardar la respuesta del cliente, el sistema genera un hash SHA-256 que vincula de forma única el contenido del formulario con la firma y el momento en que se completó.
**Construcción del hash** (en `Formulario::guardarRespuesta()`):
```
SHA-256 de:
JSON de los datos del cliente
+ firma SVG/PNG del paciente
+ ID interno del envío
+ token del enlace
+ timestamp del momento de registro
```
**¿Para qué sirve?** Cualquier persona con el hash puede ir a `verificar_formulario.php?h=HASH` para confirmar que:
- El documento existe en la base de datos.
- El nombre del formulario y del paciente.
- La fecha en que fue completado.
- El estado actual (firmado / completado).
Si el documento fue alterado, el hash no coincidirá y la verificación fallará.
---
### Firma del profesional
Algunos formularios requieren que un profesional de salud también firme el documento, además del paciente.
**Flujo:**
1. Al diseñar la plantilla en el builder se agrega un campo `tipo: firma_profesional` en la posición deseada.
2. Cuando el cliente llena el formulario en `form_cliente.php`, ese campo muestra solo un aviso: *"Uso exclusivo del profesional de salud"*. El cliente no puede interactuar con él.
3. Una vez que el cliente ha completado y enviado el formulario, el admin o enfermero abre `ver_formulario_enviado.php?id=X` con sesión activa y verá el canvas de firma en esa posición.
4. El profesional dibuja su firma y hace clic en "Guardar firma".
5. La firma se guarda mediante POST a `api/lab/firmar_profesional.php`.
**Validaciones en el servidor:**
- Requiere sesión activa (`isUserLoggedIn()`).
- Si el usuario es enfermero, solo puede firmar en formularios que él mismo envió.
- Valida que el `campo_id` corresponde a un campo `tipo: firma_profesional` en el esquema del formulario.
- Valida que la imagen enviada sea un data URI de imagen válido.
> Si el formulario se accede via `?t=TOKEN` (cliente público), el canvas **no aparece**. En su lugar se muestra un aviso *"Pendiente de firma del profesional"* (solo visible en pantalla, no en el PDF impreso).
---
### API Endpoints
| Endpoint | Método | Autenticación | Descripción |
|---|---|---|---|
| `api/lab/get_formularios.php` | GET | Sesión | Lista plantillas. `?id=X` para una sola. `?envios=1` para lista de envíos. |
| `api/lab/save_formulario.php` | POST | Admin | Crear, editar o eliminar una plantilla. |
| `api/lab/send_formulario.php` | POST | Admin / Enfermero | Crear instancia de envío. Devuelve URL pública y mensaje para WhatsApp. |
| `api/lab/submit_formulario.php` | GET | Público | Cargar el formulario por token (sin sesión). |
| `api/lab/submit_formulario.php` | POST | Público | Guardar la respuesta y firma del cliente. |
| `api/lab/firmar_profesional.php` | POST | Sesión | Guardar la firma del profesional en un campo `firma_profesional`. |
---
*Documentación generada para uso interno del equipo.*
+5 -125
View File
@@ -1,129 +1,9 @@
# Módulo Administrativo — Laboratorio Clínico # Obsoleto
Módulo add-on para el sistema de chatbot WhatsApp que permite gestionar órdenes médicas recibidas como imágenes, domicilios, enfermeras y pacientes. Este documento quedó desactualizado y se conserva solo por historial de git.
--- La documentación vigente está **dentro del sistema**, en el módulo Soporte:
## Instalación /erp.php?m=soporte&v=documentacion
### 1. Ejecutar migraciones de base de datos Ver `README_DOCS.md` para saber cómo se organiza y cómo agregar páginas.
```bash
php migrations/20260302_lab_run_migrations.php
```
Crea 7 tablas nuevas sin modificar las existentes:
- `lab_pacientes`
- `lab_enfermeras`
- `lab_ordenes_medicas`
- `lab_domicilios`
- `lab_asignaciones`
- `lab_autorizaciones`
- `lab_actividad_admin`
Para deshacer:
```bash
php migrations/20260302_lab_run_migrations.php --rollback
```
### 2. Verificar instalación
Accede desde el navegador (con sesión admin activa):
```
https://tu-servidor/lab_status.php
```
O desde CLI:
```bash
php lab_status.php
```
---
## Archivos del módulo
### Vistas PHP
| Archivo | Descripción |
|---|---|
| `lab_dashboard.php` | Panel principal con estadísticas en tiempo real |
| `lab_ordenes.php` | Gestión de órdenes médicas (estados, imágenes, historial) |
| `lab_pacientes.php` | CRUD de pacientes, vinculación con usuarios WhatsApp |
| `lab_domicilios.php` | Agenda de domicilios y asignación de enfermeras |
| `lab_enfermeras.php` | CRUD de enfermeras y visualización de agenda diaria |
| `lab_reportes.php` | Trazabilidad, log de actividad, exportación CSV |
| `lab_status.php` | Verificador de estado del módulo |
### Clases (models)
Ubicadas en `classes/lab/`:
- `ActividadAdmin.php` — Base de trazabilidad
- `Paciente.php` — Modelo de pacientes
- `Enfermera.php` — Modelo de enfermeras
- `OrdenMedica.php` — Modelo de órdenes médicas con flujo de estados
- `Domicilio.php` — Modelo de domicilios con flujo de estados
- `Asignacion.php` — Modelo de asignaciones enfermera ↔ domicilio
### API REST
Ubicados en `api/lab/`:
| Endpoint | Método | Descripción |
|---|---|---|
| `get_pacientes.php` | GET | Lista paginada de pacientes |
| `save_paciente.php` | POST | Crear/actualizar paciente |
| `get_ordenes.php` | GET | Lista/detalle de órdenes |
| `save_orden.php` | POST | Crear/actualizar orden |
| `autorizar_orden.php` | POST | Cambiar estado de una orden |
| `get_domicilios.php` | GET | Lista/detalle de domicilios |
| `save_domicilio.php` | POST | Crear/actualizar domicilio |
| `get_enfermeras.php` | GET | Lista de enfermeras + agenda |
| `save_enfermera.php` | POST | Crear/actualizar enfermera |
| `get_asignaciones.php` | GET | Asignaciones por fecha |
| `save_asignacion.php` | POST | Asignar/liberar/completar enfermera |
| `get_actividad.php` | GET | Log de actividad con filtros |
| `get_stats.php` | GET | Estadísticas para dashboard |
| `crear_desde_whatsapp.php` | GET/POST | Crear orden desde conversación activa |
---
## Flujos de estado
### Órdenes médicas
```
pendiente → en_revision → autorizada → en_domicilio → completada
↘ rechazada
```
### Domicilios
```
programado → confirmado → en_camino → en_domicilio → completado
↘ cancelado
↘ reprogramado
```
---
## Integración con el chatbot
En `conversations.php`, los mensajes de imagen entrantes tienen un botón **<i class="fas fa-flask"></i>** (verde) en las acciones del mensaje. Al hacer click:
1. Se abre un modal con la imagen adjunta
2. El operador busca o selecciona un paciente (o usa el contacto de la conversación)
3. Completa datos opcionales (médico, exámenes, ayuno)
4. Se crea la orden en estado `pendiente`
---
## Exportaciones CSV
Disponibles desde `lab_reportes.php`:
- **Órdenes médicas** del período — incluye estado, médico, exámenes
- **Domicilios** del período — incluye enfermera asignada, dirección, estado
- **Pacientes** — catálogo completo con total de órdenes
---
## Requisitos
- PHP 8.2+
- MariaDB 10.11+ (o MySQL 8+)
- Bootstrap 5.3 (ya incluido en el sistema)
- Font Awesome 6.4 (ya incluido en el sistema)
- `uploads/media/` con permisos de escritura (755/775)
-163
View File
@@ -1,163 +0,0 @@
# Webhook WhatsApp — Endpoints y Características
## Endpoint principal
```
URL: /api/webhook.php
```
---
## GET — Verificación de webhook
```
GET /api/webhook.php?hub.mode=subscribe&hub.verify_token=TOKEN&hub.challenge=CHALLENGE
```
### Parámetros que envía Meta
| Parámetro | Valor esperado |
|---|---|
| `hub.mode` | `subscribe` |
| `hub.verify_token` | El token configurado en `system_config.webhook_verify_token` |
| `hub.challenge` | Número aleatorio que debe devolverse tal cual |
### ⚠️ Bug conocido
El código lee `$_GET['hub_verify_token']` (con guión bajo), pero PHP convierte los puntos a guiones bajos automáticamente al parsear `$_GET`, por lo que **funciona correctamente**.
### Respuesta exitosa
```
HTTP 200
Body: {challenge}
```
### Respuesta fallida
```
HTTP 403
Body: {"error":"Token de verificación inválido"}
```
---
## POST — Recepción de eventos
```
POST /api/webhook.php
Content-Type: application/json
```
### Estructura del payload esperado (Meta Cloud API)
```json
{
"object": "whatsapp_business_account",
"entry": [{
"id": "WABA_ID",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"phone_number_id": "PHONE_NUMBER_ID"
},
"contacts": [{
"wa_id": "573001234567",
"profile": { "name": "Nombre Contacto" }
}],
"messages": [{
"from": "573001234567",
"id": "wamid.XXX",
"timestamp": "1234567890",
"type": "text",
"text": { "body": "Hola" }
}]
}
}]
}]
}
```
### Tipos de mensaje soportados
| `type` | Descripción |
|---|---|
| `text` | Texto plano |
| `image` | Imagen (con caption opcional) |
| `audio` | Audio / nota de voz |
| `video` | Video |
| `document` | Documento / PDF |
| `sticker` | Sticker |
| `reaction` | Reacción emoji a otro mensaje |
| `interactive` | Respuesta de lista o botón |
### El campo `field` del change puede ser
- `messages` → mensajes entrantes y estados
- `conversations` → alias aceptado también
### Eventos de estado (statuses)
```json
"statuses": [{
"id": "wamid.XXX",
"status": "sent|delivered|read|failed",
"recipient_id": "573001234567"
}]
```
### Respuesta exitosa
```
HTTP 200
Body: {"status":"success"}
```
---
## Configuración necesaria en `system_config` (BD)
| config_key | Descripción |
|---|---|
| `whatsapp_token` | Access Token de Meta |
| `whatsapp_phone_number_id` | Phone Number ID de la línea |
| `webhook_verify_token` | Token de verificación del webhook |
| `whatsapp_api_url` | `https://graph.facebook.com/v22.0/` |
---
## Variables de entorno equivalentes (`.env`)
```env
WHATSAPP_TOKEN=
WHATSAPP_PHONE_NUMBER_ID=
WEBHOOK_VERIFY_TOKEN=
WHATSAPP_API_URL=https://graph.facebook.com/v22.0/
DB_HOST=
DB_PORT=3306
DB_NAME=
DB_USER=
DB_PASS=
```
---
## Tablas BD que usa el webhook
| Tabla | Uso |
|---|---|
| `users` | Crea o busca usuario por `phone_number` |
| `conversations` | Guarda cada mensaje (deduplicado por `message_id`) |
| `webhook_logs` | Registra el payload crudo de cada POST |
| `notifications` | Crea aviso de nuevo mensaje entrante |
| `media_queue` | Encola media que no pudo descargarse en el momento |
| `system_config` | Lee tokens y configuración |
---
## Seguridad — pendiente de implementar
- No valida la firma `X-Hub-Signature-256` en el POST.
- Se recomienda agregar antes de procesar:
```php
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $input, APP_SECRET);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
```
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* GET ?id=N — Detalle de un examen del catálogo con sus ítems.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireLogin(); requireLogin();
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* GET ?exam_id=N — Tarifas de un examen por empresa o convenio.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireLogin(); requireLogin();
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* GET ?q=&categoria=&page=&limit= — Catálogo de exámenes, paginado y filtrable.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireLogin(); requireLogin();
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* POST — Crea o actualiza un examen del catálogo. Requiere administrador.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireAdmin(); requireAdmin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Método no permitido', 405); if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Método no permitido', 405);
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* POST — Crea o actualiza un ítem (analito) de un examen. Requiere administrador.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireAdmin(); requireAdmin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Método no permitido', 405); if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Método no permitido', 405);
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* POST — Crea o actualiza la tarifa de un examen. Requiere administrador.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireAdmin(); requireAdmin();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Método no permitido', 405); if ($_SERVER['REQUEST_METHOD'] !== 'POST') jsonError('Método no permitido', 405);
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* POST { id } — Elimina un médico del catálogo.
*/
require_once __DIR__ . '/../../../config/config.php'; require_once __DIR__ . '/../../../config/config.php';
if (!isUserLoggedIn()) { http_response_code(401); echo json_encode(['ok'=>false,'error'=>'No autorizado']); exit; } if (!isUserLoggedIn()) { http_response_code(401); echo json_encode(['ok'=>false,'error'=>'No autorizado']); exit; }
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* GET — Lista de médicos solicitantes.
*/
require_once __DIR__ . '/../../../config/config.php'; require_once __DIR__ . '/../../../config/config.php';
if (!isUserLoggedIn()) { http_response_code(401); echo json_encode(['ok'=>false,'error'=>'No autorizado']); exit; } if (!isUserLoggedIn()) { http_response_code(401); echo json_encode(['ok'=>false,'error'=>'No autorizado']); exit; }
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* POST — Crea o actualiza un médico del catálogo.
*/
require_once __DIR__ . '/../../../config/config.php'; require_once __DIR__ . '/../../../config/config.php';
if (!isUserLoggedIn()) { http_response_code(401); echo json_encode(['ok'=>false,'error'=>'No autorizado']); exit; } if (!isUserLoggedIn()) { http_response_code(401); echo json_encode(['ok'=>false,'error'=>'No autorizado']); exit; }
@@ -34,6 +34,27 @@ Casi siempre es una de estas dos:
| **Formularios** | Consentimientos y fichas; diseño y envíos | | **Formularios** | Consentimientos y fichas; diseño y envíos |
| **Soporte** | Esta documentación | | **Soporte** | Esta documentación |
## El recorrido de un paciente
Desde que saca su turno hasta que se va:
```
KIOSKO RECEPCIÓN TOMA DE MUESTRAS
│ │ │
▼ ▼ ▼
┌──────┐ llama ┌──────┐ deriva ┌──────────────┐ ┌────────────┐
│espera├──────────►│recep.├───────────►│ espera lugar ├──►│ en servicio│
└──┬───┘ └──┬───┘ └──────────────┘ └──────┬─────┘
│ │ │
│ no responde │ ▼
└──────────────────┴──────────► ausente ┌────────────────┐
│ finalizado │
cancelado └────────────────┘
cuenta para facturar
```
Un turno **nunca se borra**. Si el paciente no aparece se marca *ausente*; si no se hace, *cancelado*. Los dos quedan registrados, y ninguno cuenta como facturación.
## Cosas que conviene saber desde el principio ## Cosas que conviene saber desde el principio
**Los turnos no se borran.** Se cancelan o se marcan como ausente, pero quedan registrados. Es a propósito: el historial tiene valor clínico y administrativo. **Los turnos no se borran.** Se cancelan o se marcan como ausente, pero quedan registrados. Es a propósito: el historial tiene valor clínico y administrativo.
@@ -0,0 +1,70 @@
# Kiosko y pantallas de TV
Las dos pantallas que funcionan solas, sin nadie operándolas. Conviene entenderlas porque cuando fallan, el que se entera primero es quien está en recepción.
## El kiosko
El tótem de la entrada, donde el paciente saca su turno sin ayuda.
```
PACIENTE LLEGA
┌──────────────┐
│ Elige tipo │ general, preferencial, embarazada,
│ de turno │ solo entrega de muestras…
└──────┬───────┘
┌──────────────┐
│Datos básicos │ nombre y celular (opcional)
└──────┬───────┘
┌──────────────┐
│ Imprime │──► el turno aparece en la pantalla de TV
│ su turno │ y en la cola de recepción
└──────────────┘
```
Es una de las **dos únicas pantallas públicas** del sistema: no pide usuario ni contraseña, porque nadie va a iniciar sesión en el tótem de la entrada.
### Prioridades
El tipo de turno que elige el paciente define su lugar en la cola. Las prioridades las configura un administrador; quien atiende no necesita hacer nada: **Llamar siguiente** ya respeta el orden.
### Si el paciente deja su celular
Puede recibir por WhatsApp el aviso de su turno y los consentimientos para firmar desde el teléfono mientras espera. Vale la pena insistirle en que lo deje.
### Cuando el kiosko falla
Un turno siempre se puede crear a mano desde recepción. El kiosko es una comodidad, no un requisito — si está caído, la atención sigue.
## Pantalla de TV
La de la sala de espera. Muestra el turno que se está llamando y la cola, y reproduce contenido del laboratorio de fondo.
También es pública: se abre en el navegador del televisor y se deja andando.
### El contenido de fondo
Es una lista de videos e imágenes que se reproducen en bucle, uno detrás de otro:
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ video 1 │──►│ imagen │──►│ video 2 │──►│ imagen │──┐
│ hasta │ │ 8 seg │ │ hasta │ │ 10 seg │ │
│ el final│ │ │ │ el final│ │ │ │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ │
▲ │
└──────────────────── vuelve a empezar ────────────┘
```
Los videos van completos; a las imágenes se les fija cuántos segundos duran. Se administra desde **Configuración del turnero → Pantalla TV**: se suben, se reordenan arrastrando y se eliminan de a uno.
### Si la pantalla se queda pegada
Recargá la página en el televisor. Si el contenido no cambió, verificá que la lista tenga elementos activos en la configuración.
## Verificar paciente
Una consulta rápida por cédula para ver la ficha y el historial de alguien, sin abrir su turno. Útil cuando el paciente pregunta algo en el mostrador y no querés perder lo que estás haciendo.
@@ -45,6 +45,22 @@ Se admite pago combinado — efectivo más tarjeta, por ejemplo.
### 5. Consentimientos ### 5. Consentimientos
Quién firma qué, y dónde:
```
RECEPCIÓN TOMA DE MUESTRAS
┌─────────────────────┐ ┌──────────────────────┐
│ Consentimiento │ │ Datos Toma de │
│ pruebas de lab │ │ Muestras (F-LAB-08) │
│ │ │ │
│ firma: EL PACIENTE │ │ firma: EL PERSONAL │
│ vía WhatsApp │ │ en la estación │
└─────────────────────┘ └──────────────────────┘
obligatorio obligatorio SIEMPRE
salvo "solo entrega" (incluso solo entrega)
```
Los que hagan falta aparecen listados con su estado. Se envían al WhatsApp del paciente, que los firma desde el celular. Los que hagan falta aparecen listados con su estado. Se envían al WhatsApp del paciente, que los firma desde el celular.
**No podés guardar la solicitud si quedan consentimientos sin firmar**, salvo que sea una visita de *solo entrega de muestras*. **No podés guardar la solicitud si quedan consentimientos sin firmar**, salvo que sea una visita de *solo entrega de muestras*.
@@ -0,0 +1,69 @@
---
roles: recepcionista, lab_recepcion, operador_bot, supervisor
---
# Chat de WhatsApp
La línea de WhatsApp del laboratorio la atiende un bot, pero cuando hace falta una persona, la conversación pasa a un operador. Esta es esa pantalla.
## Cómo se reparte el trabajo
```
MENSAJE DEL PACIENTE
┌─────────────┐ no ┌──────────────────────┐
│ ¿Aceptó los ├────────►│ Le pide aceptar y │
│ términos? │ │ no avanza hasta que │
└──────┬──────┘ │ responda ACEPTO │
│ sí └──────────────────────┘
┌─────────────┐ sí ┌──────────────────────┐
│ ¿La tomó un ├────────►│ El bot NO interviene │
│ operador? │ │ Respondés vos │
└──────┬──────┘ └──────────────────────┘
│ no
┌─────────────┐ fuera ┌──────────────────────┐
│ ¿Está en ├────────►│ Responde con el │
│ horario? │ │ mensaje de fuera de │
└──────┬──────┘ │ horario │
│ dentro └──────────────────────┘
El bot responde
```
Lo importante: **cuando tomás una conversación, el bot deja de responder ahí**. No hay riesgo de que le conteste encima al paciente mientras vos estás escribiendo.
## Atender una conversación
La lista muestra los hilos con mensajes recientes. Al abrir uno ves el historial completo y podés responder.
Si el paciente ya está registrado, se ve su ficha; si no, se puede crear desde ahí mismo.
## La ventana de 24 horas
Es una regla de WhatsApp, no del sistema:
> Podés escribir libremente durante **24 horas** desde el último mensaje del paciente. Pasado ese plazo, solo se le puede escribir con una **plantilla aprobada**.
Por eso a veces el sistema no te deja mandar un texto libre y ofrece plantillas. No es una falla: es la restricción de WhatsApp.
Las plantillas las crea y aprueba Meta. Si necesitás una nueva para un caso que se repite, pedila a un administrador — el trámite lleva días.
## Términos y condiciones
Todo contacto nuevo debe aceptar los términos antes de que el bot converse. Responde **ACEPTO** o **NO ACEPTO**.
Se le vuelven a pedir cuando pasan 6 meses o cuando se publica una versión nueva. Si un paciente dice que no puede abrir el documento de términos, avisá: puede ser que el enlace esté caído, y eso lo resuelve un administrador.
## Problemas frecuentes
**El paciente dice que escribió y nadie le respondió.**
Revisá si la conversación quedó tomada por un operador que no siguió. En ese estado el bot no responde y queda esperando a una persona.
**No me deja enviar un mensaje.**
Pasaron más de 24 horas desde el último mensaje del paciente. Usá una plantilla.
**El paciente no recibe los consentimientos.**
Verificá el número en su ficha. Si está bien y aun así no llegan, es problema de plantilla — avisá a un administrador.
@@ -28,6 +28,19 @@ Al rechazar hay que indicar el motivo. Ese motivo queda registrado y se ve despu
### Muestras de visitas anteriores ### Muestras de visitas anteriores
```
VISITA 1 · lunes VISITA 2 · jueves
┌────────────────────┐ ┌────────────────────┐
│ Turno A-042 │ │ Turno B-017 │
│ │ │ │
│ Sangre recibida │ │ Orina recibida │
│ Orina PENDIENTE ├───────────────►│ └ visita anterior│
└────────────────────┘ reaparece └────────────────────┘
finalizado sola │
NO se modifica ◄─────────────────────────────
quedan enlazados
```
Si el paciente quedó debiendo una muestra otro día, te aparece con una etiqueta ámbar **visita anterior**, e incluye los exámenes de aquella orden para que sepas de qué se trataba. Si el paciente quedó debiendo una muestra otro día, te aparece con una etiqueta ámbar **visita anterior**, e incluye los exámenes de aquella orden para que sepas de qué se trataba.
Se reciben con un clic, igual que cualquier otra. Al hacerlo, los dos turnos quedan enlazados: desde el historial podés saltar de uno al otro. Se reciben con un clic, igual que cualquier otra. Al hacerlo, los dos turnos quedan enlazados: desde el historial podés saltar de uno al otro.
@@ -48,6 +61,22 @@ Lo firmás vos, no el paciente.
Para exámenes que requieren varias tomas en el tiempo: curvas de glicemia, prolactina, cortisol, test de Sullivan. Para exámenes que requieren varias tomas en el tiempo: curvas de glicemia, prolactina, cortisol, test de Sullivan.
```
Glicemia pre y post carga
min 0 min 30 min 60 min 120
│ │ │ │
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ ✔ │ ────► │ ✔ │ ────► │ │ ... │ │
└─────┘ 30min └─────┘ 30min └─────┘ └─────┘
07:28 08:06 pendiente bloqueada
M. Monterrosa Y. Parada ▲
└ cuenta regresiva
Cada toma guarda SU hora y QUIÉN la firmó. Si cambia
el turno del personal, cada firma conserva su nombre.
```
Cómo funciona: Cómo funciona:
1. **Marcá el examen.** El formulario muestra solo las tomas de ese examen; si el paciente tiene dos exámenes seriados, muestra las de ambos. 1. **Marcá el examen.** El formulario muestra solo las tomas de ese examen; si el paciente tiene dos exámenes seriados, muestra las de ambos.
@@ -0,0 +1,88 @@
# Respaldos y recuperación
Qué hay que respaldar, y qué se pierde si no está.
## Las tres cosas a respaldar
```
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ BASE DE DATOS │ │ ARCHIVOS SUBIDOS │ │ CÓDIGO │
│ │ │ │ │ │
│ 91 tablas │ │ uploads/terms/ │ │ repositorio git │
│ pacientes, turnos, │ │ uploads/turnero/ │ │ │
│ formularios, │ │ │ │ ya respaldado por │
│ firmas │ │ NO están en git │ │ estar en el remoto │
└────────────────────┘ └────────────────────┘ └────────────────────┘
crítico crítico cubierto
```
El código está a salvo por estar versionado. Los otros dos **no tienen respaldo automático por el solo hecho de existir**.
## Base de datos
Es lo único irreemplazable. Contiene historia clínica, consentimientos firmados y facturación — información con valor legal y sin forma de reconstruirse.
```bash
mysqldump -h <host> -u <usuario> -p <base> \
--single-transaction --routines --triggers \
| gzip > respaldo_$(date +%F).sql.gz
```
`--single-transaction` evita bloquear las tablas mientras corre, así se puede hacer con el sistema en uso.
### Qué contiene lo crítico
| Tabla | Por qué importa |
|---|---|
| `lab_pacientes` | Fichas clínicas |
| `turnero_consentimientos`, `lab_form_envios` | **Formularios firmados** — valor legal |
| `turnero_turnos`, `turnero_solicitudes` | Historial de atención y facturación |
| `terms_acceptance` | Aceptación de términos, ~5.000 registros |
| `admin_users`, `roles`, `role_modules` | Acceso al sistema |
Las firmas se guardan como imagen **dentro** de las tablas, no como archivos sueltos. Un respaldo de la base las incluye.
## Archivos subidos
```
uploads/terms/ documentos de términos y condiciones
uploads/turnero/tv_media/ videos e imágenes de la pantalla de TV
```
**No están en el repositorio.** Al mover el sistema de servidor hay que copiarlos aparte, o los enlaces quedan apuntando a archivos que ya no existen.
Es exactamente lo que pasó con el documento de términos cuando cambió el dominio: la base seguía apuntando a una URL que ya no respondía.
## Antes de un cambio riesgoso
Si vas a tocar datos en producción, respaldá **solo lo que vas a tocar**:
```bash
mysqldump -h <host> -u <usuario> -p <base> role_modules admin_users \
> antes_del_cambio.sql
```
Es rápido y suele alcanzar. Un respaldo completo para cambiar una columna es desproporcionado; no tener ninguno es imprudente.
## Verificar que el respaldo sirve
Un respaldo que nunca se restauró no es un respaldo, es un archivo:
```bash
gunzip -t respaldo_2026-08-03.sql.gz # ¿está íntegro?
zcat respaldo_2026-08-03.sql.gz | head -40 # ¿tiene lo que esperás?
```
Lo ideal es restaurarlo de vez en cuando en una base de prueba y comprobar que el sistema arranca contra ella.
## Qué NO es recuperable
Aunque tengas respaldos, hay datos que nunca se guardaron y no hay de dónde sacarlos:
| Dato | Desde cuándo existe |
|---|---|
| Quién creó cada consentimiento del turnero | 3 de agosto de 2026 |
| Quién firmó cada toma de F-LAB-28 | 3 de agosto de 2026 |
| Cédula del personal no enfermero | 3 de agosto de 2026 |
Los registros anteriores tienen esos campos vacíos, y **no hay traza de auditoría** que permita reconstruirlos. Vale como advertencia: cuando se agrega una columna para registrar quién hizo algo, lo anterior se pierde.
@@ -0,0 +1,54 @@
# Registro de exámenes
Registro de exámenes realizados en sede: creación de órdenes, toma de muestras e ingreso de resultados. Es de los módulos nuevos — todo su código vive dentro de `modules/registro_exams/`.
## Vistas
| Vista | Qué hace |
|---|---|
| `lista` | Órdenes con filtros y estado |
| `nueva_orden` | Alta de una orden |
| `orden` | Detalle: ítems, estados e ingreso de resultados |
| `etiqueta` | Etiqueta imprimible para rotular la muestra |
## Endpoints
| Endpoint | Qué hace |
|---|---|
| `get_ordenes.php` | Lista con filtros |
| `get_orden.php` | Detalle de una orden |
| `save_orden.php` | Crea o actualiza |
| `save_resultado.php` | Guarda el resultado de un ítem |
| `cambiar_estado_item.php` | Avanza el estado de un ítem individual |
Usan `api/_helpers.php` del módulo, con las mismas convenciones que el turnero.
## Flujo
```
nueva_orden orden orden
│ │ │
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌─────────────┐
│ crear ├──────►│ muestra ├──────────►│ resultado │
│ orden │ │ tomada │ │ ingresado │
└─────────┘ └───────────┘ └─────────────┘
│ │
▼ ▼
etiqueta se rotula
imprimible la muestra
```
El estado se lleva **por ítem**, no por orden completa: una orden puede tener unos exámenes resueltos y otros pendientes.
## Relación con otros módulos
| Módulo | Vínculo |
|---|---|
| `lab_examenes` | De ahí sale el catálogo y los valores de referencia |
| `lab_pacientes` | El paciente de la orden |
| `medicos` | El médico que la solicitó |
## Al tocarlo
Los resultados de laboratorio son información clínica: un ítem mal guardado o un valor de referencia equivocado tienen consecuencias reales. Cualquier cambio en `save_resultado.php` merece probarse con una orden de prueba antes de subirlo.
@@ -0,0 +1,67 @@
# Catálogos del laboratorio
Los módulos que administran las tablas maestras. Se agrupan acá porque comparten la misma forma: una pantalla de listado con alta, edición y baja sobre una tabla.
## Los módulos
| Módulo | Tabla principal | Qué administra |
|---|---|---|
| `lab_examenes` | `exam_tipos` | Catálogo de exámenes, valores de referencia y tarifas |
| `medicos` | `medicos` | Médicos solicitantes |
| `lab_pacientes` | `lab_pacientes` | Fichas clínicas e historial |
| `lab_enfermeras` | `lab_enfermeras` | Personal clínico |
| `lab_eps` | `lab_eps` | EPS y aseguradoras |
| `lab_empresas` | `lab_empresas` | Empresas, convenios, subgrupos y tarifas |
| `lab_ciudades` | `lab_ciudades` | Ciudades de pacientes |
| `usuarios` | `admin_users`, `roles` | Usuarios y asignación de roles |
## Los que tienen endpoints propios
La mayoría son puentes al sistema anterior. Dos tienen código propio:
### `lab_examenes`
| Endpoint | Qué hace |
|---|---|
| `list.php` / `get.php` | Listado y detalle |
| `save.php` | Alta y edición del examen |
| `save_item.php` | Ítems que componen un examen |
| `get_tarifas.php` / `save_tarifa.php` | Tarifas por empresa o convenio |
Es el catálogo del que dependen el turnero y el registro de exámenes. Un examen mal configurado se propaga a todo lo demás: consentimientos que no se piden, tarifas que no se aplican.
**`exam_tipo_consentimientos`** vincula un examen con los formularios que obliga a firmar. Es una de las dos fuentes de consentimientos del turnero; la otra es la estación destino.
### `medicos`
`list.php`, `save.php`, `delete.php`. El médico se asocia a la solicitud del turnero y sale impreso en los documentos.
## Pacientes
`lab_pacientes` es el más consultado de todos: lo usan el turnero, domicilios, registro de exámenes y el bot.
Campos que otros módulos dan por sentados:
| Campo | Quién lo usa |
|---|---|
| `numero_documento` | Búsqueda en todas las pantallas |
| `telefono` | Envío de consentimientos y encuestas |
| `nombre_completo`, `tipo_documento` | Encabezado de todos los documentos |
| `fecha_nacimiento` | Cálculo de edad en formularios |
| `eps` | Facturación |
Un teléfono mal cargado se manifiesta lejos de donde se originó: como un consentimiento que nunca llegó.
## Usuarios
Ver [Roles y permisos](?m=soporte&v=documentacion&s=arquitectura&d=roles-y-permisos) para el detalle del control de acceso.
Lo esencial al crear o editar un usuario:
- **`role` y `role_id` deben cambiarse juntos.** La interfaz lee uno, los permisos salen del otro.
- **Cargá la cédula.** Es lo que aparece bajo la firma en los formularios.
- El usuario debe **volver a iniciar sesión** para que un cambio de permisos surta efecto.
## Al agregar un catálogo nuevo
Si es un ABM simple, seguí el patrón de `medicos`: un `views/index.php` y tres endpoints (`list`, `save`, `delete`). Registralo en `SYSTEM_MODULES` y concedelo a los roles que corresponda, o nadie lo verá.
+3
View File
@@ -1,4 +1,7 @@
<?php <?php
/**
* GET ?q= — Busca diagnósticos CIE-10 por código o descripción.
*/
require_once __DIR__ . '/_helpers.php'; require_once __DIR__ . '/_helpers.php';
requireMethod('GET'); requireMethod('GET');
requireTurnero(); requireTurnero();