Commit Graph
75 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Opus 5 2b1f08b03f Evitar teléfonos falsos en la historia clínica, y pedir el número a quien lo oculta
El arreglo anterior dejó un defecto: obtenerOCrearDesdeWhatsapp() copiaba
users.phone_number al teléfono de la ficha, y para quien oculta su número ahí
va el BSUID. normalizarTelefono() le quitaba el punto y las letras, así que
"CO.1761088155094242" quedaba guardado como "1761088155094242": un número de
16 dígitos, falso, con apariencia de real, dentro de una historia clínica y
sin que nadie lo notara. Ahora esa ficha se crea sin teléfono, que es la
verdad: no lo tenemos.

- esBsuid() queda definido una sola vez, en config.php, que es donde lo ven
  tanto el servicio de WhatsApp como las clases del laboratorio.
- La lista y el detalle de pacientes dicen "Solo por WhatsApp" en vez de
  mostrar el identificador crudo: recepción necesita entender por qué no
  puede llamar, no ver un código.
- Al crear la ficha desde una conversación se le pide el número con el botón
  de Meta, una sola vez (users.contacto_pedido_at). El texto es editable
  desde configuración. Solo se marca como pedido si Meta aceptó el envío,
  para poder reintentar si falló.
- tests/test_bsuid.php: 30 verificaciones sobre identidad, envío, vinculación
  y creación de fichas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:26:31 -05:00
Lizandro GuarnizoandClaude Opus 5 26025a6399 Reconocer por BSUID a quien oculta su teléfono, y poder pedírselo
Meta no permite averiguar el teléfono a partir del BSUID: no hay endpoint de
consulta inversa, cada empresa debe llevar su propia equivalencia. Pero el
BSUID llega en TODOS los webhooks de mensaje, también en los que aún traen
teléfono, así que la equivalencia se puede ir guardando sola mientras la
persona todavía muestra su número.

- users.bsuid guarda esa equivalencia, y el webhook la anota en cada mensaje.
  Cuando alguien oculte su número, se le seguirá reconociendo y respondiendo
  a su teléfono de siempre.
- Va en columna aparte y no en phone_number porque el teléfono además cruza
  con el paciente y el turnero; mezclarlos rompería esos cruces.
- Para quien nunca escribió mostrando el número queda pedirle el contacto:
  pedirContacto() manda el botón request_contact_info, y el webhook atiende
  el mensaje `contacts` que llega si acepta.
- Al vincular puede aparecer un segundo registro de la misma persona. No se
  fusionan: una fusión mal hecha mezcla dos historias clínicas. Gana el que
  tiene el teléfono, el otro queda anotado en el log para revisarlo a mano.
- scripts/backfill_bsuid.php carga las equivalencias del histórico (6.423),
  con --simular para verlas sin escribir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 20:07:52 -05:00
Lizandro GuarnizoandClaude Opus 5 92179af56a Atender a quien oculta su teléfono en WhatsApp (BSUID)
Meta desplegó los nombres de usuario: quien oculta su teléfono llega al
webhook sin `from` ni `wa_id`, identificado solo por su BSUID con la forma
"CO.1761088155094242". El webhook exigía teléfono y descartaba el mensaje
en silencio, sin guardarlo ni dejar rastro: 221 mensajes de 65 personas
ignorados por completo, ya el 3-6% de lo que entra cada día y subiendo.

- webhook.php lee `from_user_id` cuando no llega el teléfono, y rescata el
  nombre del contacto indexando también por `user_id`.
- WhatsAppService responde por BSUID: Meta exige el campo `recipient` en
  lugar de `to`. Se traduce en sendMessage(), el punto por donde pasan todos
  los envíos, en vez de en los diez métodos que arman payloads.
- formatPhoneNumber() devuelve el BSUID intacto; antes le quitaba el punto
  y las letras y lo dejaba en un destinatario inexistente.
- Las cuatro columnas del flujo pasan a varchar(32): los BSUID de Meta
  llegan a 23 caracteres y se truncaban en silencio en varchar(20).

De estas personas no tendremos el teléfono; si un flujo lo necesita, hay
que identificarlas por documento.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 19:41:10 -05:00
Lizandro GuarnizoandClaude Opus 5 381c2ca04e Rol Calidad incluye también formularios
La coordinación del SIG administra los documentos del sistema además de revisar
los tiempos del proceso, así que el rol suma lab_formularios en solo lectura.
Permite consolidar en una sola cuenta a quien antes necesitaba dos accesos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:42:57 -05:00
Lizandro GuarnizoandClaude Opus 5 3a7b267f65 Rol de Calidad, y restricción por rol dentro del turnero
Rol nuevo para consultar turnos y tiempos de atención, de solo lectura y con
acceso únicamente a dashboard e historial. Se le permite exportar el historial,
que es la forma de analizar tiempos fuera del sistema.

Al crearlo salió que las vistas del turnero no verificaban nada por su cuenta:
el control del ERP es por módulo, así que cualquiera con acceso al turnero podía
abrir Configuración —lugares, dispositivos, plantillas— escribiendo la URL,
aunque el menú no se la mostrara. Aplicaba a todos los roles, no solo al nuevo.

Se agrega _acceso.php con una verificación de rol que usan las tres pantallas
que operan sobre la atención o cambian configuración. Verificado que cada rol
conserva lo que ya usaba: recepcionista entra a recepción, bacteriólogo a las
estaciones, supervisor y administradores a todo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:50:24 -05:00
Lizandro GuarnizoandClaude Opus 5 dc17c6a469 Permitir atender Pediatría y Ginecología desde equipos con estación fija
Un equipo registrado por IP o token queda atado a su estación: si pedía otra,
lugar.php lo devolvía a la suya sin mostrar nada, y parecía que el enlace estaba
roto. La restricción tiene sentido para las Tomas de Muestras, pero Pediatría y
Ginecología se atienden desde cualquier puesto.

Se agrega la marca acceso_libre en turnero_lugares en vez de codificar los
nombres, para que mañana se habilite otra estación sin tocar código. Las
marcadas así no redirigen y aparecen en el menú del equipo junto a la suya.

El bloqueo se mantiene en todo lo demás: el equipo sigue atado a su estación, el
botón de cambiar sigue oculto y no puede saltar a una Toma de Muestras ajena.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:47:11 -05:00
Lizandro GuarnizoandClaude Opus 5 d448ccc707 Registrar en migrations los cambios de esquema aplicados hoy
Los cambios del 2026-08-03 (admin_users.cedula, turnero_consentimientos.creado_por,
turnero_muestras.recibida_en_turno_id y la tabla turnero_tv_media) se aplicaron
directo en producción y no quedaron en el repo, así que un entorno nuevo no
los tendría. Se registran como migración idempotente (IF NOT EXISTS + guardas
en el UPDATE/INSERT); verificada contra producción sin efectos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:05:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e595f5e569 fix: add db() to ingest_paciente; get_examenes_rips and recepcion updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 15:43:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f5de2c90ca feat: add CIE-10 diagnosticos migration endpoint and table
- migrations/20260717_cie10_diagnosticos.sql: CREATE TABLE cie10_diagnosticos
- api/lab/ingest_diagnosticos.php: POST endpoint that receives batches of
  {cod, concepto} rows and upserts into cie10_diagnosticos (ON DUPLICATE KEY)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-17 10:27:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0f6c837d35 feat: campo número de recibo al pagar con tarjeta
- Campo inp-recibo aparece/desaparece al seleccionar 'Tarjeta' en forma de pago
- Se incluye en el payload como numero_recibo y se guarda en turnero_solicitudes
- Migración: ADD COLUMN numero_recibo VARCHAR(40) en turnero_solicitudes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 20:42:39 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7506f63627 chore: migración lab_eps y lab_ciudades en run_pendientes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-16 20:40:00 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e58ebf57e1 Migración: registrar lab_tomas_config en system_modules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 10:49:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 912798fbcc feat(pacientes): origen 'lab' en vez de 'rips' + tabla ampliada + proteger origen en update
- Valor de BD cambia de 'rips' a 'lab' (más genérico)
- ingest_paciente: en UPDATE nunca pisa origen (conserva manual/whatsapp/lab)
- Badge muestra 'Resultados Lab' con ícono matraz; retrocompat con valor 'rips'
- Tabla agrega: teléfono+WA, email, EPS, género en nombre
- filtro dropdown usa 'lab' en lugar de 'rips'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 11:34:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 828e323eba feat(lab_pacientes): columna origen + vista mejorada con stats y filtros
- Migration: columna 'origen' (manual|rips|whatsapp) en lab_pacientes
- ingest_paciente.php: guarda origen='rips' en cada paciente importado
- Paciente::listar(): filtro por origen + orden por created_at DESC
- Paciente::stats(): conteo total/importados/con_wa/manuales
- get_pacientes.php: expone ?origen= y ?stats=1
- lab_pacientes.php: cards de stats clicables, badge origen por fila,
  columna ciudad, columna fecha registro, dropdown filtro por origen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 11:16:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d5b6de1f46 turnero: restricción por token de navegador en lugar de IP pública
- Token UUID generado en localStorage del navegador, persistido como cookie
- turnero_dispositivos.token: nueva columna para identificar equipos
- recepcion.php y lugar.php: chequean token (cookie) primero, IP como fallback
- configuracion.php: banner con token del equipo actual + botón "Registrar este equipo"
- save_dispositivo.php: acepta campo token al crear/editar dispositivo
- Migración: 20260709_dispositivos_token.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 10:56:53 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f4da864db7 feat(turnero): toma prolongada secuencial + bloqueo muestras/cierre
- ver_formulario_enviado: auto-fill médico (nombres/especialidad/código) en datos_prefilled desde solicitud
- ver_formulario_enviado: canvases de toma progresiva bloqueados secuencialmente — solo el turno activo es firmable, los siguientes aparecen al expirar el countdown
- ver_formulario_enviado: countdown prominente con reloj grande, color dinámico, sonido (Web Audio) y vibración al llegar la hora
- lugar: bloquear cierre del modal durante toma progresiva activa (botón X deshabilitado hasta mp_completar)
- lugar: bloquear Finalizar atención si hay muestras pendientes sin decisión (recibida/rechazada)
- lugar: _syncBtnFinalizar combina consentimientos + muestras para el aviso y estado del botón
- lugar: restricción de usuario a lugar específico via turnero_lugar_id (admin_users) + bloqueo JS en confirmarLugar()
- migrations: turnero_lugar_id en admin_users para restringir bacteriólogos por usuario

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 21:16:07 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 229977785b fix(turnero): no mostrar consentimientos de toma de muestras en recepcion
Agrega origen_lugar_id a turnero_consentimientos para distinguir consents
creados por examen (null) vs por estación (= lugar_id destino).
Recepcion filtra los de origen_lugar_id != null; lugar.php muestra
solo los suyos + los de examen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:05:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8760b685ad feat(turnero): dispositivos por IP, firmas, formularios y muestras prolongadas
- login.php: detectar dispositivo por IP al autenticar y redirigir al lugar asignado
- dashboard.php / lugar.php / recepcion.php: bloquear acceso por dispositivo (IP→lugar)
- lugar.php: modal firma profesional pre-guardada; compact 2-col en iframe; botón WA
  solo cuando el formulario requiere firma del paciente
- ver_formulario_enviado.php: modo compact tablet, auto-save borrador (merge), firma
  profesional en 1 clic, Muestras Prolongadas (countdown + Finalizar), auto_profesional
  pre-fill desde sesión, fecha→fecha_hoy en campos de toma/recepción
- get_consentimientos.php: añadir requiere_firma_paciente para controlar botón WA
- save_firma_profesional.php: API para guardar firma pre-configurada del profesional
- migrations: turnero_dispositivos, admin_users.firma_svg, clon VIH F-LAB-05 para turnero

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:58:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a9f2528a2d fix: agregar columna numero_orden a lab_domicilios y turnero_solicitudes
La columna se usaba en ver_formulario_enviado.php pero no existía
en la BD, causando error 500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-06 08:54:26 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f695b04f5b feat(caja): generar lab_recepciones al confirmar solicitud en turnero
- create_solicitud.php crea lab_recepciones (es_historico=0) dentro de
  la transacción: num_factura auto-secuencial para registros nuevos,
  campos empresa/subgrupo/autorización/diagnóstico tomados de la solicitud
- Inserta lab_relaciones por cada examen con precio del snapshot JSON
- Inserta lab_pagos + lab_pagos_det si hay metodo_pago registrado
- Bloque try/catch: si migración 11 aún no corrió solo se loguea,
  no se revierte la solicitud principal
- recepcion.php muestra toast "Recepción #N generada" al guardar
- Migración 11: ALTER lab_recepciones.id → AUTO_INCREMENT,
  ADD solicitud_id, ALTER lab_pagos.numcaja_legacy → nullable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:12:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a7aac706ee feat(recepcion): motor de precios + selector empresa/convenio
- Nueva sección empresa/convenio en recepcion.php: búsqueda live por
  nombre/NIT, selector de subgrupo, campo autorización (cuando req_autoriza),
  diagnóstico CIE-10
- Tabla de precios calculados en tiempo real: resolución por tarifa de la
  empresa o subgrupo, descuento empresa aplicado, botón "Aplicar al cobro"
- Al agregar/quitar exámenes o cambiar empresa/subgrupo se recalcula automáticamente
- Nuevo API get_precios_examenes.php: resuelve precios desde lab_tarifas
  con soporte de tarifas derivadas por porcentaje
- create_solicitud.php guarda nit_empresa, subgrupo_id, autorizacion,
  diag_ppal, items_precio (snapshot JSON al momento de la recepción)
- Migración 10: ALTER turnero_solicitudes añade los 5 campos nuevos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:09:26 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b9bb4fc628 feat(lab_empresas): módulo CRUD de empresas, convenios y tarifas
- Nuevo módulo lab_empresas: lista paginada, modal create/edit empresa,
  gestión de subgrupos inline y CRUD de catálogo lab_tarifas_id
- APIs: empresas.php (list/get/save/toggle/delete),
  empresa_subgrupos.php, tarifas_id.php
- Migración 09 registra el módulo en system_modules y asigna permiso admin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:05:14 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5fedd233f4 feat(lis): migración completa Firebird→MySQL + tracking de muestras en turnero
- Migraciones LIS 01-08: schema completo del nuevo LIS (secciones, protocolos,
  ítems de resultado, perfiles, tarifas, empresas, histórico transaccional)
- ETL Firebird→MySQL: script CLI con conversión WIN1252→UTF-8, batches de 500,
  resolución de FKs y deduplicación de pacientes
- turnero_muestras: tracking pendiente/recibida/rechazada por tipo de tubo
- lugar.php: widget de recepción de muestras (solo tipo=muestras)
- update_muestra_estado.php: API para marcar estado de muestra
- create_solicitud.php: auto-crea muestras al guardar solicitud
- get_consentimientos.php: incluye muestras[] en el response
- 6 vistas SQL: v_muestras_hoy, v_recepcion_completa, v_examen_precio, etc.
- numero_orden en encabezado del formulario firmado (D-/F- color diferenciado)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 21:44:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f8245b4d6f feat: modo embebido de formulario por lugar (link vs iframe)
- Nueva columna turnero_lugares.formulario_modo (link|embebido)
- Config: radio en modal de edición para elegir el modo
- lugar.php: muestra iframe con ver_formulario_enviado al activar turno
- API get_consent_token.php: crea/obtiene token para turno+lugar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 11:35:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d3826972ad feat(turnero): selector de médico tratante en recepción, visible en toma de muestras
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:55:32 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3b378c0d75 feat(medicos): seed 444 médicos + columnas telefonos/email en tabla y modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:51:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 48f9e32c22 chore(migrations): script para correr migraciones pendientes de una vez
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:48:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 bb6ee12e0a fix: restaurar migraciones del turnero perdidas por force push
- 20260626_activar_numero_turnero.php
- 20260626_turnero_canal_conversations.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:43:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1574f12275 feat(medicos): módulo CRUD de médicos con tabla, búsqueda y modal edición
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:42:36 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 34004b4d98 feat(turnero): opción solo entrega de muestras con prioridad y badge naranja en cola
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:07:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 76dda4722a feat(turnero): pago combinado con desglose por efectivo, transferencia y tarjeta
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 00:02:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c9e5744de4 feat(turnero): campo embarazada en recepción y badge en toma de muestras
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 23:48:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1342d305ec fix: corregir migraciones fallidas, redis doble carga y cache de assets
- Eliminar .env del tracking de git (credenciales no deben ir en repo)
- Agregar .env y .env.* al .dockerignore (no hornear credenciales en imagen)
- Quitar extension=redis.so duplicada en php.ini
- Activar cache 7d para assets estáticos en nginx
- Corregir 5 migraciones: eliminar INSERT INTO migrations con columna incorrecta
  y remover semicolons en comentarios -- que partian el parser SQL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 19:24:41 -05:00
Lizandro Guarnizo 8d577947a6 up 2026-06-18 09:35:17 -05:00
Lizandro Guarnizo c58998599b up 2026-06-16 19:54:04 -05:00
Lizandro Guarnizo 8128616b63 up 2026-06-16 19:09:59 -05:00
Lizandro Guarnizo ff8454fa5a up 2026-06-16 16:55:22 -05:00
Lizandro Guarnizo a4a062095e up 2026-06-16 16:35:12 -05:00
Lizandro Guarnizo 5af94e50e5 up 2026-06-16 13:04:59 -05:00
Lizandro Guarnizo 415ea3ef56 up 2026-06-16 12:06:26 -05:00
Lizandro Guarnizo 64f21b083a up 2026-06-13 13:24:49 -05:00
Lizandro Guarnizo 375cba86eb up 2026-06-11 23:11:32 -05:00
Lizandro Guarnizo ca964c76a3 up 2026-06-11 22:52:45 -05:00
Lizandro Guarnizo e7820c7c2d Update 003_turnero.sql 2026-06-11 20:57:41 -05:00
Lizandro Guarnizo 313e57aace p 2026-05-27 10:30:47 -05:00
Lizandro Guarnizo 0de3e6e281 up 2026-05-11 14:55:17 -05:00
Lizandro Guarnizo 36f9524c48 Update 20260422_turnero_lugar_consentimientos.sql 2026-04-22 20:59:00 -05:00
Lizandro Guarnizo 076fae7857 up 2026-04-22 09:03:43 -05:00
Lizandro Guarnizo 63da89c31a up 2026-04-21 18:13:53 -05:00
Lizandro Guarnizo f1e81002de Update 005_registro_exams.sql 2026-04-19 21:07:39 -05:00