diff --git a/README_DOCS.md b/README_DOCS.md new file mode 100644 index 0000000..4bf6d97 --- /dev/null +++ b/README_DOCS.md @@ -0,0 +1,45 @@ +# Documentación del proyecto + +La documentación vive **dentro del sistema**, en el módulo Soporte: + + /erp.php?m=soporte&v=documentacion + +Se escribe en Markdown, en `modules/soporte/docs/`, y se versiona con el código. + +| Sección | Carpeta | Quién la ve | +|---|---|---| +| Manual de usuario | `docs/manual/` | Cualquier usuario autenticado | +| Documentación técnica | `docs/tecnica/` | Administradores | +| Arquitectura | `docs/arquitectura/` | Administradores | +| Operación y soporte | `docs/operacion/` | Administradores | + +## Agregar o editar una página + +Creá un `.md` en la carpeta de la sección. El nombre lleva un prefijo numérico +que solo sirve para ordenar: + + modules/soporte/docs/tecnica/70-mi-tema.md + +El título sale del primer encabezado `#` del archivo. No hay que registrar nada +en ningún índice: se descubre solo. + +## Contenido que se genera solo + +Estos marcadores, en una línea propia, se reemplazan al cargar la página con +datos leídos del código y de la base: + +| Marcador | Qué inserta | +|----------|-------------| +| `{{modulos}}` | Módulos, con sus vistas y endpoints | +| `{{endpoints}}` | Todos los endpoints por módulo | +| `{{tablas}}` | Tablas de la base, agrupadas por prefijo | +| `{{roles}}` | Roles, usuarios activos y sus permisos | +| `{{servicios}}` | Clases de `core/`, `services/` y `classes/` | + +Así los inventarios no pueden quedar desactualizados. La descripción de cada +endpoint sale de su comentario de cabecera: si lo escribís bien, aparece bien. + +## Documentos anteriores + +`DOCUMENTACION_LAB.md` y `README_LAB.md` quedaron de una etapa previa y están +desactualizados. `WEBHOOK_ENDPOINTS.md` se migró a la sección técnica. diff --git a/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md b/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md new file mode 100644 index 0000000..cbc97ea --- /dev/null +++ b/modules/soporte/docs/arquitectura/60-decisiones-y-deuda.md @@ -0,0 +1,104 @@ +# Decisiones y deuda técnica + +Por qué el sistema es como es, y qué cosas conviene saber antes de tocarlo. + +## Decisiones tomadas a propósito + +### Migración gradual, sin corte + +Conviven el sistema original (archivos en la raíz) y el nuevo (módulos). No hubo una reescritura de golpe. + +**Por qué:** el laboratorio opera todos los días. Una reescritura completa implicaba congelar el desarrollo o mantener dos sistemas en paralelo. + +**Costo:** hay que saber en cuál de los dos está el código de cada pantalla. Los `lab_*` suelen estar en la raíz; el turnero está en el módulo. + +### Sin framework de frontend + +Las vistas son PHP que emiten HTML, con JavaScript embebido en la misma vista. + +**Por qué:** despliegue por `git pull`, sin build ni compilación. Un archivo se edita y ya está en producción. + +**Costo:** hay código repetido entre vistas, y las vistas grandes (el turnero) pasan de las 2.000 líneas. + +### Esquemas de formulario en JSON + +Los formularios se definen en JSON dentro de `lab_formularios.esquema`, no en tablas normalizadas. + +**Por qué:** las fichas clínicas cambian seguido y cada una tiene campos distintos. Normalizarlas obligaba a migrar el esquema con cada formulario nuevo. + +**Costo:** no se puede consultar por SQL «todos los pacientes con fiebre». Las respuestas viven dentro de un JSON. + +### La identidad de quien firma se resuelve en el servidor + +Nunca se acepta del navegador quién firmó algo. + +**Por qué:** es un dato con valor legal. Un cliente puede mentir; la sesión no. + +### El turno original nunca se modifica + +Cuando una muestra pendiente se completa en una visita posterior, el turno original **queda como estaba**. Solo se registra el vínculo. + +**Por qué:** el turno cerrado es un registro histórico. Alterarlo retroactivamente falsea los tiempos de atención y la facturación de aquel día. + +### Inventarios generados, no escritos + +Las tablas de módulos, endpoints, tablas y roles de esta documentación se leen del código y la base en cada carga. + +**Por qué:** una lista escrita a mano envejece sin que nadie se entere. Una generada no puede mentir. + +## Deuda técnica conocida + +### Columnas `can_*` que no hacen nada + +`role_modules` tiene `can_view`, `can_create`, `can_edit`, `can_delete`, `can_export` — y **el control de acceso no las lee**. Solo usa `permission` (`read`/`write`). + +**Riesgo:** poner `can_edit = 0` da falsa sensación de haber restringido algo. Ya causó confusión. + +**Arreglo:** o se usan de verdad, o se eliminan. Mientras tanto, conviene mantenerlas coherentes con `permission`. + +### `role` y `role_id` duplicados + +Un usuario tiene el rol en dos columnas. La interfaz lee una, los permisos salen de la otra. + +**Riesgo:** cambiar solo `role` deja al usuario con permisos que no corresponden. + +**Arreglo:** derivar `role` de `role_id` en lugar de almacenarlo. + +### Dos vías para el mismo formulario + +`turnero_consentimientos` y `lab_form_envios` guardan lo mismo con estructuras distintas y tokens de formato distinto. `ver_formulario_enviado.php` tiene que manejar ambos, y `form_cliente.php` existe solo para redirigir entre ellos. + +**Por qué sigue así:** unificarlas obliga a cambiar la plantilla aprobada en Meta y migrar los registros históricos. + +### Convenciones de nombre mezcladas + +Conviven `creado_at` y `created_at`, `creado_por` y `enviado_por`, español e inglés. Depende de la época de cada tabla. + +### Vistas muy grandes + +`ver_formulario_enviado.php` supera las 3.000 líneas y mezcla render, lógica de tomas prolongadas y JavaScript. Es el archivo más delicado de tocar del sistema. + +### Datos históricos incompletos + +Algunas columnas se agregaron después y las filas viejas quedaron en `NULL`, sin forma de recuperarlas: + +| Columna | Desde | Antes | +|---|---|---| +| `turnero_consentimientos.creado_por` | 3 ago 2026 | `NULL` | +| Identidad por toma en F-LAB-28 | 3 ago 2026 | No se guardaba | +| `admin_users.cedula` | 3 ago 2026 | Solo enfermeros la tenían | + +No hay traza de auditoría que permita reconstruirlos. + +### El dominio se deduce de cada petición + +`APP_URL` sale del `HTTP_HOST`. Si alguien entra por una IP o un dominio alterno, los enlaces que se generen llevarán esa dirección — y quedan guardados así en el WhatsApp del paciente. + +**Arreglo:** fijar `APP_URL` explícitamente. + +## Al hacer cambios + +- **Cambio de esquema** → dejá la migración en `migrations/`, idempotente. +- **Endpoint nuevo** → verificá permisos ahí adentro, no confíes en la vista. +- **Tocar el turnero** → es lo que más gente usa a diario; probá con un turno real. +- **Tocar formularios firmados** → tienen valor legal. Un render roto es un documento inválido. diff --git a/modules/soporte/docs/manual/10-primeros-pasos.md b/modules/soporte/docs/manual/10-primeros-pasos.md new file mode 100644 index 0000000..067af55 --- /dev/null +++ b/modules/soporte/docs/manual/10-primeros-pasos.md @@ -0,0 +1,52 @@ +# Primeros pasos + +Lo mínimo para moverse por el sistema, sin importar el rol. + +## Entrar + +Se ingresa con usuario y contraseña. Si no reconocés tu usuario, buscá tu **número de cédula**: la mayoría de las cuentas del personal se crearon así. + +Al entrar vas directo a la pantalla principal de tu rol. No todos ven lo mismo: el menú de la izquierda muestra únicamente los módulos habilitados para vos. + +## Si no ves algo que deberías ver + +Casi siempre es una de estas dos: + +1. **Te cambiaron los permisos hace poco.** Los permisos se cargan **al iniciar sesión**. Cerrá sesión y volvé a entrar. +2. **Tu rol no lo incluye.** Pedile a un administrador que lo revise. + +## Cómo está organizado + +| Zona | Qué contiene | +|---|---| +| Menú izquierdo | Los módulos a los que tenés acceso | +| Barra superior | Tu usuario y el cierre de sesión | +| Centro | La pantalla activa | + +## Los módulos principales + +| Módulo | Para qué | +|---|---| +| **Turnero** | Turnos presenciales: recepción, toma de muestras, pantallas | +| **Domicilios** | Visitas domiciliarias y su asignación a enfermeros | +| **Pacientes** | Fichas clínicas e historial | +| **Órdenes médicas** | Órdenes recibidas | +| **Formularios** | Consentimientos y fichas; diseño y envíos | +| **Soporte** | Esta documentación | + +## 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. + +**Las firmas quedan con nombre y cédula.** Cuando firmás un formulario, el sistema registra quién sos. No es opcional ni configurable. + +**Una muestra pendiente no se pierde.** Si un paciente queda debiendo una muestra y vuelve otro día, aparece sola en la estación, marcada como *visita anterior*, con los exámenes de aquella orden. + +**Nadie factura lo que no terminó.** En los reportes del día, lo cobrado en turnos finalizados y lo que sigue en curso se muestran por separado. Los ausentes y cancelados no se cuentan. + +## Manual según tu rol + +- [Recepción](?m=soporte&v=documentacion&s=manual&d=recepcion) +- [Toma de muestras](?m=soporte&v=documentacion&s=manual&d=toma-de-muestras) +- [Enfermeros — domicilios](?m=soporte&v=documentacion&s=manual&d=enfermeros) +- [Administración](?m=soporte&v=documentacion&s=manual&d=administracion) diff --git a/modules/soporte/docs/manual/20-recepcion.md b/modules/soporte/docs/manual/20-recepcion.md new file mode 100644 index 0000000..7609b2a --- /dev/null +++ b/modules/soporte/docs/manual/20-recepcion.md @@ -0,0 +1,78 @@ +# Recepción + +Guía de la pantalla de recepción del turnero: desde que llamás al paciente hasta que pasa a toma de muestras. + +## Tu escritorio + +Si el equipo está registrado por IP o token, el menú te muestra **solo tu escritorio**. Si no lo está, ves todos y elegís. + +Esto lo configura un administrador en `Configuración del turnero`. Si estás viendo escritorios que no son el tuyo, avisá: significa que ese equipo no quedó registrado. + +## El flujo completo + +### 1. Llamar al paciente + +**Llamar siguiente** toma el turno con mayor prioridad de la cola. También podés llamar a uno específico si hace falta saltarse el orden. + +El turno aparece en la pantalla de TV de la sala de espera. + +Si el paciente no responde, **Marcar ausente**. Queda registrado como ausente y no cuenta en la facturación. + +### 2. Verificar o crear el paciente + +Buscá por cédula o nombre. + +- **Existe** → se cargan sus datos y su historial. +- **No existe** → creá la ficha. Cédula, nombre completo, fecha de nacimiento, teléfono y EPS. + +> El teléfono importa más de lo que parece: es a donde se envían los consentimientos y las encuestas. Un número mal escrito significa un consentimiento que nunca llega. + +### 3. Seleccionar exámenes + +Cargá los exámenes solicitados. Si viene con una orden en RIPS, se pueden importar directamente en vez de cargarlos a mano. + +Al elegir los exámenes, el sistema decide solo qué consentimientos hacen falta: algunos van atados a un examen concreto (VIH, por ejemplo) y otros a la estación de destino. + +### 4. Datos de facturación + +Valor cobrado, método de pago, número de recibo. Si es por empresa o EPS, cargá el NIT y la autorización. + +Se admite pago combinado — efectivo más tarjeta, por ejemplo. + +### 5. Consentimientos + +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*. + +Si el envío por WhatsApp falla, el sistema manda un enlace en texto plano como respaldo. + +### 6. Elegir destino y guardar + +Seleccioná la estación de toma de muestras y guardá. El turno pasa a esa estación y el paciente sale de tu escritorio. + +## Situaciones frecuentes + +### El paciente solo viene a entregar una muestra + +Marcá **Solo entrega de muestras**. Se saltan los consentimientos por examen y no hace falta seleccionar exámenes. + +> Ojo: el formulario **Datos Toma de Muestras (F-LAB-08)** se sigue exigiendo en la estación. Ese no se omite nunca, porque recoge la historia clínica del momento de la toma. + +### El paciente ya vino antes y quedó debiendo una muestra + +No tenés que hacer nada especial. En la estación de toma de muestras le va a aparecer sola, marcada como *visita anterior*, junto con los exámenes de aquella orden. + +### El paciente no recibió el consentimiento + +1. Verificá el número de teléfono en su ficha. +2. Reenvialo desde la lista de consentimientos. +3. Si sigue sin llegar, avisá a un administrador: puede ser un problema de la plantilla en Meta, que no se arregla desde acá. + +### Hay que corregir algo después de guardar + +Mientras el turno no esté finalizado, un administrador puede reabrirlo desde el historial y cambiar su estado. + +## Encuestas + +Desde el historial se le puede enviar una encuesta de satisfacción al paciente por WhatsApp. diff --git a/modules/soporte/docs/manual/30-toma-de-muestras.md b/modules/soporte/docs/manual/30-toma-de-muestras.md new file mode 100644 index 0000000..ac0ccf0 --- /dev/null +++ b/modules/soporte/docs/manual/30-toma-de-muestras.md @@ -0,0 +1,75 @@ +# Toma de muestras + +Guía de la estación de toma de muestras: atender al paciente, recibir sus muestras y firmar los formularios. + +## Tu estación + +Igual que en recepción, si el equipo está registrado por IP o token, ves **solo tu estación**. Si no, las ves todas. + +## Atender un turno + +Los pacientes derivados desde recepción aparecen en tu bandeja. Al abrir uno ves su ficha completa: datos, exámenes solicitados, muestras a recibir y formularios pendientes. + +## Recibir muestras + +Cada muestra tiene tres estados posibles: + +| Estado | Significado | +|---|---| +| **Recibida** | La tomaste o el paciente la entregó correctamente | +| **Pendiente** | No se pudo obtener; queda debiendo | +| **Rechazada** | Se obtuvo pero no sirve — hemólisis, volumen insuficiente, mal rotulada | + +Al rechazar hay que indicar el motivo. Ese motivo queda registrado y se ve después en el historial. + +### Muestras de visitas anteriores + +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. + +> **El turno original no se modifica.** Sigue finalizado como estaba. Solo se registra en qué visita se completó la muestra. + +## Formularios + +### Datos Toma de Muestras (F-LAB-08) + +Obligatorio en todas las estaciones. Recoge la historia clínica del momento: síntomas, antecedentes familiares y personales, medicación, datos obstétricos si corresponde. + +Lo firmás vos, no el paciente. + +**Si el paciente ya lo llenó en una visita anterior**, aparece el botón *Cargar datos de la visita anterior*. Trae las respuestas de la última vez para que solo revises y ajustes lo que cambió. **No trae la firma**: esa la ponés vos, con la fecha de hoy. + +### Control de Tomas de Muestras Prolongadas (F-LAB-28) + +Para exámenes que requieren varias tomas en el tiempo: curvas de glicemia, prolactina, cortisol, test de Sullivan. + +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. +2. **Configurá los tiempos** si te lo pide (minuto 0, 30, 60…). +3. **Firmá cada toma** a medida que la hacés. El sistema registra la hora y **quién firmó**. +4. Cuando firmás una, el sistema calcula cuándo toca la siguiente y muestra una cuenta regresiva. + +> Cada toma se firma por separado y queda con el nombre de quien la hizo. Si cambia el turno del personal a mitad del protocolo, cada toma conserva el nombre correcto. + +Si hay que cerrar el protocolo antes de terminar todas las tomas, se puede hacer indicando el motivo. + +## Antes de finalizar + +El sistema no te deja finalizar si quedan muestras sin decidir. Cada una tiene que estar recibida, pendiente o rechazada. + +## Comentarios + +Podés dejar notas en el turno. Quedan visibles para el resto del personal y en el historial. + +## Preguntas frecuentes + +**¿Puedo revertir una muestra que marqué mal?** +Sí. Con el botón de deshacer vuelve a pendiente. + +**El formulario me muestra secciones de exámenes que el paciente no tiene.** +Avisá a soporte. Debería mostrar únicamente las del examen marcado. + +**¿Qué pasa si el paciente se va sin dar una muestra?** +Dejala en **pendiente**. Cuando vuelva —el día que sea— le va a aparecer sola a quien lo atienda. diff --git a/modules/soporte/docs/manual/40-enfermeros.md b/modules/soporte/docs/manual/40-enfermeros.md new file mode 100644 index 0000000..67fdfe8 --- /dev/null +++ b/modules/soporte/docs/manual/40-enfermeros.md @@ -0,0 +1,66 @@ +# Enfermeros — domicilios + +Guía del portal del enfermero: tus visitas domiciliarias, cómo agendarlas y qué hacer en cada una. + +## Tu portal + +Al entrar vas directo al portal. Ves **tus** domicilios: los que te asignaron y los que agendaste vos. + +Está pensado para usarse desde el celular en la calle. + +## Agendar un domicilio + +**Nuevo domicilio** abre el formulario: + +1. **Paciente** — buscalo por cédula. Si no existe, se crea ahí mismo. +2. **Dirección** — la del paciente con un botón, o escribí otra. Agregá indicaciones si el lugar es difícil de encontrar («apto 302, tocar campanilla»). +3. **Fecha y hora**. +4. **Servicio** — qué se va a hacer. +5. **Seguro y autorización** si aplica. +6. **Valores** — domicilio, copago. + +## Editar + +Podés editar los domicilios **que agendaste vos** y también **los que te asignaron**. El botón *Editar* aparece mientras el domicilio no esté completado ni cancelado. + +## Avisar al paciente por WhatsApp + +Hay un enlace que abre WhatsApp con el mensaje ya escrito, presentándote como profesional del Laboratorio Ximena Caicedo. Solo revisás y enviás. + +## Notas y archivos + +Podés dejar dos tipos de nota en cada domicilio: + +- **Nota de ficha** — estructurada, para datos clínicos. +- **Nota libre** — texto suelto. + +Ambas admiten fotos y archivos adjuntos, útil para órdenes médicas en papel o resultados. + +## Formularios + +Podés enviarle un formulario al paciente para que lo firme desde su celular, o copiar el enlace para pasárselo por otro medio. Los que ya firmó se pueden consultar desde el mismo domicilio. + +Cuando firmás vos un formulario, queda registrado con tu **nombre y cédula**, tomados de tu ficha de enfermero. + +## Estados de un domicilio + +| Estado | Significado | +|---|---| +| Programado | Agendado, sin atender | +| En curso | Estás en la visita | +| Completado | Terminado | +| Cancelado | No se hizo | + +## Preguntas frecuentes + +**No puedo editar un domicilio.** +Solo se pueden editar los propios o los asignados a vos, y solo si no está completado ni cancelado. + +**El paciente cambió de dirección.** +Editá el domicilio. Si el cambio es permanente, actualizá también la ficha del paciente. + +**Necesito reprogramar.** +Editá la fecha y hora, y avisale al paciente por WhatsApp. + +**¿Puedo ver domicilios de otro enfermero?** +No. El portal muestra únicamente los tuyos. diff --git a/modules/soporte/docs/manual/50-administracion.md b/modules/soporte/docs/manual/50-administracion.md new file mode 100644 index 0000000..67fd21a --- /dev/null +++ b/modules/soporte/docs/manual/50-administracion.md @@ -0,0 +1,87 @@ +# Administración + +Tareas de administrador: usuarios, permisos, configuración y reportes. + +## Usuarios y permisos + +### Crear un usuario + +La convención de la casa es usar el **número de cédula como nombre de usuario** para el personal asistencial. + +Cargá también la **cédula** en su ficha: es lo que aparece bajo la firma en los formularios. Si falta, el documento sale firmado sin identificación. + +Los enfermeros son un caso aparte: su cédula sale de la ficha de enfermero, no del usuario. + +### Cambiar el rol de alguien + +> Un usuario tiene **dos** campos de rol y hay que cambiar los dos. El texto (`role`) es lo que muestra la interfaz; el vínculo (`role_id`) es de donde salen los permisos reales. + +Si cambiás solo uno, el usuario ve un rol y tiene los permisos del otro. Ya pasó. + +Después del cambio, **el usuario debe cerrar sesión y volver a entrar**: los permisos se cargan al iniciar sesión, no en cada pantalla. + +### Dejar un módulo en solo lectura + +Lo que decide si alguien puede modificar es el campo `permission` (`read` o `write`) de cada módulo del rol. + +> Las columnas `can_editar`, `can_crear` y similares **no se usan** para el control de acceso. Ponerlas en cero no impide nada. Lo que manda es `permission`. + +## Turnero + +### Escritorios y estaciones + +Cada puesto de recepción y cada estación de muestras es un *lugar*. Se les puede asignar: + +- **Formularios obligatorios** — todo paciente que pase por ahí los debe firmar. +- **Equipos por IP o token** — así el operador ve solo su puesto y no puede confundirse. + +### Formularios obligatorios + +Se exigen por dos vías, y se acumulan: + +| Vía | Ejemplo | +|---|---| +| Por examen | VIH exige su consentimiento específico | +| Por estación | Toda toma de muestras exige F-LAB-08 | + +### Pantalla de TV + +Admite una lista de videos e imágenes que se reproducen en bucle, uno detrás de otro. Se pueden reordenar arrastrando y a cada imagen se le fija cuántos segundos dura. + +### Reabrir un turno + +Desde el historial se puede cambiar el estado de un turno, incluso reabrir uno finalizado, ausente o cancelado. + +## Facturación del día + +El panel muestra tres cifras: + +| Cifra | Qué incluye | +|---|---| +| **Facturado** | Turnos finalizados | +| **En proceso** | Turnos aún activos, ya cobrados pero sin cerrar | +| **Total estimado** | La suma de ambos | + +Los turnos **ausentes y cancelados no se cuentan** en ninguna: no se van a cobrar. + +## LIA + +El asistente del dashboard del turnero responde preguntas sobre la operación del día: tiempos por profesional, facturación, exámenes más pedidos, buscar un paciente. + +Tiene un presupuesto de consumo. Cuando se agota, se bloquea y hay que reponerlo. El consumo por pregunta es alto porque envía el contexto completo del día cada vez. + +## Documentos + +Los datos que salen en el encabezado de todos los documentos —nombre, dirección, ciudad, teléfono, logo, color— se editan desde **Configuración del laboratorio**, sin tocar código. + +> La dirección física se cambia desde ahí. Pero **la URL de los botones que llegan por WhatsApp no**: esa vive en la plantilla aprobada por Meta y se cambia en el WhatsApp Manager, con reaprobación de por medio. + +## Términos y condiciones + +El bot exige aceptarlos antes de conversar. La URL del documento está en **dos lugares** que hay que cambiar juntos: la versión activa de términos y el texto del mensaje de bienvenida, que la repite dentro. + +Se vuelve a pedir la aceptación cuando pasan 6 meses o cuando se publica una versión nueva marcada para reenvío. + +## Cuando algo falla + +El [runbook de incidentes](?m=soporte&v=documentacion&s=operacion&d=runbook) tiene los casos frecuentes con su diagnóstico y solución. diff --git a/modules/soporte/docs/operacion/30-despliegue.md b/modules/soporte/docs/operacion/30-despliegue.md new file mode 100644 index 0000000..09d9978 --- /dev/null +++ b/modules/soporte/docs/operacion/30-despliegue.md @@ -0,0 +1,79 @@ +# Despliegue y mantenimiento + +## Cómo se despliega + +No hay build ni compilación. El código PHP se sirve directo: + +```bash +git pull +``` + +Con eso los cambios están en producción. Es la contrapartida de no usar framework de frontend. + +**Si el cambio incluye esquema de base de datos**, hay que correr la migración además del `git pull`. + +## Repositorio + +| | | +|---|---| +| Remoto | `gitea` | +| Rama | `main` | + +Se trabaja directo sobre `main`. + +## Migraciones + +Van en `migrations/`, con nombre `AAAAMMDD_descripcion.sql`. + +**Deben ser idempotentes** — poder correrse más de una vez sin causar daño: + +```sql +ALTER TABLE admin_users + ADD COLUMN IF NOT EXISTS cedula VARCHAR(30) NULL AFTER cargo; + +UPDATE admin_users SET cedula = username + WHERE cedula IS NULL AND username REGEXP '^[0-9]{5,15}$'; +``` + +MariaDB 11.8 admite `IF NOT EXISTS` en `ALTER TABLE`. Para `UPDATE` e `INSERT`, la guarda va en el `WHERE`. + +Antes de dar por buena una migración, corrila dos veces y verificá que la segunda no cambie nada. + +> Aplicar un cambio directo en producción sin dejar la migración hace que un entorno nuevo no lo tenga, y nadie se entera hasta que algo falla. Si tocás el esquema, dejá el archivo. + +## Archivos subidos + +| Carpeta | Contenido | +|---|---| +| `uploads/turnero/tv_media/` | Videos e imágenes de la pantalla de TV | +| `uploads/terms/` | Documentos de términos y condiciones | + +Se crean solas al primer uso. **No están en el repositorio**: al mover el sistema de servidor hay que copiarlas aparte, o los enlaces quedan rotos. + +## Verificaciones después de desplegar + +```bash +# Sintaxis de los archivos tocados +php -l archivo.php + +# ¿Responde un enlace público? +curl -s -o /dev/null -w "%{http_code}\n" "https:///" +``` + +Si el cambio afectó permisos, recordá que **las sesiones abiertas conservan los permisos viejos** hasta que el usuario vuelva a entrar. + +## Configuración por entorno + +Las credenciales se leen de variables de entorno (`.env`, vía phpdotenv) y de `system_config`. El dominio no se configura: se deduce del `HTTP_HOST` de cada petición. + +## Mantenimiento periódico + +| Cada | Revisar | +|---|---| +| Semana | Consumo de LIA (`lab_config.lia_tokens_usados`) contra el tope de 1.000.000 | +| Mes | Que los enlaces enviados por WhatsApp respondan — sobre todo el de términos | +| Mes | Consentimientos que quedaron sin firmar | +| Trimestre | Plantillas de Meta: que sigan aprobadas | +| Trimestre | Muestras pendientes acumuladas por paciente | + +Las consultas para varias de estas revisiones están en el [runbook](?m=soporte&v=documentacion&s=operacion&d=runbook). diff --git a/modules/soporte/docs/tecnica/10-indice-de-modulos.md b/modules/soporte/docs/tecnica/10-indice-de-modulos.md new file mode 100644 index 0000000..4a534f2 --- /dev/null +++ b/modules/soporte/docs/tecnica/10-indice-de-modulos.md @@ -0,0 +1,41 @@ +# Índice de módulos + +Inventario de los módulos del sistema, generado del filesystem en cada carga. + +{{modulos}} + +## Cómo leer esta tabla + +**Vistas** son las pantallas (`modules//views/*.php`). **Endpoints** son los archivos que devuelven JSON (`modules//api/*.php`). + +Un módulo con **1 vista y 0 endpoints** suele ser un puente al sistema anterior: la vista solo incluye el archivo de la raíz, donde está el código real. + +```php +// modules/lab_domicilios/views/index.php +require_once APP_ROOT . '/lab_domicilios.php'; +``` + +**En SYSTEM_MODULES** indica si el módulo pasa por el control de permisos. Los que dicen «no» quedan accesibles para cualquier sesión — es el caso de módulos auxiliares que se consumen desde otras pantallas. + +## Dónde está el código de verdad + +| Módulo | Código real | +|---|---| +| `turnero` | En el módulo. Es el más grande y el más nuevo | +| `registro_exams` | En el módulo | +| `lab_examenes`, `medicos` | En el módulo | +| `lab_domicilios`, `lab_pacientes`, `lab_ordenes`, y demás `lab_*` | Archivo de la raíz; el módulo es un puente | +| `whatsapp` | `index.php` y `services/BotService.php` | +| `enfermero_portal` | `enfermero_portal.php` | + +## Servicios y clases compartidas + +{{servicios}} + +## Detalle por módulo + +- [Turnero](?m=soporte&v=documentacion&s=tecnica&d=turnero) +- [WhatsApp y bot](?m=soporte&v=documentacion&s=tecnica&d=whatsapp-bot) +- [Formularios](?m=soporte&v=documentacion&s=tecnica&d=formularios) +- [Domicilios](?m=soporte&v=documentacion&s=tecnica&d=domicilios) +- [Todos los endpoints](?m=soporte&v=documentacion&s=tecnica&d=endpoints) diff --git a/modules/soporte/docs/tecnica/20-turnero.md b/modules/soporte/docs/tecnica/20-turnero.md new file mode 100644 index 0000000..a28c084 --- /dev/null +++ b/modules/soporte/docs/tecnica/20-turnero.md @@ -0,0 +1,104 @@ +# Módulo Turnero + +El módulo más grande del sistema: 12 vistas y unos 70 endpoints. Gestiona la atención presencial completa. + +## Vistas + +| Vista | Para quién | Qué hace | +|---|---|---| +| `dashboard` | Admin, supervisor | Métricas del día, facturación, asistente LIA | +| `historial` | Admin, supervisor | Turnos de varios días, filtros, exportación | +| `bandeja` | Bacteriólogo, admin | Turnos del día con su detalle completo | +| `recepcion` | Recepcionista | Atención en el mostrador | +| `lugar` | Bacteriólogo | Estación de toma de muestras | +| `kiosko` | Público | El paciente saca su turno | +| `display_global` | Público | Pantalla de TV de la sala | +| `verificar_paciente` | Recepción | Consulta rápida de una ficha | +| `chat` | Recepción | Conversación de WhatsApp del turnero | +| `configuracion` | Admin | Lugares, dispositivos, plantillas, pantalla de TV | + +`kiosko` y `display_global` son las **únicas rutas públicas** del sistema (`core/Router.php`): no hay quién inicie sesión en un televisor ni en el tótem de la entrada. + +## Menú dinámico + +`modules/turnero/module.php` no devuelve una lista fija: la arma según **el rol y la IP del equipo**. + +``` +recepcionista → chat, verificar paciente, TV, y su escritorio +bacteriólogo → bandeja y su estación +admin → todo +``` + +Si el equipo está en `turnero_dispositivos` (por IP o por cookie `turnero_token`), el usuario ve **solo su puesto**. Si no, los ve todos. Evita que alguien atienda desde el escritorio equivocado. + +## Modelo de datos + +``` +turnero_sesiones un día de operación + └── turnero_turnos código, estado, tiempos, prioridad + ├── turnero_solicitudes qué se hace y cuánto se cobró + │ ├── turnero_examen_items + │ └── turnero_muestras + ├── turnero_consentimientos + └── turnero_comentarios +``` + +### Estados + +``` +espera → en_recepcion → en_espera_lugar → en_servicio → finalizado + ausente / cancelado +``` + +Solo `finalizado` cuenta como facturación real. + +## Consentimientos + +Se crean automáticamente desde **dos fuentes** que se acumulan: + +| Fuente | Tabla | +|---|---| +| Por examen | `exam_tipo_consentimientos` | +| Por estación destino | `turnero_lugar_consentimientos` | + +`get_consentimientos.php` los autocrea si faltan y calcula el estado de cada uno. + +> En visitas de *solo entrega de muestras* no aplican los formularios por examen (no hay exámenes), pero **sí los de estación**. Por eso F-LAB-08 se exige igual. + +## Tomas prolongadas + +El formulario **F-LAB-28** cubre exámenes seriados. La lógica está repartida entre `ver_formulario_enviado.php` (render) y `modules/turnero/api/guardar_toma.php` (guardado). + +- El esquema trae secciones para todos los exámenes posibles; se muestran solo las del examen marcado, mediante la `condicion` de cada separador. +- `_tomas_config` en `datos_respuestas` guarda qué ciclos se configuraron para ese paciente. +- Cada firma se guarda en su propio campo (`_tm00_f`, `_tm30_f`, …) junto con la hora y **la identidad de quien firmó**, resuelta en el servidor desde la sesión. +- Al firmar, el endpoint calcula cuándo toca la siguiente toma y actualiza `siguiente_toma_at`. + +> La identidad se resuelve **en el servidor**, no se acepta del cliente: cada toma puede firmarla un profesional distinto y esa es la única fuente confiable. Las firmas anteriores al 3 de agosto de 2026 no tienen ese dato y no es recuperable. + +## Muestras entre visitas + +Una muestra que queda `pendiente` o `rechazada` reaparece cuando el paciente vuelve, con la bandera `es_pendiente_anterior` y los exámenes de la orden original. + +Al recibirla, `turnero_muestras.recibida_en_turno_id` registra en qué turno se completó. Historial y bandeja usan ese dato para enlazar ambos turnos en los dos sentidos. + +**El turno original no se modifica**: sigue finalizado. Solo se agrega la trazabilidad. + +## LIA + +`api/ai_chat.php` — asistente sobre Gemini Flash. + +| Aspecto | Detalle | +|---|---| +| Contexto | Se arma en cada llamada con hasta 60 turnos del día y sus detalles | +| Historial | Los últimos intercambios se envían para que entienda preguntas de seguimiento | +| Tope de salida | `maxOutputTokens`; si Gemini corta, se avisa con `finishReason` | +| Presupuesto | `lab_config.lia_tokens_usados` contra `LIA_TOKENS_MAX` | + +Se contabiliza `totalTokenCount`, que **incluye el contexto de entrada**. Como el contexto va completo en cada pregunta, el gasto por consulta es alto aunque la respuesta sea breve. + +## Endpoints propios + +Los del turnero usan `api/_helpers.php`, que provee `db()`, `inputJson()`, `jsonOk()`, `jsonError()`, `adminId()`, `requireTurnero()` y `notificarSSE()`. + +`notificarSSE()` avisa a las pantallas conectadas para que se refresquen sin recargar. diff --git a/modules/soporte/docs/tecnica/30-formularios.md b/modules/soporte/docs/tecnica/30-formularios.md new file mode 100644 index 0000000..a42a177 --- /dev/null +++ b/modules/soporte/docs/tecnica/30-formularios.md @@ -0,0 +1,104 @@ +# Formularios y firma digital + +Cómo se definen los formularios, cómo se envían y cómo se firman. Es transversal: lo usan el turnero, los domicilios y los envíos sueltos. + +## Definición + +Un formulario es una fila en `lab_formularios`. Su estructura está en la columna `esquema`, un JSON con la lista de campos: + +```json +[ + {"id": "_sep1", "tipo": "separador", "label": "Datos del paciente"}, + {"id": "_nom", "tipo": "linked", "linked_key": "nombre_completo", "label": "Nombre"}, + {"id": "_sint", "tipo": "checkbox", "label": "Síntomas", "options": ["Fiebre", "Tos"]}, + {"id": "_fir", "tipo": "firma_profesional", "label": "Firma del profesional"} +] +``` + +### Tipos de campo + +| Tipo | Qué es | +|---|---| +| `separador` | Encabezado de sección; admite `condicion` | +| `parrafo` | Texto fijo (consentimientos, notas legales) | +| `texto`, `textarea`, `numero` | Entrada libre | +| `fecha`, `fecha_hoy`, `hora` | Fechas y horas | +| `radio`, `checkbox`, `select` | Opciones | +| `linked` | Se autocompleta con un dato del paciente vía `linked_key` | +| `firma` | Firma del paciente | +| `firma_profesional` | Firma del profesional | + +### Secciones condicionales + +Un separador puede depender de otro campo: + +```json +{"id": "_sep_ins", "tipo": "separador", "label": "Insulina · Minuto 0", + "condicion": {"campo_id": "_examen", "valores": ["Insulina"]}} +``` + +La sección y **todos sus campos** se ocultan si la condición no se cumple. Los campos heredan el estado mediante el atributo `data-sep-id`. + +## Las dos vías de envío + +Un mismo formulario se firma por dos caminos, con tablas y tokens distintos: + +| Vía | Tabla | Token | Respuestas | +|---|---|---|---| +| Turnero | `turnero_consentimientos` | UUID → `?token=` | `datos_respuestas` | +| Domicilios y envíos | `lab_form_envios` | 64 hex → `?t=` | `datos_cliente` | + +Las dos las muestra `ver_formulario_enviado.php`, que distingue **por el formato del token**. De ahí que existan dos parámetros para lo que parece lo mismo. + +`form_cliente.php` detecta tokens con formato UUID y redirige a `ver_formulario_enviado.php` — necesario porque la plantilla de WhatsApp aprobada en Meta apunta a la primera página. + +## Parámetros del visor + +| Parámetro | Efecto | +|---|---| +| `token` | Consentimiento del turnero (UUID) | +| `t` | Envío de formulario (64 hex) | +| `id` | Acceso interno con sesión | +| `embed=1` | Modo embebido; **activa el filtrado de secciones** | +| `compact=1` | Grilla de campos y tarjetas de toma | +| `zoom` | Escala | +| `autoprint=1` | Abre el diálogo de impresión | + +> `embed=1` no es cosmético: sin él no se aplica el filtrado de secciones de tomas prolongadas y el documento muestra secciones que no corresponden. Bandeja e historial lo pasan siempre. + +## Firma + +### Del paciente + +Se dibuja en un canvas y se guarda como imagen en `datos_cliente['_svg']`. También se admite pad biométrico Topaz. + +### Del profesional + +Se dibuja igual, pero además queda **quién firmó**. Hay tres endpoints según el contexto: + +| Endpoint | Contexto | +|---|---| +| `modules/turnero/api/guardar_toma.php` | Tomas prolongadas — una firma por toma | +| `modules/turnero/api/firmar_profesional_consentimiento.php` | Consentimientos del turnero | +| `api/lab/firmar_profesional.php` | Envíos de formularios | + +La identidad se guarda como `_pro_nombre` y `_pro_cedula`; en tomas prolongadas, además por campo (`_tm30_f_pro_nombre`), porque cada toma puede firmarla alguien distinto. + +De dónde sale la identidad: + +``` +admin_users.cedula → personal en general +lab_enfermeras.numero_documento → enfermeros (vía admin_users.enfermera_id) +``` + +> Al mostrar un documento firmado **no se usa un valor por defecto**: si no quedó guardado quién firmó, se muestra vacío. Antes se caía al usuario de la sesión actual, lo que atribuía la firma a quien simplemente estaba mirando el documento. + +## Precarga desde una visita anterior + +`modules/turnero/api/get_formulario_anterior.php` devuelve las respuestas del último formulario firmado del mismo paciente, para no reescribir la historia clínica en cada visita. + +Excluye deliberadamente firmas e identidad del profesional anterior: cada visita se firma de nuevo, con la fecha de hoy y quien atienda. + +## Diseñador + +`lab_formulario_builder.php` permite armar el esquema desde la interfaz, sin escribir JSON a mano. diff --git a/modules/soporte/docs/tecnica/40-whatsapp-bot.md b/modules/soporte/docs/tecnica/40-whatsapp-bot.md new file mode 100644 index 0000000..b560ee7 --- /dev/null +++ b/modules/soporte/docs/tecnica/40-whatsapp-bot.md @@ -0,0 +1,90 @@ +# WhatsApp y bot + +El sistema nació como bot de WhatsApp y esa integración sigue siendo central. + +## Servicios + +{{servicios}} + +## `WhatsAppService` + +Envuelve la Cloud API de Meta. Se elige la línea al construirlo: + +```php +$wa = new WhatsAppService(); // principal +$wa = new WhatsAppService('turnero'); // turnero +``` + +Ambas líneas comparten cuenta (WABA) y token; **lo único que cambia es el `phone_number_id`**. Si un mensaje sale por la línea equivocada, casi siempre falta el argumento. + +Métodos principales: + +```php +$wa->sendTextMessage($telefono, $texto, $meta); +$wa->sendTemplateMessage($telefono, $plantilla, $idioma, [], [], $componentes, $meta); +``` + +`$meta` acompaña el registro del mensaje: `['canal' => 'turnero', 'operator_id' => adminId()]`. + +## Plantillas + +Fuera de la ventana de 24 horas hay que usar plantilla aprobada. `message_templates` guarda una copia local con sus `components`, pero **la copia no manda**: la versión real vive en Meta. + +Consecuencia importante: + +> Las URL de los botones están **en la plantilla**, no en el código. Nuestro código solo envía los parámetros (`{{1}}`). Cambiar el código no altera el enlace que recibe el paciente. + +Ejemplo — botón de `consentimiento_turno_v2`: + +``` +https://erp.laboratorioximenacaicedo.com/form_cliente.php?t={{1}} +``` + +Y así se arman los componentes al enviar: + +```php +$rawComps = [ + ['type' => 'body', 'parameters' => [['type' => 'text', 'text' => $codigo]]], + ['type' => 'button', 'sub_type' => 'url', 'index' => '0', + 'parameters' => [['type' => 'text', 'text' => $token]]], +]; +``` + +### Respaldo + +Si el envío por plantilla falla, se manda un texto plano con el enlace armado desde el dominio del servidor. Ese texto **no** pasa por Meta, así que su URL puede diferir de la del botón. + +## `BotService` + +Decide qué hacer con cada mensaje entrante: + +1. ¿Aceptó los términos? Si no, se los pide y no avanza. +2. ¿Está en horario? (`BusinessHoursService`) +3. ¿La conversación la tomó un operador humano? El bot no interrumpe. +4. Si no, responde según el estado de la conversación (`ConversationStateService`) y el menú (`MenuService`). + +## Términos y condiciones + +| Tabla | Contenido | +|---|---| +| `terms_versions` | Versión activa, URL del documento, mensajes | +| `terms_acceptance` | Historial de aceptaciones | +| `system_config.terms_message` | Texto de bienvenida, **repite la URL dentro** | + +Se vuelve a pedir la aceptación si nunca aceptó, si pasaron más de 6 meses, o si hay versión nueva con `forzar_reenvio`. + +> La URL del documento está en **dos** lugares. Cambiar solo uno deja al otro sirviendo un enlace viejo. + +## Configuración + +Todo en `system_config`: `whatsapp_token`, `whatsapp_api_url`, `whatsapp_business_account_id`, `whatsapp_phone_number_id`, `whatsapp_phone_number_id_turnero`, `webhook_verify_token`. + +## Diagnóstico + +| Síntoma | Dónde mirar | +|---|---| +| Sale por el número equivocado | Falta `'turnero'` en el constructor | +| Enlace roto en un botón | La plantilla en Meta | +| No llega ninguna plantilla | Estado de aprobación en WhatsApp Manager | +| Llega texto plano en vez de plantilla | El respaldo actuó: la plantilla falló | +| El bot no responde | `webhook_logs`, horario, estado de la conversación | diff --git a/modules/soporte/docs/tecnica/50-domicilios.md b/modules/soporte/docs/tecnica/50-domicilios.md new file mode 100644 index 0000000..073d72a --- /dev/null +++ b/modules/soporte/docs/tecnica/50-domicilios.md @@ -0,0 +1,65 @@ +# Domicilios + +Visitas domiciliarias: agendamiento, asignación de enfermeros y seguimiento. + +## Dónde está el código + +Es de la generación anterior. El módulo es un puente: + +```php +// modules/lab_domicilios/views/index.php +require_once APP_ROOT . '/lab_domicilios.php'; +``` + +| Archivo | Rol | +|---|---| +| `lab_domicilios.php` | Pantalla administrativa | +| `enfermero_portal.php` | Portal del enfermero, pensado para celular | +| `classes/lab/Domicilio.php` | Reglas de negocio | +| `classes/lab/Asignacion.php` | Vínculo enfermero–domicilio | +| `api/lab/*.php` | Endpoints | + +## Datos + +``` +lab_domicilios + ├── lab_asignaciones qué enfermero atiende + ├── lab_domicilio_notas seguimiento, admite adjuntos + └── lab_domicilio_pagos cobros +``` + +Estados: `programado` → `en_curso` → `completado`, o `cancelado`. + +## Permisos + +Se combinan dos niveles. + +**Nivel módulo** — `hasModuleWrite('lab_domicilios')` decide si aparecen los botones de crear y editar. Un rol con `permission = 'read'` ve la pantalla sin poder modificar. + +**Nivel registro** — un enfermero solo puede editar domicilios **que creó o que tiene asignados**. Se verifica en el servidor (`api/lab/save_domicilio.php`): + +```sql +SELECT d.creado_por, + (SELECT COUNT(*) FROM lab_asignaciones a + WHERE a.domicilio_id = d.id AND a.enfermera_id = ?) AS asignado + FROM lab_domicilios d WHERE d.id = ? +``` + +> No alcanza con ocultar el botón en la vista: el endpoint verifica por su cuenta. Cualquier endpoint que modifique datos debe hacer lo mismo. + +## Portal del enfermero + +`enfermero_portal.php` admite `admin`, `superadmin` y `enfermero`. Los administradores pueden ver el portal de un enfermero concreto pasando `?eid=`, útil para dar soporte. + +Incluye: + +- Agenda propia con creación y edición +- Notas de ficha y notas libres, con fotos y archivos +- Envío de formularios al paciente, o copia del enlace +- Enlace a WhatsApp con el mensaje ya redactado, presentando al profesional como parte del Laboratorio Ximena Caicedo + +## Formularios + +Los domicilios usan la vía `lab_form_envios` (token de 64 hex, parámetro `?t=`), a diferencia del turnero que usa UUID. Ver [Formularios](?m=soporte&v=documentacion&s=tecnica&d=formularios). + +Cuando el enfermero firma, su nombre y cédula salen de `lab_enfermeras` a través de `admin_users.enfermera_id`. diff --git a/modules/soporte/docs/tecnica/60-webhook.md b/modules/soporte/docs/tecnica/60-webhook.md new file mode 100644 index 0000000..f8f573d --- /dev/null +++ b/modules/soporte/docs/tecnica/60-webhook.md @@ -0,0 +1,166 @@ +# Webhook de WhatsApp + +_Migrado de `WEBHOOK_ENDPOINTS.md` (raíz del repositorio), donde vivía suelto._ + + +## 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; +} +``` diff --git a/modules/soporte/docs/tecnica/90-endpoints.md b/modules/soporte/docs/tecnica/90-endpoints.md new file mode 100644 index 0000000..1079281 --- /dev/null +++ b/modules/soporte/docs/tecnica/90-endpoints.md @@ -0,0 +1,41 @@ +# Endpoints + +Inventario de los endpoints de todos los módulos, generado del filesystem en cada carga. La descripción sale del comentario de cabecera de cada archivo. + +## Convenciones + +Los endpoints son archivos PHP sueltos que devuelven JSON. **No pasan por el enrutador**: se invocan por su ruta real. + +``` +modules/turnero/api/get_historial.php +api/lab/save_domicilio.php +``` + +Cada módulo tiene su `api/_helpers.php` con lo común. Los archivos que empiezan con guión bajo son internos y no se listan acá. + +### Helpers típicos + +| Función | Qué hace | +|---|---| +| `db()` | Conexión PDO | +| `inputJson()` | Cuerpo de la petición como arreglo | +| `jsonOk($datos)` | Respuesta correcta | +| `jsonError($msg, $codigo)` | Error con código HTTP | +| `adminId()` | Id del usuario de la sesión | +| `requireMethod('POST')` | Corta si el método no coincide | +| `requireTurnero()` | Corta si no tiene acceso al turnero | + +### Reglas al agregar uno + +1. **Verificá permisos en el propio endpoint.** Que la vista haya ocultado el botón no protege nada. +2. **Resolvé la identidad en el servidor.** Para saber quién hace una acción, usá `adminId()`, no un valor que mande el navegador. +3. **Consultas preparadas siempre.** +4. **Dejá un comentario de cabecera** describiendo qué hace y qué recibe: es lo que aparece en la tabla de abajo. + +## Inventario + +{{endpoints}} + +## Endpoints fuera de módulos + +`api/lab/` agrupa los del laboratorio de la generación anterior — domicilios, pacientes, formularios, configuración. Siguen las mismas convenciones y usan `api/lab/_helpers.php`.