Commit Graph
1134 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 e1738d3fa4 feat(LIA): botón minimizar visible con ícono — / ▲ en header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 20:19:39 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f2ce60137a fix(LIA): inicia con sonido silenciado por defecto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 20:16:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4010e6e6c6 fix(LIA): voz femenina en español y quitar emoji de saludo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 20:12:18 -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 a8752b9e9d feat(dashboard): asistente IA con voz, markdown y preguntas rápidas
- Audio input: botón micrófono con SpeechRecognition (es-CO)
- Audio output: TTS con SpeechSynthesis, toggle para silenciar
- Markdown: **negrita** → <strong>, * items → <ul> en respuestas
- Chips de preguntas rápidas: Resumen, Facturación, Tiempos, En espera
- Panel minimizable: clic en header colapsa/expande el cuerpo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 19:55:41 -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 fb448d2c2f fix(bandeja): labels de muestras de vuelta a 'pendientes' en UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 18:04:50 -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 a4ddc63b6f fix(encuesta): ocultar barra si paciente no tiene celular registrado
Si turnoActivo.paciente_cel está vacío, _ofrecerEncuesta retorna sin
mostrar la barra, evitando el error de WA al intentar enviar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 17:55:10 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 90e3125e45 feat(historial): mostrar nombre kiosko y nombre BD en columna paciente
Si el nombre registrado en BD difiere del ingresado en kiosko, muestra
ambos: el BD como principal y el kiosko en gris debajo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 17:52:25 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7e44fd4050 fix(historial): corregir SyntaxError por backtick/backslash en renderTabla
- esc() ahora escapa backticks (&#96;) para evitar que nombres de paciente
  con backticks rompan el template literal
- onclick simplificado: eliminado .replace(/'/g,\\'\\\\\\'\\'') que era JS
  inválido (backslash fuera de string literal dentro de ${}); esc() ya
  convierte comillas simples a &#39;

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-30 16:43:44 -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 90d4e1bc49 fix: nombre profesional bajo firma firmada; ver paciente desde espera toma
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:32:03 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 309c1c93c6 fix: nombre y cédula del profesional en cada toma de muestras prolongada
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:24:32 -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 4a4cbc31b8 fix: permitir guardar borrador en estado enviado desde turnero
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:16:11 -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 806ae0b552 fix: colapsar márgenes !important de secciones condicionales ocultas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 21:01:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8a384088c3 fix: radios con 3 opciones span full en modal compact
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:55:13 -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 bf8478244b fix(formulario): reducir espacios en blanco en modo compact del modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:44:36 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8937a46362 fix(formulario): radio options en fila horizontal — override display:flex + fix form_cliente
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:42:29 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 7bb69f6efd fix(formulario): opciones de radio en fila horizontal en vez de vertical
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:40:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ad5a4c834f fix(lugar): alarma de toma progresiva suena en panel de espera entre muestras
El timer de 1s solo escaneaba chips en la cola principal. Los pacientes
en espera entre extracciones (muestra_espera_at IS NOT NULL) viven en el
panel de espera sin chip, por lo que nunca sonaba. Se añade un chip oculto
con data-toma-at en cada fila del panel; el timer existente lo detecta y
dispara banner + sonido en todas las tablets del grupo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:34:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2da5960596 fix(bandeja): default hoy + etiqueta Hoy/Ayer/fecha en cada registro
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-29 20:32:08 -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 2e3534ab02 feat(bandeja): filtro por estado en la bandeja del día
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 12:02:37 -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 7b599b28ef fix(turnero): bacterióloga sin token va al dashboard, no a lugar_id=1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 11:55:30 -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 3d26700e16 feat(recepcion): mostrar contador de exámenes seleccionados
Badge junto al título "Exámenes solicitados" que muestra cuántos
exámenes lleva el turno actual. Se actualiza en tiempo real al
agregar o quitar ítems del TomSelect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 09:41:06 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fa591873a9 fix(auth): redirigir admins/supervisores al dashboard del turnero tras login
login.php y index.php enviaban a v=recepcion a cualquier usuario con el
módulo turnero que no fuera bacteriólogo, incluyendo superadmin, admin
y supervisor. Esto causaba que al cerrar sesión y volver a entrar, el
admin aterrizaba en recepción en vez del dashboard.

Ahora:
- bacteriologo → lugar_id=1
- superadmin/admin/supervisor → v=dashboard
- recepcionista/otros → v=recepcion (comportamiento previo correcto)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 09:30:38 -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 70fda3dacc fix(turnero): dos correcciones de estado y redirección
1. App::bacteDefaultUrl() ahora busca también por token cookie (antes
   solo buscaba por IP), consistente con login.php y lugar.php.

2. cambiarEstadoTurno(): cuando el servidor devuelve 422 (transición
   inválida porque otro operador cambió el estado), muestra mensaje
   claro y refresca la UI en lugar de mostrar el error técnico bruto
   "Transición 'en_recepcion' → 'finalizado' no está permitida".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 08:40:49 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 88fd0e4151 fix(turnero): login detecta dispositivo por token antes de buscar por IP
La detección de tablet fija solo buscaba por IP; tablets con token
configurado no hacían match y caían al redirect de rol (recepcion).
Ahora busca primero por cookie turnero_token y como fallback por IP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 08:16:37 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 e41663cff0 fix(turnero): bacteriologo inicia en lugar_id=1 en lugar de dashboard
El rol bacteriologo iba a v=dashboard al hacer login. dashboard.php solo
redirige automáticamente si la IP está registrada como dispositivo; sin
IP registrada, la bacterióloga se quedaba en el dashboard sin usar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-28 08:14:54 -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