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>
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>
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>
- 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>
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>
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>
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>
- 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>
- getVozES() prefiere voces locales (offline) sobre Microsoft Online que
fallan silenciosamente sin internet o con latencia
- cancel() antes de speak() para limpiar cola atascada
- keepalive sin check !speaking, cada 10s
- sonidoActivo persiste en localStorage; banner rojo si no activado
- AudioContext se resume si suspended antes de reproducir
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- DB: añade estado 'cerrado_anticipado' al enum de turnero_consentimientos
- ver_formulario_enviado.php: POST handler mp_cerrar_anticipado guarda motivo
en _cierre_anticipado y cambia estado; botón "Cerrar anticipadamente" con modal
textarea; bloque display para estado cerrado_anticipado mostrando el motivo;
modoEditar y check de estado actualizados para excluir cerrado_anticipado
- get_consentimientos.php: expone cierre_anticipado en respuesta; mueve parse
de datos_respuestas fuera del if(esTomaProg) para ambos loops
- lugar.php: CONSENT_IC/LBL incluye cerrado_anticipado; ya[] lo incluye;
hayPendientes lo excluye; fila muestra motivo en banner amarillo
- recepcion.php: CONSENT_META incluye cerrado_anticipado; ya[] lo incluye;
motivo visible en la tarjeta del consentimiento
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- configurar_tomas.php: almacena _tomas_config como { examName: { groupKey: [fids] } }
en vez de flat, detecta y resetea formato antiguo, valida exam_type requerido
- ver_formulario_enviado.php: rendering aplana nested→flat para filtrar secciones;
JS lee _mpTomasConfig[examName][gk] en inSaved y _mpRenderBadges; auto-open
verifica per-examen; _mpActiveConfigExam evita race entre exámenes simultáneos
- get_consentimientos.php: flatten de nested o flat para calcular allowedFids
en ambos loops de tomas progresivas
- F-LAB-08 (id=16): añade 'Grupo sanguíneo' a opciones de _m5n865g;
agrega campos condicionales '¿Qué tipo de cáncer?' y 'Especifique grupo sanguíneo'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Al guardar config de un examen se sobreescribía _c8j2g16 con solo ese examen,
perdiendo los demás en el reload. Ahora se envía exam_types (array completo),
se guarda como array en datos_respuestas y sessionStorage restaura todos los
checkboxes marcados al recargar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Antes solo aparecían pacientes con inicio_lugar_at IS NOT NULL (ya llamados).
Ahora incluye estado IN ('en_espera_lugar','en_servicio') para mostrar
pacientes enviados a ginecología/pediatría antes de ser llamados.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- configurar_tomas.php: merge config por examen en lugar de overwrite
- ver_formulario_enviado.php: auto-abrir panel de tiempos al cargar; soporte formularios solo_profesional con botón Guardar y completar
- get_consentimientos.php: leer campo solo_profesional de lab_formularios
- recepcion.php: formularios sin firma paciente muestran badge "Completa en laboratorio"
- lugar.php: botón "Completar" para formularios solo_profesional
DB: ALTER TABLE lab_formularios ADD solo_profesional; UPDATE Rubeola id=8; UPDATE descripcion Niños kiosko
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB: columna muestra_espera_at en turnero_turnos
- Panel 'Esperando siguiente muestra' en la cola de lugar.php
· Muestra pacientes en espera con countdown a siguiente_toma_at
· Botón 'Llamar' los devuelve a la cola activa
- Botón 'Mandar a espera' en la ficha (visible cuando toma progresiva activa)
· Desaparece de la cola principal, queda en sección de espera
- get_cola.php: excluye muestra_espera_at IS NOT NULL de cola y activo
· Agrega array en_espera_muestra en la respuesta
- Nuevas APIs: poner_en_espera_muestra.php, llamar_desde_espera.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sección en la parte superior de lugar.php muestra pacientes esperando
en Ginecología y Pediatría (inicio_lugar_at IS NOT NULL, fin_lugar_at IS NULL)
- Chips de acceso rápido (botones) al tope de la cola para navegar directo
- Contadores de espera actualizados cada 30s
- Clicking en un paciente o "Ir →" navega a esa vista completa para atenderlo
- Solo aparece si existen esos lugares en DB y hay pacientes esperando
- Nuevo: get_especialidades.php para consultar pacientes de lugares especiales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Muestra que el paciente trae después no se muestra como rechazo.
Colores rojo → naranja, botón/etiqueta/íconos actualizados en lugar.php y bandeja.php.
DB/API internos sin cambio (enum 'rechazada' se mantiene).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Consentimientos abren en modal iframe (ver_formulario_enviado.php?embed=1)
- Igual que en toma de muestras: spinner de carga, click fuera para cerrar
- Badges de comentarios (Recep./Toma/Gral.) y estado (Finalizado/En servicio)
confirmados visibles en tarjeta
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Indicadores por tipo: Recep. (azul) / Toma (verde) / Gral. (amarillo)
- Label de estado del servicio: En servicio / En espera / Finalizado / etc.
- Query agrega SUM por tipo de comentario sin subquery adicional
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reemplaza SELECT MAX() (vulnerable a concurrencia) por UPDATE atómico sobre
turnero_consecutivos. Dos recepcionistas simultáneas nunca pueden recibir
el mismo número de orden. Sin UNIQUE constraint, sin riesgo en datos históricos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reemplaza activos/cerrados por pendientes (por revisar) / vistos
- Columna bandeja_visto_at en turnero_turnos; API marcar_visto.php
- Botón "Marcar como visto" → mueve paciente a sección inferior
- Filtro en tiempo real por nombre y por código de examen (GROUP_CONCAT)
- URL de consentimientos: relativa (consent.php?token=...)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Vista split: lista ordenada por numero_orden (activos arriba, cerrados abajo),
stats bar con totales, panel de detalle con exámenes por categoría, muestras,
consentimientos, comentarios, PDF via window.print() y acción de cerrar turno.
Auto-refresh cada 30s. Visible para bacteriólogos y admins.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Agrega botón de cambio de estado en cada fila del historial de turnos.
Admins (sin role_id) pueden reabrir turnos finalizados/ausentes/cancelados.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zoom en el elemento hijo encogía 100vh dejando espacio vacío abajo.
En html el browser recalcula vh correctamente y el layout llena la pantalla.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Los nombres en MAYÚSCULAS son deletreados por el motor de voz.
Convertir a Title Case para que se lean como palabras normales.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Elimina speechSynthesis.cancel() para que los anuncios no se interrumpan.
El overlay espera a que el TTS termine (onend) antes de pasar al siguiente,
con un mínimo visual de 5 segundos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Muestra "Hoy", "Ayer" o la fecha entre grupos de mensajes de días distintos.
El timestamp del bubble ahora solo muestra la hora.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sonido más fuerte (gain 1.0, 3 pulsos grave-agudo), se repite cada
30 s mientras el banner no se cierre con click.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El Tab es la señal de fin de cédula en el barcode. Al recibirlo se
activa _scannerLocked y se ignoran todos los caracteres siguientes
(apellidos, fecha, sangre) hasta que llegue Enter o se limpie el campo.
También se redujo el numpad ~10% para que entre en pantalla.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restaura campo WhatsApp (inputmode=none), mueve el numpad debajo.
El numpad detecta cuál input tiene foco y escribe en ese campo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El campo WhatsApp generaba fricción innecesaria. Ahora el numpad
tiene un botón "Obtener mi turno" de ancho completo como última fila.
WhatsApp queda como hidden vacío para no romper la API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- inputmode=none en inp-cedula suprime el teclado Windows
- Grid 3×4 con dígitos 0-9, C (borrar todo) y ⌫ (borrar uno)
- pointerdown + preventDefault mantiene el foco en el input
- El escáner sigue funcionando (escribe por keydown, no por IME)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Navegar al ERP dentro del kiosk no sirve porque --kiosk es un flag
del proceso. window.close() cierra Chrome y devuelve el escritorio
Windows táctil, desde donde pueden abrir el ERP normalmente.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Toca el logo o el nombre del laboratorio 7 veces en 4 s para abrir
el overlay de salida: ir al ERP, recargar kiosko o cancelar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>