- Tabla lab_ciudades creada y poblada con 86 ciudades existentes
- Módulo lab_ciudades: listado, crear, editar, activar/desactivar, eliminar
- API /api/lab/ciudades.php: GET list, POST save/toggle/delete
- Modal paciente: campo ciudad cambiado a select, cargado junto con EPS
en un solo Promise.all al iniciar; Cúcuta preseleccionada por defecto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tabla lab_eps creada y poblada con valores existentes de lab_pacientes
- Módulo lab_eps: listado, crear, editar, activar/desactivar, eliminar
- API /api/lab/eps.php: GET list, POST save/toggle/delete
- Modal paciente en recepción: campo EPS cambiado de texto libre a select
cargado desde lab_eps al iniciar la página
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El kiosko guarda la cédula en paciente_nombre del turno. Al abrir el
modal de nuevo paciente, si el valor es numérico se pone en el campo
documento (no en nombre). El foco cae en nombre para que el recepcionista
complete los datos restantes. Campo editable normalmente.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sin empresa: usa precio_base de exam_tipos (tarifa particular)
- Panel de precios se muestra con solo seleccionar exámenes (no requiere convenio)
- Total se aplica automáticamente al campo de cobro sin clic manual
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Con video activo: layout pasa de 3 columnas (37/39/24%) a 2 (1fr/29%).
El panel central (turno-activo) se oculta via CSS; left-area (listado)
absorbe el espacio liberado y el video sube de 24% a 29%.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Búsqueda automática con debounce 300ms al escribir (sin necesidad de lupa)
- Enter: si ya hay resultados selecciona el primero, si no dispara la búsqueda
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JSON.stringify(e) dentro de onmousedown="..." producía comillas dobles
que el parser HTML interpretaba como cierre del atributo, dejando la
función con un objeto incompleto. Ahora los resultados se guardan en
_empresaResultados[] y el handler pasa solo el índice numérico.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Items seleccionados se muestran en filas verticales con chip de CUPS/código
- Búsqueda por CUPS funciona (código CUPS incluido en texto de <option>)
- Dropdown muestra chip de CUPS junto al nombre del examen
- PHP SELECT incluye columna cups de exam_tipos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El banner de alerta solo se cerraba vía 'Ver paciente'. Si el profesional
hacía clic en 'Siguiente toma' directamente, el banner seguía parpadeando
y sonando aunque el formulario ya estuviera abierto.
- abrirTomaProgresiva() ahora llama cerrarBannerToma() + _tomaAlertados.delete()
- renderConsentimientos() preserva alertado:true si la siguiente_toma_at no
cambió, evitando beep doble al re-sincronizar después de firmar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Panel ya no se renderiza server-side, sino que JS lo muestra al instante
cuando el usuario selecciona un tipo de examen del selector _c8j2g16
- _mpGetGroupsForExam() filtra los grupos correspondientes al examen elegido
- Si solo hay 1 toma por grupo, auto-guarda sin mostrar panel
- Si al cargar ya hay examen seleccionado y sin _tomas_config, muestra panel
- configurar_tomas.php: guarda también _c8j2g16 (exam_type) en datos_respuestas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Feature 1 — Ciclos configurables por paciente:
- ver_formulario_enviado.php: pantalla de configuración aparece la primera
vez que el profesional abre el formulario (antes de firmar); permite
seleccionar qué tomas realizar para cada protocolo. $_mpSkippedSepIds
oculta las secciones no seleccionadas en render. $_mpMap se reconstruye
desde los grupos filtrados para que next_firma_id salte tomas omitidas.
- configurar_tomas.php (nuevo): guarda _tomas_config en datos_respuestas
del consentimiento; valida que cada grupo tenga al menos una toma.
- guardar_toma.php: aplica el filtro _tomas_config al array firmasCampos
para que el conteo de tomas firmadas/pendientes sea correcto.
Feature 2 — Gestión de tipos de examen (admin):
- lab_tomas_config.php (nuevo): página admin que muestra los exámenes
actuales con sus tomas y permite añadir nuevos tipos con su esquema
completo (separador + hora + resultado + firma_profesional por toma).
- api/lab/save_tomas_config.php (nuevo): modifica el esquema JSON del
formulario id=15; add_exam agrega opción al selector y los campos de
cada toma; delete_exam elimina la opción y los separadores exclusivos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Agrega script inline en <head> de Layout.php, lugar.php y recepcion.php
que detecta prefers-color-scheme y activa data-bs-theme="dark" en <html>
antes del primer paint (evita flash de contenido blanco)
- sidebar.php: script de fallback para páginas con head propio no actualizado
- Layout.php: corrige background hardcodeado #f8f9fa en .main-content y
reemplaza bg-white por bg-body-tertiary en barra de título
- styles.css: elimina bloque @media duplicado y contradictorio; nuevo bloque
unificado cubre @media (fallback sin JS) + [data-bs-theme="dark"] para
elementos custom (sidebar, chat); Bootstrap maneja el resto automáticamente
- Bump styles.css a v=15 en los tres templates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- guardar_toma.php: reconoce firma campo_id_svg (compat. registros históricos)
- get_consentimientos.php: mismo fix para conteo tomas_firmadas
- ver_formulario_enviado.php: $_mpMap incluye hora fija (Cortisol 3pm/4pm),
agrupa por condición de separador para no mezclar Glicemia/Cortisol,
muestra resumen de tomas al completar antes de cerrar el modal
- lugar.php: banner rojo + beep cuando el countdown de una toma llega a cero,
resetea alerta al cerrar el modal; función _playBeepLugar() compartida
DB Form 15: +10 campos (firma_profesional Minuto 60 Glicemia + campo
Resultado numérico en cada toma Glicemia y Cortisol)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- guardar_toma.php: filtrar firmas por condición del examen seleccionado; corrige
countdown espurio y detección de 'completado' cuando hay múltiples tipos de examen.
También descarta siguiente_toma_at si ya es pasado (minuto 0).
- ver_formulario_enviado.php: auto-scroll a primera firma pendiente al cargar el modal;
si el examen se selecciona y la firma no está en DOM, guarda y recarga el iframe.
Al completar tomas, cierra el modal directamente sin botón "Finalizar".
- get_cola.php: incluye siguiente_toma_at y en_toma_progresiva por turno desde
turnero_consentimientos para mostrar cuenta regresiva en tarjetas.
- lugar.php: muestra chip de cuenta regresiva en tarjetas de cola para pacientes
en toma progresiva; color cambia a warn (<2 min) y crit (¡Ahora!).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- get_consentimientos: tomas_total ahora filtra por examen elegido en
datos_respuestas (no cuenta todos los 13 campos globales); badge
se oculta (total=0) hasta que se seleccione un examen.
- ver_formulario_enviado: agrega f.es_toma_progresiva al SELECT y lo
usa para forzar _soloFirmaPro=true, evitando que aparezca el canvas
de firma del paciente cuando la DB indica toma progresiva.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ver_formulario_enviado.php:
- $_mpPrimeraPendiente, $_mpBloqueado y $fSvg buscaban la clave campoId_svg
pero guardar_toma.php guarda con clave campoId (sin sufijo). Se añade
fallback a ambas claves para compatibilidad hacia adelante y atrás.
lugar.php:
- new Date('2026-07-09 15:30:00') no parsea en Safari; se reemplaza el
espacio por 'T' para formato ISO antes de construir el timestamp.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _guardarFirmaMP ahora llama guardar_toma.php en vez del draft POST,
guardando la firma con clave campoId (sin _svg) que es la que lee
get_consentimientos para el contador X/13.
guardar_toma.php también actualiza estado→en_progreso y siguiente_toma_at,
lo que activa el countdown en lugar.php via polling SSE.
- Al completar la última toma envía postMessage turneroFirmado para cerrar
el modal automáticamente.
- Botón cerrar del modal siempre habilitado (el operador puede cerrar
en cualquier momento durante el proceso).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Agrega medico_nombre al __paciente prefilled de crear_envio_presencial.php
haciendo JOIN con medicos vía turnero_solicitudes.medico_id, de modo que
el campo linked 'Médico solicitante' del formulario id=15 (tomas prolongadas)
se auto-cargue con el médico registrado en recepción.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- get_consentimientos: skip auto-create of consentimientos when solo_muestras=1
- get_consentimientos: add paciente_id to turno query; fallback muestras fetch
when no solicitud exists but turno has paciente_id (F-turns without reception)
- lugar: badge "Solo entrega de muestras" in ficha header for prioridad F
- lugar: hide sec-consent and sec-form-embebido when solo_muestras
- lugar: skip consent polling and form embedding for F-turns
- lugar: restore sec-consent visibility on resetFicha
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Before creating a new turn, check if the patient (identified by
paciente_id) already has a turn in today's session that is not
finalizado/ausente/cancelado. Returns 409 with the existing turn
code so the receptionist or kiosko can show a clear message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a patient with a cedula is selected, the system queries RIPS
Manager for exams registered in the last 5 minutes. A green banner
shows the count with a "Cargar" button that pre-fills TomSelect.
Unmapped exam codes shown as a warning. Banner clears on deselect/reset.
- config.php: RIPS_MANAGER_URL constant from env var
- api/lab/get_examenes_rips.php: proxy + codigo_legacy mapper
- recepcion.php: banner HTML + consultarExamenesRips/cargarExamenesRips JS
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Toggling the checkbox hides the doctor field and clears any selection.
Unchecking or resetting the form restores the field to visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New Bono field in the combined payment panel — included in the
running total sum, reset, and pagos_detalle payload on submit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For recepcionista and bacteriologo roles, module.php now checks the
turnero_token cookie first — same order as recepcion.php — so token-
registered devices show only their assigned desk/lugar in the menu.
IP fallback now requires token IS NULL to avoid ambiguous matches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After pressing Enter to add an exam, the typed text is cleared
and the dropdown closes so the next code can be typed immediately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
onType highlights the first visible option on each keystroke so pressing
Enter immediately selects it — no need to arrow-down manually.
Also adds selectOnTab for the same effect with Tab key.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
En lugar de confiar solo en la sesión (seteada al login), recepcion.php
y lugar.php ahora consultan turnero_dispositivos por IP en cada request.
Esto cubre sesiones preexistentes y evita que aparezca el selector cuando
la IP ya tiene un lugar asignado.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- authenticateUser incluye turnero_lugar_id en la sesión
- login.php redirige con desk_id/lugar_id si el usuario tiene lugar fijo (sin necesitar IP)
- recepcion.php redirige con desk_id si la sesión tiene turnero_lugar_id, evitando el modal de selección
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Cuando la solicitud tiene solo_muestras=1, get_consentimientos.php
busca turnero_muestras pendientes de otras solicitudes del mismo
paciente y las antepone en el array muestras con es_pendiente_anterior=1.
lugar.php renderiza un tag 'visita anterior' en cada muestra previa
para que el bacteriólogo identifique el origen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El campo observaciones de la solicitud ya venía en el JSON de
get_consentimientos.php pero nunca se renderizaba en lugar.php.
Se agrega un bloque amber visible solo cuando sol.observaciones
tiene contenido, y se limpia al hacer resetFicha.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
llamarTurnoEspecifico usaba cambiar_estado.php sin pasar desk_id, por
lo que recepcion_desk_id quedaba NULL. El display TV filtra por
recepcion_desk_id = ?, así el turno llamado no aparecía en pantalla.
También se corrige llamado_recepcion_at: era COALESCE (no actualizaba
si ya tenía valor), ahora es NOW() para que el display reaccione al
re-llamar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El else branch de 3062cd7 marcaba todos los formularios de lugar con
origen_lugar_id=-1 cuando lugar_destino_id es NULL, ocultándolos en
recepción. Como create_solicitud ya no crea consentimientos de lugar,
el else es innecesario — en recepción fidsDeLugar queda vacío y los
consentimientos de examen pasan el filtro !c.origen_lugar_id.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Al escribir en el textarea de comentarios se guarda automáticamente
en localStorage con clave toma_draft_<turno_id>. Al abrir la ficha
del mismo turno el texto se restaura con borde amarillo de aviso.
Al guardar exitosamente o al hacer resetFicha se borra el draft.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
create_solicitud.php: cambia de crear consents por lugar (toma de muestras)
a crear consents por examen (exam_tipo_consentimientos). Los de lugar se
crean cuando el paciente llega a la estación vía get_consentimientos.php.
get_consentimientos.php: cuando lugar_destino_id es NULL (turno en recepción),
busca TODOS los formularios de turnero_lugar_consentimientos y los marca con
origen_lugar_id = -1 (truthy), para que el filtro !c.origen_lugar_id de
recepcion.php los excluya correctamente aunque no haya destino asignado aún.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
siguienteTurnoEnCola ahora acepta sesion_id opcional. llamar_turno.php
pasa la sesión de hoy para que nunca agarre turnos viejos de días
anteriores que quedaron en estado 'espera'.
Sin el filtro, el primer llamado del día podía agarrar un turno de ayer,
la ficha abría en recepción pero la pantalla TV (get_display_recepcion.php)
filtra por sesion_id=hoy y no lo encontraba → el número no salía en display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- App.php: clientIp() público resuelve X-Forwarded-For/X-Real-IP
- module.php: usa el mismo IP resolution en sidebar de recepcionista
- chat.php: botón Menú regresa al desk solo si rol=recepcionista con IP
registrada; bacteriólogo no aplica lógica de IP en este flujo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Si la IP está registrada en turnero_dispositivos para una recepción,
el botón volver apunta a ese desk en lugar de al dashboard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>