Cuando recepcion_desk_id es NULL, el mismo turno aparecía en los 3
escritorios de recepción con claves distintas (incluían el nombre del
escritorio). Eso generaba 3 entradas nuevas en lastSeenKeys → 3 anuncios.
Fix: añadir announcedCalls Set con clave id|llamado_at (sin destino).
El sonido/anuncio solo se encola una vez por evento de llamada,
independientemente de cuántos escritorios devuelvan el mismo turno.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dos causas:
1. sse_turno.php inicializaba \$lastPing = null, haciendo que la primera
iteración del loop (2s después del emit inicial) siempre emitiera
un snapshot extra aunque no hubiera cambios reales.
Fix: inicializar \$lastPing con el sse_ping_at actual de la BD.
2. display.php comparaba activo.codigo para detectar nueva llamada.
Si el poll y el SSE llegaban casi simultáneamente antes de que
lastCodigoActivo se actualizara, ambos disparaban el sonido.
Fix: comparar llamado_at en vez del código — el timestamp es
único por llamada, por lo que sonidos duplicados son imposibles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- item-row con flex-wrap para que los tags pasen a la siguiente línea
- consent-tag con word-break y max-width para textos largos
- Botones de acción agrupados en .item-actions con margin-left:auto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Toggle activo/inactivo en configuración → guarda ia_activa en lab_config
- Botón "Probar" llama api/lab/test_gemini.php con la clave actual
- test_gemini.php envía pregunta mínima a Gemini y confirma respuesta
- Dashboard oculta botón y panel IA cuando ia_activa != '1'
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Campo N.° Orden editable en la ficha, antes de seleccionar paciente
- Se carga automáticamente al abrir un turno (YYYYMMDD-001, 002…)
- Botón para regenerar el siguiente consecutivo
- Columna numero_orden en turnero_solicitudes (VARCHAR 20)
- Nuevo endpoint get_consecutivo.php calcula el siguiente del día
- create_solicitud.php guarda el numero_orden junto con la solicitud
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Columnas añadidas: Recepción (desk + recepcionista), Lugar/Bacteriólogo, Exámenes
- Elimina columnas Lugar y Entrada que eran menos informativas
- colspan actualizado a 10
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Si llega con desk_id redirige a la URL canónica erp.php?m=turnero&v=recepcion&desk_id=N
- Si llega sin desk_id muestra lista de turnero_lugares activos para elegir
- Elimina acceso directo sin contexto de desk
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ai_chat.php: contexto completo (tiempos, facturación, franjas, turnos detallados)
- Preguntas soportadas: demoras por recepcionista, total facturado, horas pico,
tiempo total por paciente, exámenes, búsqueda por cédula
- Panel flotante de chat en dashboard (botón robot azul)
- Búsqueda de tabla ahora filtra por cédula/documento
- Ícono de fila gira al expandir detalle (flecha → rotada)
- lab_configuracion.php: nueva sección IA con campo token Gemini
- save_config.php: permite guardar gemini_api_key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Columnas nuevas en tabla principal:
- Recepción: módulo de desk + nombre del recepcionista
- Lugar / Bacteriólogo: lugar destino + quien atendió toma de muestras
- Exámenes: badges con código de cada examen solicitado
Los datos ya venían del API (get_historial.php), solo se exponen en la tabla.
Se elimina columna Teléfono y Entrada de la vista principal (siguen en detalle).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
create_consent_token.php ahora acepta paciente_id y lo vincula al turno
si aún no lo tiene, eliminando la carrera con vincular_paciente.php
(fire-and-forget). Ya no hace falta refrescar para firmar el consentimiento.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Escribir manualmente ya no dispara el turno: la persona oprime el botón.
El lector de cédula sí lo dispara porque mete los dígitos en ráfaga
(<35ms entre cada uno) o termina con Enter. Guard contra doble envío.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
La firma se guardaba en datos_cliente[campo_svg] pero el display
solo leía firma_profesional_svg (columna inexistente en lab_form_envios).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Kiosko: detector scanner (gap < 80ms), iconos 90px, fa-person-pregnant, ticket Arial
- Recepción: vincular_paciente.php para persistir paciente_id antes de firmar consentimiento
- Historial y dashboard: recepción desk, empleado recepción/muestras, exámenes y comentarios
- display_global: turno más pequeño, lugar-nombre más grande
- lab_pacientes: modal compacta con form-sm
- Configuración: icono embarazada fa-person-pregnant en prioridades
- ver_formulario_enviado: firma por campo (fix doble firma)
- docker-compose.local.yml: override BD producción para dev local
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Guarantees that the turno state is correct in memory before pasarALugar()
attempts the transition to en_espera_lugar, preventing 'espera' → 'en_espera_lugar'
errors.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The sel-lugar dropdown is only for selecting the patient's next destination.
Desk consents are loaded by cargarConsentimientosDesk() on turno open
and must not be affected by destination lugar changes.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- cargarConsentimientosDesk() called from abrirFicha using DESK_ID
- First checks existing DB records (get_consentimientos), falls back to desk config
- abrirFormularioLugar shows proper errors if no patient or no turno
- Starts polling on open to detect patient signature in real time
- Fix creado_at column error in create_consent_token INSERT
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>