Commit Graph
197 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Opus 5 6589503c57 Tablet de firma para el paciente en Recepción 4
Hasta ahora, para que el paciente firmara el consentimiento de bienvenida
había que girarle el monitor a la recepcionista o pasarle el mouse: el botón
Firmar abre el formulario en la pantalla de ella.

Se agrega una pantalla aparte (/erp.php?m=turnero&v=firma) para poner frente
al paciente. Sola, sin que la recepcionista haga nada: cuando el turno entra
a ese puesto y le falta el F-LAB-01, aparece su nombre y un botón grande de
firmar; al terminar dice gracias y vuelve a reposo.

Reutiliza lo que ya existía: el formulario de ver_formulario_enviado.php, el
aviso 'turneroFirmado' que ya emite al firmar, y el sondeo de recepción, que
seguirá poniendo el renglón en verde sin cambios.

Sobre el acceso: va en PUBLIC_ROUTES porque nadie va a iniciar sesión cada
mañana en una tablet que manipula el público. No queda abierta: se identifica
por la cookie del dispositivo, y sin ella no muestra ningún dato. El endpoint
devuelve solo el turno que está en ese puesto en ese instante —no permite
buscar, ni ver otros, ni consultar historial— y descarta turnos de días
anteriores, igual que el televisor. La tablet debe quedar en modo kiosco.

Falta registrar el dispositivo de Recepción 4 desde Configuración; hoy no
tiene ninguno.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:35:20 -05:00
Lizandro GuarnizoandClaude Opus 5 2a87f66bb7 Pantalla TV: dejar de llamar a pacientes que ya se fueron
Reportan que a veces llama a otro paciente. Sí ocurre, y hay 26 turnos sin
cerrar en estado "en servicio" desde el 28 de julio, varios de ellos dos y
tres en el mismo puesto a la vez.

La pantalla pregunta por cada puesto cuál turno está en servicio y se queda
con el más reciente. Mientras hay uno real todo va bien, pero al cerrarse
ese, el puesto queda con el viejo como único en servicio y sube al panel como
si lo estuvieran llamando. Peor: al cargar, la pantalla anunciaba POR VOZ el
turno que encontrara arriba, así que cada recarga —y se recarga sola— podía
gritar el nombre de alguien que se fue hace días.

- Al abrir ya no se anuncia nada: los turnos presentes se dan por vistos y
  solo se anuncia lo que pase de ahí en adelante. Mostrar el estado actual es
  correcto; declamarlo como si fuera un llamado nuevo, no.
- Un turno vale hasta la medianoche de su día, tanto en recepción como en los
  puestos. De los 26 atascados, 25 dejan de aparecer; queda el único de hoy.

El filtro es una red, no la solución: esos 26 turnos hay que cerrarlos, y
mientras existan siguen inflando el "en proceso" del dashboard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 16:19:47 -05:00
Lizandro GuarnizoandClaude Opus 5 813b712df0 Motivo al marcar ausente, y notas visibles con su origen en dashboard e historial
Marcar ausente solo pedía confirmación: el turno quedaba registrado sin ninguna
explicación, y hoy son 6 en un día. Ahora se pide el motivo y se guarda como
comentario de recepción, no como columna nueva: así aparece en todas las
pantallas que ya muestran comentarios, sin duplicar el dato ni tocar el esquema.

Dashboard e historial mostraban las notas sin indicar de dónde venían, así que no
se distinguía una observación de toma de muestras de una de recepción. Se agrega
la insignia de origen con los mismos colores que ya usa la bandeja, y el título
pasa a "Notas y observaciones" con su cantidad.

El historial además traía solo las 3 últimas por turno y en orden inverso. Con
recepción empezando a dejar notas eso habría escondido justamente el motivo de
una ausencia sin que nada indicara que faltaban; ahora trae hasta 20, en orden
cronológico.

De las 114 notas existentes, todas son de toma de muestras: recepción nunca dejó
ninguna porque no tenía dónde hacerlo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:37:33 -05:00
Lizandro GuarnizoandClaude Opus 5 74e219e2ae LIA consulta solo el manual de usuario, y descarta coincidencias irrelevantes
El asistente está para ayudar a usar el sistema, no a mantenerlo: la
documentación técnica, de arquitectura y de operación queda fuera de su
contexto incluso para administradores, que pueden leerla directamente en el
módulo. El filtrado por rol sigue aplicándose dentro del manual.

Al restringirlo apareció que la relevancia era débil: una pregunta sobre
respaldos devolvía Recepción y Kiosko porque palabras comunes como "paciente"
suman igual en todos los documentos. Ahora cada palabra pesa según en cuántos
documentos aparece, y se descartan los resultados que no superen un umbral
absoluto ni queden cerca del mejor. Una pregunta fuera del manual devuelve
cero fuentes y LIA lo dice, en vez de responder con lo que tenga a mano.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 12:00:04 -05:00
Lizandro GuarnizoandClaude Opus 5 fe96e5b60d Documentación en usted, y panel de LIA con el mismo diseño del dashboard
Toda la documentación pasa de voseo a tratamiento de usted, incluidos los
diagramas y los textos de la interfaz. Los prompts de ambos asistentes lo
piden explícitamente, para que las respuestas generadas también lo respeten.

El panel de LIA en la página de documentación adopta el diseño del dashboard
del turnero: mismo botón circular, mismo panel deslizante con encabezado en
degradado y logo LIA, mismos chips de atajo, burbujas y campo de entrada.
Los atajos se arman con los documentos que ese usuario puede ver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 10:57:28 -05:00
Lizandro GuarnizoandClaude Opus 5 71c9dfd424 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>
2026-08-04 10:49:02 -05:00
Lizandro GuarnizoandClaude Opus 5 81c007c516 LIA responde sobre la documentación, filtrada por el rol de quien pregunta
DocIndex::contextoIA() selecciona los documentos relevantes a la pregunta y
devuelve su texto. Reutiliza el filtrado por rol ya existente: al contexto que
se envía al modelo solo entra lo que ese usuario podría leer por su cuenta en
el módulo Soporte, así el asistente no puede revelar contenido restringido.
Verificado: un recepcionista preguntando por permisos recibe solo el manual
básico, sin el SQL ni los detalles internos que sí recibe un administrador.

- services/GeminiService.php concentra la llamada a la API y la contabilidad
  del presupuesto, que antes vivía dentro de ai_chat.php y ahora comparten los
  dos asistentes.
- La LIA del turnero suma la documentación a su contexto operativo, así responde
  tanto "cuánto facturamos hoy" como "cómo marco un paciente ausente".
- modules/soporte/api/ai_docs.php es el asistente de la documentación: no accede
  a datos de pacientes ni de la operación, solo a los documentos visibles. A
  diferencia del anterior no exige acceso al turnero, así que lo puede usar
  cualquier usuario autenticado desde la página de documentación.
- La respuesta cita de qué documentos salió.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 10:41:40 -05:00
Lizandro GuarnizoandClaude Opus 5 d79d33db36 Facturación del día: finalizados vs en proceso, en vez de extrapolación lineal
La proyección estimaba el cierre del día multiplicando el ritmo promedio por
las horas que faltaban hasta un cierre fijo a las 17:00. Eso sobreestimaba
(el laboratorio concentra las tomas en la mañana por el ayuno), dependía de
una hora de cierre escrita a mano, y en fechas pasadas usaba date('G') —la
hora actual real— así que proyectaba facturación futura sobre días ya
cerrados.

Ahora son dos cifras concretas, sin estimar: Facturado = turnos finalizados;
En proceso = turnos aún activos, cuyo total_cobrado ya está registrado pero
no han cerrado. Ausentes y cancelados quedan fuera de ambos. La gráfica por
hora usa la misma separación.

Hoy: $22.779.145 en 92 finalizados + $667.000 en 2 en proceso (antes
proyectaba $28.740.490).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:08:28 -05:00
Lizandro GuarnizoandClaude Opus 5 25a099c6ef LIA: historial de conversación y prompt que no fuerza brevedad
Cada pregunta se enviaba sola, sin los intercambios previos, así que un
seguimiento del tipo "¿y de esos cuántos en la mañana?" no tenía contexto.
Ahora el dashboard acumula los últimos 4 intercambios y los envía; el
backend los valida y antepone a la pregunta actual (recortados para no
inflar el consumo).

El prompt pedía "responde de forma concisa" en todos los casos, lo que
sumado al tope de tokens producía respuestas cortadas. Ahora ajusta el
detalle a lo que pide la pregunta y sabe que tiene historial disponible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 15:03:43 -05:00
Lizandro GuarnizoandClaude Opus 5 b1a8b1abf8 LIA: subir maxOutputTokens a 2048 y avisar cuando Gemini corta la respuesta
El límite de salida estaba en 600 tokens (~450 palabras), que truncaba a
media frase las respuestas largas — listados de turnos del día, comparativas
por bacteriólogo. Gemini Flash admite hasta 8192; 2048 cubre esos casos sin
inflar el consumo del presupuesto.

Además el código nunca leía finishReason, así que una respuesta cortada por
MAX_TOKENS llegaba al usuario indistinguible de una completa. Ahora se marca
en el texto y se expone el flag `truncada` en la respuesta JSON.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:58:45 -05:00
Lizandro GuarnizoandClaude Opus 5 f6d4ee9bdd Capturar firmante de cada toma en el servidor; igualar render del modal en historial
guardar_toma.php (única ruta de firma de F-LAB-28 Tomas Prolongadas) nunca
registró quién firmaba: solo guardaba el SVG por campo. De los 111 registros
del formulario, 0 tienen identidad del firmante y no hay traza en ninguna
tabla de auditoría, así que no es recuperable retroactivamente. Ahora el
nombre/cédula se resuelven desde la sesión en el servidor (adminId()) y se
guardan por toma — cada toma puede firmarla un profesional distinto, y esta
es la única fuente confiable. El intento anterior dependía de que el
navegador enviara window._profNombre.

historial.php abría el modal sin compact=1&zoom=1.1, que sí usa bandeja;
por eso el mismo documento se veía en lista larga en vez de la grilla con
tarjetas de toma.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:55:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e480e14974 Corregir fuga de tomas_detalle entre formularios del mismo turno
\$tomasDetalle solo se inicializaba dentro del if(\$esTomaProg), así que un
formulario que NO es de toma progresiva (ej. "Datos Toma de Muestras")
heredaba el arreglo del formulario anterior en el mismo turno (ej.
"Control de Tomas de Muestras Prolongadas") si ese sí lo era. Se reinicia
en cada vuelta del loop junto con tomas_total/tomas_firmadas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 14:39:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6254e45e8f Pantalla TV del turnero: playlist de videos e imágenes en vez de un solo video
Nueva tabla turnero_tv_media (tipo, url, orden, duracion_segundos) para
soportar múltiples videos/imágenes reproducidos en secuencia y en bucle.
El video que ya estaba cargado se migró como primer ítem de la playlist.

- save_tv_media.php / delete_tv_media.php / reorder_tv_media.php
  reemplazan a save_tv_video.php (eliminado).
- configuracion.php (tab TV): subida múltiple, lista reordenable por
  arrastre, duración editable por imagen.
- display_global.php: el <video> único se reemplaza por un reproductor
  JS que recorre la playlist — video hasta 'ended', imagen por su
  duración configurada, y vuelve a empezar al terminar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 14:33:05 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2ba13add34 Corregir Tomas Prolongadas: filtrado de secciones, firmante por toma, detalle en historial
Tres bugs relacionados en el formulario "Control de Tomas de Muestras
Prolongadas" (F-LAB-28), detectados en el turno E017:

1. El modal de documentos del historial abría el documento sin &embed=1,
   por lo que el filtrado de tomas irrelevantes (según examen seleccionado)
   nunca se aplicaba — se mostraban las 16 secciones posibles en vez de
   solo las 2-3 que aplicaban, con textos "Pendiente de firma" de más.

2. El guardado de firma en tomas progresivas usa un endpoint distinto
   (_guardarFirmaMP → guardar_toma.php) que no pasaba por el fix de
   nombre/cédula del firmante aplicado antes solo a _guardarFirmaPro.
   Como además una toma progresiva puede pasar por varios profesionales
   en el transcurso de horas, ahora se guarda identidad por-toma
   (campoId + '_pro_nombre'/'_pro_cedula'), no solo global.

3. El historial no mostraba quién atendió cada toma. get_consentimientos.php
   ahora expone tomas_detalle (etiqueta, hora, firmante) por consentimiento
   de toma progresiva, y el panel de documentos del historial lo renderiza
   en una tabla debajo del pill del formulario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 14:26:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ec552136bf Incluir muestras rechazadas al volver, resaltar servicio prolongado en historial
Las consultas de "muestras pendientes de visita anterior" solo incluían
estado='pendiente'; ahora también 'rechazada', así una muestra rechazada
en una visita aparece igual cuando el paciente vuelve, y se puede recibir
directo (sin el paso extra de revertir a pendiente primero).

En historial.php, la columna Servicio se resalta en rojo con ícono de
alerta cuando supera 20 minutos (SERVICIO_PROLONGADO_MIN), para detectar
turnos prolongados a simple vista.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 14:19:35 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 538ee8ad25 Vincular turnos por muestra pendiente y precargar Datos Toma de Muestras
El turno original no se modifica (queda finalizado igual que antes) — solo
se agrega trazabilidad: turnero_muestras.recibida_en_turno_id registra en
qué turno se completó una muestra que había quedado pendiente en otra
visita. Historial, bandeja y lugar.php ahora muestran un chip/botón "Turno
vinculado" en ambos sentidos (origen ⇄ seguimiento) con acceso directo.

Además, al abrir un formulario "Datos Toma de Muestras" pendiente para una
visita de seguimiento, aparece un botón para cargar las respuestas del
último formulario firmado de ese mismo paciente (sin firma ni identidad
del profesional anterior) — evita reescribir la historia clínica; la
firma y fecha siguen siendo las de hoy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 13:10:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8e1f46904c Registrar quién crea cada consentimiento del turnero para el pie "Enviado por"
turnero_consentimientos nunca guardó quién lo generó; el pie del documento
mostraba el nombre de quien tuviera sesión abierta al verlo, no de quien
realmente lo atendió (por eso siempre aparecía el mismo usuario). Se agrega
la columna creado_por, se llena en los 6 puntos donde se crea un registro
(recepción, envío por WhatsApp, anexar formulario extra, autocreación por
examen/lugar), y ver_formulario_enviado.php ahora prioriza ese dato real
sobre el usuario de la sesión actual. Los documentos ya firmados antes de
este cambio siguen sin ese dato y usan el fallback anterior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 12:50:41 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 aacf26168b Toma de muestras: mostrar exámenes de la visita anterior en muestras pendientes
Cuando el paciente vuelve a completar una muestra pendiente, el panel de
lugar.php ahora lista los exámenes de la solicitud original debajo del
badge "visita anterior", con la misma clase visual que el motivo de
rechazo (hereda color según estado: pendiente/recibida/rechazada).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 12:41:28 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fd8b83a588 Exigir Datos Toma de Muestras en solo_muestras; nombre+cédula del firmante en todos los formatos
get_consentimientos.php ya no salta la autocreación del formulario F-LAB-08
(Datos Toma de Muestras) cuando la visita es "solo entrega de muestras" —
solo se omitían los formularios por examen, los de lugar deben seguir
exigiéndose igual.

ver_formulario_enviado.php cargaba nombre/cédula del profesional firmante
solo dentro del turnero embebido; se amplía también a la firma directa
fuera de turnero (staff logueado). firmar_profesional.php (usado por los
formatos no-turnero) no recibía esos datos del frontend y se perdían;
ahora los guarda en datos_cliente igual que el flujo turnero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 12:24:58 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 86aea30f62 Historial turnero: usar el formulario real de tomas prolongadas (F-LAB-28)
El desglose anterior se basaba en turnero_muestras, que no representa el
formulario "Control de Tomas de Muestras Prolongadas" que el laboratorio
ya usa para tomas seriadas (curvas de glicemia, etc.). Ahora el doc-pill
del historial muestra "X/Y tomas" usando es_toma_progresiva/tomas_firmadas
que get_consentimientos.php ya calculaba, sin duplicar lógica.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 11:57:27 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0b8418ec76 Historial turnero: desglose de tomas de muestra y modal para documentos firmados
Muestra el detalle de cada toma (tipo, quien la recibió, tiempo) cuando un
turno tuvo varias, para explicar tiempos de servicio prolongados. Los
documentos firmados ahora se abren en un modal en vez de nueva pestaña,
preservando los filtros de búsqueda activos.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-03 11:44:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 006772f265 fix(encuesta): usar plantilla encuesta_turnero en lugar de encuesta
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 22:14:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 320b47797a feat(LIA): barra de tokens 1M con bloqueo al agotarse
- ai_chat.php: cuenta tokens reales desde Gemini usageMetadata, persiste en lab_config, bloquea con HTTP 402 al agotar 1.000.000
- dashboard.php: _actualizarTokens() actualiza barra visual, _bloquearLIA() deshabilita input+mic y muestra aviso de soporte

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 20:09:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 34d0fed21b feat(dashboard): LIA voz+saludo, proyección Chart.js, Excel, informe WA
- LIA: saludo de voz al abrir (1x/día), auto-escucha 3s, olas animadas
- Gráfica de barras (Chart.js) con facturación real + proyección lineal
- Exportar a Excel (.xls con HTML table, se abre directo en Excel)
- Botón "Informe WA": envía resumen del día por WhatsApp a número elegido
- Diseño accesible: texto más grande, botones claros para usuario adulto

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 20:03:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 941b6740f0 feat(recepcion): panel muestras muestra último turno atendido por letra
API: agrega por_prioridad con último código llamado a muestras por letra.
Vista: badges con color de prioridad mostrando "A: A-023", "B: B-011", etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 19:51:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 498a144c18 feat(historial): mostrar nombre kiosko y datos BD (nombre+doc) en paciente
- API: agrega tipo_documento y numero_documento al SELECT
- Vista: línea 1 = nombre BD + CC/doc, línea 2 gris = Kiosko: nombre

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 19:49:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1a3ff7219e fix(encuesta): leer language_code de message_templates igual que consentimiento
Reemplaza 'es_CO' hardcodeado por consulta a message_templates,
con fallback a 'es'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 19:45:00 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 44b737c253 feat(tiempos): agregar tiempo total puerta a puerta en historial y dashboard
- APIs: total_min = creado_at → fin_lugar_at (solo finalizados)
- Dashboard: KPI card 'Total prom. puerta a puerta' + columna Total en tabla
- Historial: columna Total en tabla

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 18:14:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c357f18b61 fix(resumen-muestras): obtenerOCrearSesionHoy() retorna int no array
Usaba ['id'] sobre un int → sesionId=0 → sin resultados.
Cambiado a sesionHoy() que sí retorna array con 'id'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 18:06:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 22ed64d8ad fix(bandeja): filtrar muestras rechazadas en vez de pendientes
- get_bandeja.php: contar estado='rechazada' (no 'pendiente')
- chip renombrado a "Muestras rechaz." con ícono fa-flask
- card muestra badge rojo con ícono y cantidad cuando hay rechazadas

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 18:04:23 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0e0dc4bd44 refactor(encuesta): usar paciente_cel directo igual que send_consentimiento
Elimina el lookup innecesario en users/$userId que nunca se usaba.
Ahora sigue el mismo patrón que recepción: BD telefono → paciente_cel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 17:57:36 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9a18184ad7 fix(encuesta): usar paciente_cel directamente si no está en tabla users
El paciente siempre tiene celular registrado; el error ocurría porque se
requería match en users. Ahora usa paciente_cel como fallback final.
Revierte la guarda del frontend del commit anterior.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 17:55:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 25bdf9aed2 fix: resetear toma limpia siguiente_toma_at y toma_inicio_at
Al olvidar una toma los tiempos del ciclo anterior quedaban en la DB,
haciendo que al re-firmar la toma reseteada las demás aparecieran
inmediatamente (sin countdown) y el profesional tuviera que firmar 3
veces en vez de 1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 16:04:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2455417bb4 feat: encuesta de satisfacción post-finalizar toma de muestras
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:23:07 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c85181ad56 feat: mostrar nombre y cédula del profesional bajo la firma
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:10:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4fee7f753a fix(turnero): anexar_formulario usa UUID() en vez de bin2hex para el token
bin2hex(random_bytes(16)) genera hex sin guiones que no pasa la validación
de ver_formulario_enviado.php — el formulario adicional daba token inválido
al intentar firmar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:50:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fcfdcccba9 fix(consentimiento): resetear firma conserva datos_respuestas para re-firma
El usuario puede modificar y volver a firmar sin perder lo llenado.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:49:23 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 00f6e7f759 fix(historial): default hoy en vez de 7 días para evitar timeout inicial
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:30:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e3216faaa5 feat(bandeja): chips clickeables para muestras pend. y prog. en espera
- Chip "Muestras pend." y nuevo "Prog. en espera" son toggles de filtro
- Se resaltan con borde al activarse; combinables entre sí y con el dropdown
- API retorna muestras_pendientes (turnos con ≥1 muestra pendiente) y
  prolongadas_pendientes (toma progresiva en espera entre extracciones)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:29:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 118e98b360 feat(bandeja): filtros por fecha, nombre, # orden y estado
- Inputs desde/hasta pasan al API y filtran por rango de fechas
- Buscador cubre nombre, número de orden, código y examen
- API usa fecha cuando se pasa, sesión actual como fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:22:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4cb133010a feat(bandeja): filtro de muestras pendientes
Añade conteo de muestras_pendientes por turno en la lista y opción
de filtro en el selector de estados de la bandeja del día.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:21:06 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7d444699d3 feat(turnero): panel de estado de muestras en recepción
Recepción ve en una barra inferior qué turno atiende cada estación de
toma de muestras (verde = ocupada, gris = libre), cuántos están en
espera y cuántos en espera entre muestras progresivas. Se refresca cada 7 s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 11:59:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8472c916b0 fix(turnero): llamar_desde_espera actualiza lugar y dispara display
Al llamar un paciente de toma progresiva desde la espera, ahora se
actualiza lugar_destino_id al puesto que llama, se pone llamado_lugar_at
para que el display anuncie al paciente, y se notifica por SSE. El JS
abre la ficha y muestra el toast igual que un llamar normal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 11:49:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 925b29b00b fix(turnero): incluir solo_muestras en llamar_turno y bypass consent para entrega
- llamar_turno.php: ambos SELECT de refresco hacen LEFT JOIN con
  turnero_solicitudes para devolver COALESCE(ts.solo_muestras,0).
  Sin este JOIN, JS recibía undefined y _esSoloEntrega() retornaba
  false → polling de consentimientos arrancaba → hayPendientes=true
  → finalizar bloqueado (caso turno E047 ALVARO TIRADO).
- lugar.php renderConsentimientos: hayPendientes se fuerza a false
  cuando _esSoloEntrega(turnoActivo) es true, para que un
  consentimiento huérfano previo no bloquee la finalización en
  turnos de solo entrega de muestras.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 09:20:56 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 dffb242522 perf(historial): reemplazar N+1 queries por 3 batch queries
Las 3 subqueries por turno (examenes, consentimientos, comentarios)
× 50 turnos = 150 round-trips al servidor remoto (~83ms c/u) sumaban
12.5s de espera, causando timeouts o página sin carga.

Ahora se agrupan en 3 queries con IN(?) sobre todos los IDs del lote.
Tiempo total: ~357ms (35x más rápido).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 08:54:04 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 afa7cc6812 feat(turnero): anexar formulario adicional a turno desde lugar.php
Permite al operador agregar un consentimiento informado extra (fuera de
los asignados por defecto) mientras el turno está en servicio.

- PHP: consulta lab_formularios activos al cargar lugar.php
- JS: modal de selección con lista de formularios
- Botón visible solo cuando turno está en_servicio, oculto en resetFicha
- API: modules/turnero/api/anexar_formulario.php crea el consentimiento
  y notifica SSE para que el dispositivo lo vea inmediatamente
- Fix: _olvidarConsentimiento usaba cargarConsentimientos() inexistente;
  corregido a actualizarConsentimientos(turnoActivo.id)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 07:49:06 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ff52fa5ba7 feat: olvidar firma, editar tiempos tomas, obs en header, fix dispositivos
- feat(turnero): Botón "Olvidar" en lugar.php y recepcion.php para descartar firma de consentimiento completo (nuevo endpoint resetear_consentimiento.php)
- feat(turnero): Botón "Olvidar" por toma individual en prolongadas para borrar firma+hora de una toma específica (nuevo endpoint resetear_toma.php)
- feat(turnero): Editar tiempos en tomas en_progreso: hora_campo signed muestra readonly con botón olvidar, permite re-editar tras olvidar
- feat(turnero): Guardar tiempos (draft save) también en estado en_progreso
- feat(turnero): Observaciones de tomas prolongadas se muestran en header de tarjeta firmada (campo obs_campo en _mpMap)
- fix(turnero): Token del equipo se sobreescribía en tabla — ip permitía solo un registro con ip='' (UNIQUE); ahora ip es nullable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 07:37:09 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a08e2002c4 chore: modelo Gemini 2.5-flash → 3.5-flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 01:11:14 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f5ef28e2c7 chore: actualizar modelo Gemini 2.0-flash → 2.5-flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 01:09:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ab8b0702a7 fix: handle nested _tomas_config format in guardar_toma.php
PHP 8.1+ throws TypeError when using an array as array key.
Old flat-format loop iterated nested { examName: { group: [fids] } }
and tried $allowed[$arrayValue] = true, causing a fatal error and
returning ok:false ("Error al guardar") on every toma signing attempt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 00:48:34 -05:00