Commit Graph
201 Commits
Author SHA1 Message Date
Lizandro GuarnizoandClaude Sonnet 4.6 d5b6de1f46 turnero: restricción por token de navegador en lugar de IP pública
- 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>
2026-07-09 10:56:53 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f5b38a331e turnero/configuracion: mostrar IP actual del cliente en sección dispositivos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-09 10:27:47 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 9afadb9a6f turnero: chequeo IP en vivo en cada carga de página
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>
2026-07-09 10:25:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 003d16b6a2 turnero: auto-redirigir a desk/lugar asignado sin mostrar selector
- 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>
2026-07-09 10:17:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f4da864db7 feat(turnero): toma prolongada secuencial + bloqueo muestras/cierre
- 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>
2026-07-08 21:16:07 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0e116359f8 feat(recepcion): agregar método de pago Bono
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 18:46:04 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8a44cb8bae feat(turnero): mostrar muestras pendientes de visitas anteriores en entrega
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>
2026-07-08 18:42:19 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6f1e2adafe feat(turnero): mostrar nota de recepción en toma de muestras
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>
2026-07-08 18:13:58 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 71e97f2d95 fix(turnero): asignar recepcion_desk_id al llamar turno específico
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>
2026-07-08 16:07:26 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5109db4a4b feat: borrador de comentario en toma de muestras (localStorage)
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>
2026-07-08 10:02:06 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8aa2f35585 fix(turnero/chat): recepcionista siempre vuelve a recepción, no a dashboard
IP registrada → su desk; sin IP registrada → primer desk activo de recepción.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 08:56:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1f2198225c fix(turnero): IP real con X-Forwarded-For; chat back solo para recepcionista
- 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>
2026-07-08 08:53:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 6f56734b53 fix(turnero/chat): botón Menú regresa al desk asignado por IP
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>
2026-07-08 08:48:55 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ea1f0e30c9 feat(turnero): tomas de muestras prolongadas con temporizador y progreso
- DB: ALTER TABLE turnero_consentimientos ADD siguiente_toma_at, toma_inicio_at;
  MODIFY estado para incluir 'en_progreso'
- guardar_toma.php (nuevo): guarda firma parcial por campo_id, parsea el
  esquema para calcular siguiente_toma_at desde 'Minuto X' o hora fija,
  marca 'en_progreso' o 'firmado' según tomas restantes
- get_consentimientos.php: expone siguiente_toma_at, es_toma_progresiva,
  tomas_total, tomas_firmadas, campos_firma_pro en cada consentimiento
- lugar.php: estado en_progreso con badge X/Y, countdown en tiempo real,
  animación de alerta cuando el tiempo llega, botón 'Siguiente toma' que
  abre el modal embebido en modo toma_progresiva

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 08:42:43 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b2bc0f950a feat(turnero): múltiples consentimientos por tipo de examen
- save_ex_tipo.php: GET devuelve form_ids[], POST acepta form_ids[] y
  hace DELETE+INSERT por cada uno (la tabla ya soportaba muchos a muchos)
- configuracion.php: reemplaza select único por checkboxes en formulario
  Agregar y modal Editar; JS lee los checks y envía form_ids[]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-08 08:10:57 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 bad3d42a92 feat(turnero): gestión de dispositivos/tablets en configuración
Sección nueva en tab Lugares con tabla CRUD de turnero_dispositivos:
IP, nombre, lugar asignado, estado activo/inactivo.
API save_dispositivo.php para crear/editar/eliminar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:08:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 229977785b fix(turnero): no mostrar consentimientos de toma de muestras en recepcion
Agrega origen_lugar_id a turnero_consentimientos para distinguir consents
creados por examen (null) vs por estación (= lugar_id destino).
Recepcion filtra los de origen_lugar_id != null; lugar.php muestra
solo los suyos + los de examen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 23:05:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 333e856f38 feat(turnero): buscador y paginación en tab de examenes
Filtrado en tiempo real + paginación client-side (15 por página).
Categorías vacías se ocultan automáticamente al buscar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:53:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 d24b234e30 fix(display): corregir turnos que no aparecen en pantalla TV
- sse_turno.php: sleep(2)→sleep(1) para detectar notificarSSE en máx 1s
- display*.php: visibilitychange + focus → fetch inmediato al despertar pantalla
- display*.php: Wake Lock API para evitar throttling del browser en TVs
- display.php: SSE open → cargarSnapshot() inmediato al reconectar

Causa raíz: browsers throttlean setInterval a >1min en tabs sin foco;
las TVs inactivas dejaban de recibir actualizaciones aunque el servidor
las enviara correctamente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 22:47:13 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 8760b685ad feat(turnero): dispositivos por IP, firmas, formularios y muestras prolongadas
- login.php: detectar dispositivo por IP al autenticar y redirigir al lugar asignado
- dashboard.php / lugar.php / recepcion.php: bloquear acceso por dispositivo (IP→lugar)
- lugar.php: modal firma profesional pre-guardada; compact 2-col en iframe; botón WA
  solo cuando el formulario requiere firma del paciente
- ver_formulario_enviado.php: modo compact tablet, auto-save borrador (merge), firma
  profesional en 1 clic, Muestras Prolongadas (countdown + Finalizar), auto_profesional
  pre-fill desde sesión, fecha→fecha_hoy en campos de toma/recepción
- get_consentimientos.php: añadir requiere_firma_paciente para controlar botón WA
- save_firma_profesional.php: API para guardar firma pre-configurada del profesional
- migrations: turnero_dispositivos, admin_users.firma_svg, clon VIH F-LAB-05 para turnero

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 21:58:52 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4de9c2f9b4 turnero: simplificar URLs de pantalla TV y añadir URLs de gestión por lugar
- Quita pantallas individuales por estación de muestras y kiosko del bloque anterior
- Deja solo Pantalla Global y Kiosko como accesos de visualización
- Añade bloque de URLs de gestión por escritorio de Recepción (recepcion&desk_id=X)
- Añade bloque de URLs de gestión por estación de Muestras (lugar&lugar_id=X)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 10:34:32 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f695b04f5b feat(caja): generar lab_recepciones al confirmar solicitud en turnero
- create_solicitud.php crea lab_recepciones (es_historico=0) dentro de
  la transacción: num_factura auto-secuencial para registros nuevos,
  campos empresa/subgrupo/autorización/diagnóstico tomados de la solicitud
- Inserta lab_relaciones por cada examen con precio del snapshot JSON
- Inserta lab_pagos + lab_pagos_det si hay metodo_pago registrado
- Bloque try/catch: si migración 11 aún no corrió solo se loguea,
  no se revierte la solicitud principal
- recepcion.php muestra toast "Recepción #N generada" al guardar
- Migración 11: ALTER lab_recepciones.id → AUTO_INCREMENT,
  ADD solicitud_id, ALTER lab_pagos.numcaja_legacy → nullable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:12:48 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a7aac706ee feat(recepcion): motor de precios + selector empresa/convenio
- Nueva sección empresa/convenio en recepcion.php: búsqueda live por
  nombre/NIT, selector de subgrupo, campo autorización (cuando req_autoriza),
  diagnóstico CIE-10
- Tabla de precios calculados en tiempo real: resolución por tarifa de la
  empresa o subgrupo, descuento empresa aplicado, botón "Aplicar al cobro"
- Al agregar/quitar exámenes o cambiar empresa/subgrupo se recalcula automáticamente
- Nuevo API get_precios_examenes.php: resuelve precios desde lab_tarifas
  con soporte de tarifas derivadas por porcentaje
- create_solicitud.php guarda nit_empresa, subgrupo_id, autorizacion,
  diag_ppal, items_precio (snapshot JSON al momento de la recepción)
- Migración 10: ALTER turnero_solicitudes añade los 5 campos nuevos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:09:26 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5fedd233f4 feat(lis): migración completa Firebird→MySQL + tracking de muestras en turnero
- Migraciones LIS 01-08: schema completo del nuevo LIS (secciones, protocolos,
  ítems de resultado, perfiles, tarifas, empresas, histórico transaccional)
- ETL Firebird→MySQL: script CLI con conversión WIN1252→UTF-8, batches de 500,
  resolución de FKs y deduplicación de pacientes
- turnero_muestras: tracking pendiente/recibida/rechazada por tipo de tubo
- lugar.php: widget de recepción de muestras (solo tipo=muestras)
- update_muestra_estado.php: API para marcar estado de muestra
- create_solicitud.php: auto-crea muestras al guardar solicitud
- get_consentimientos.php: incluye muestras[] en el response
- 6 vistas SQL: v_muestras_hoy, v_recepcion_completa, v_examen_precio, etc.
- numero_orden en encabezado del formulario firmado (D-/F- color diferenciado)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 21:44:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 86ed70db34 fix(recepcion): corregir lectura de data-consent en Tom Select
Tom Select expone los data-* de los <option> como data.consent (sin prefijo),
no como data.element.dataset.consent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 16:00:46 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 56b8fc98df feat(recepcion): Tom Select para selección de exámenes con badge de consentimiento
Reemplaza la lista de checkboxes por Tom Select (sin jQuery):
- Búsqueda integrada mientras se escribe
- Exámenes agrupados por categoría (optgroup)
- Cada examen con consentimiento asociado muestra badge ⚠ Consentimiento
  en el dropdown y marcador visual en el tag seleccionado
- Solo entrega de muestras deshabilita el select automáticamente
- Remove_button plugin para quitar ítems individualmente
- Elimina filtrarExamenes / toggleCategoria / actualizarCatChk (ya innecesarios)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 15:54:36 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 c566c1f33f feat(recepcion): destacar turnos en atención en la cola
Los turnos ya tomados (en_recepcion) muestran fondo azul pálido,
borde izquierdo azul grueso y badge "EN ATENCIÓN" en lugar del
chip de tiempo, para que el recepcionista identifique de un vistazo
qué turno ya está siendo atendido y no llame otro.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 15:19:54 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1d285da6ae feat(recepcion): mostrar tiempo de espera en tarjetas de cola
Cada turno en la lista muestra cuánto lleva el paciente esperando,
igual que en toma de muestras — verde < 10 min, amarillo < 25 min,
rojo ≥ 25 min.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 15:15:34 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 53b483d51a feat(turnero): UX audit improvements — lugar & recepcion
lugar.php:
- Bell button enlarged to 44×44px touch target
- Fecha nac. / Celular fields hidden from bacteriologist ficha
- Cancel button added to station-selector overlay; shows only when changing
- ESC key closes overlay when a station is already selected
- visibilitychange pauses polling when tab hidden, resumes on return

recepcion.php:
- Consent action buttons: padding 2px 8px → 5px 12px, font-size .72→.82rem
- Exam list: per-category select-all checkbox with indeterminate state support
- guardarSolicitud: auto-calls pasarALugar() when no consents required
- abrirModalPaciente: nome field auto-focused for both new and edit cases

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 13:46:32 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 69a6859b51 style(turnero): alinear ficha-acciones de recepcion con lugar
- Eliminar display:flex de ficha-acciones (rompía el apilado de filas
  accion-primary-row / accion-secondary-row, quedaban side-by-side)
- Padding: 1rem 0 .25rem → .8rem 1.4rem .9rem (igual que lugar)
- Background: #fff → rgba(255,255,255,.97) + backdrop-filter:blur(6px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 13:33:44 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 fb1cd2161c style(turnero): alinear botones de recepcion con lugar
- btn-accion-primary: padding .8rem 1rem→.8rem, font-size .97→1rem, opacity .55→.5
- touch target: eliminar override de padding en cola-card que contradecía el base

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 13:28:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 54fc986d82 style(turnero): homologar UI/UX entre lugar y recepcion
lugar.php:
- Cola: 340px→370px, border 1.5px→1px, border-radius 11px, padding .65rem .9rem,
  min-height 58px→62px (touch target)
- Cola header: font-size .72→.78rem, letter-spacing .06→.08em
- btn-llamar-next: padding .6→.65rem
- ficha-sec: padding 1rem 1.1rem→1.2rem, header .72→.78rem, .07→.08em
- consent-row: padding .55→.5rem, min-height 46px añadido
- Transición mobile: .22s→.2s
- Modal paciente: header plano→gradiente azul (#1043a0→#1565c0)

recepcion.php:
- cola-card: border-radius 10→11px
- prio-dot: border-radius 50%→9px (cuadrado redondeado, consistente con lugar)
- btn-accion-sec: font-size .8→.82rem

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 12:57:50 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 400547bd7a style(turnero): ajustar tamaños de fuente en display_global
- Código turno: clamp(4→3rem, 14→13vw, 10→9rem)
- Nombre lugar: clamp(3→1rem, 10→8vw, 8→6rem)
- Nombre paciente: clamp(1.15→2.65rem, 2.1→3.6vw, 1.45→2.95rem)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 12:23:44 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 4f25cd6b59 fix(turnero): corregir anuncios de sonido en display_global
- Bug 1: announcedCalls usaba el ID del turno como clave, silenciando todas
  las re-llamadas. Ahora usa key=codigo|destino|llamado_at, igual que
  lastSeenKeys, para que cada re-llamada anuncie correctamente.
- Bug 2: anunciarTurno no verificaba sonidoActivo antes de llamar a
  speechSynthesis.speak(); agregado guard al inicio de la función.
- Bug 3: Chrome congela speechSynthesis tras ~15 min sin interacción;
  agregado keepalive de 14s con pause()+resume() para mantener el motor activo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 12:09:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5af985581c feat(turnero): UI/UX tablet improvements for recepcion and lugar views
- recepcion: wider queue column (370px), breakpoint at 680px so portrait
  tablets get 2-column layout; prominent "Llamar siguiente" button in queue
  column header; searchable exam filter; touch-friendly action bar with
  large primary/secondary button rows; larger touch targets for cards,
  checkboxes and consent items
- lugar: wider queue column (340px), breakpoint at 680px; larger action
  buttons (.8rem padding, 1rem font) and secondary buttons; consistent
  mobile breakpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 18:55:07 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b55c7c1a19 feat: mostrar número de orden en ficha del turno (lugar)
get_consentimientos.php ahora incluye s.numero_orden en el SELECT.
En lugar.php el número de orden aparece como badge de solo lectura
en el header de la ficha, junto al código del turno, cuando la
solicitud tiene número de orden asignado.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 18:04:16 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a526a29a68 feat: botón Ver paciente con historial en vista lugar
Agrega botón "Ver" en la sección Paciente de la ficha del turno.
Abre modal con datos completos del paciente (nombre, doc, fecha nac,
celular, médico, embarazo) y timeline de historial de visitas anterior
(misma lógica que recepción), cargado desde get_historial.php.

El botón solo aparece cuando hay un paciente vinculado al turno.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:55:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 282c372de3 fix: corregir reproducción de audio en chat turnero
- URL absoluta para local_file y media_url_external (igual que conversations.php)
- Múltiples <source> con MIME types (ogg, mpeg, wav, webm) para compatibilidad
- onerror handler cuando el archivo no está disponible
- Fallback visible cuando mediaSrc es null ("Audio pendiente de descarga")

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:50:43 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 53dc492b5e feat: auto-rellenar variable {{name}} con nombre del paciente en nuevo mensaje
Al seleccionar un paciente de la lista, si la plantilla tiene una variable
llamada 'name', se pre-rellena automáticamente con su nombre. Si se ingresa
un número directo, el campo queda vacío para llenarlo manualmente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:48:02 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1bd799a501 fix: corregir envío de plantillas con variables nombradas en chat turnero
El parámetro_name enviado a WhatsApp era el índice numérico ("1") en vez
del nombre real de la variable ("name", "fecha", etc.), causando el error
#100 Invalid parameter. Ahora se detecta si las variables son nombradas o
numéricas y se construye el payload correctamente, igual que conversations.php.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:37:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 12c790fb5c fix: mover CSS del modal nuevo-mensaje fuera del media query mobile
Los estilos de #nuevo-modal y .nv-* estaban dentro de @media (max-width: 680px),
por lo que el modal no tenía diseño en desktop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:28:08 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f975844d9d fix: reacciones y audio en chat turnero
- chat_react: guardaba en tabla `messages` en lugar de `conversations` (tabla real del chat)
- chat_react: columna era `whatsapp_message_id`; la correcta es `message_id`
- chat.php: sendReactionEmoji actualiza la burbuja DOM inmediatamente sin esperar poll
- chat_upload_media: mime_content_type() devuelve video/webm en Linux para audio grabado;
  ahora cualquier mime con "webm" se remapea a audio/ogg antes de determinar mediaType

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:24:35 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 ab72750410 feat(chat): botón Nuevo mensaje con búsqueda de paciente y picker de plantilla
- chat_buscar_paciente.php: GET ?q= busca en lab_pacientes por nombre,
  documento o teléfono (mín. 2 chars, máx. 20 resultados)
- chat_start_conversation.php: POST {phone, template_name, lang, params}
  — normaliza teléfono, busca/crea usuario en users, envía plantilla
  vía WhatsAppService('turnero'), devuelve {ok, user_id}
- chat.php: botón "+ Nuevo" en sidebar header; modal 3 pasos:
  1) buscar paciente (debounce 350ms) o ingresar número libre
  2) lista de plantillas habilitadas
  3) variables de la plantilla + Enviar
  Al enviar recarga el sidebar y abre la conversación automáticamente

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:19:18 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 3d6147bf0d fix(template): send parameter_name for named-variable templates
WhatsApp API returns "(#100) Invalid parameter — Parameter name is
missing or empty" for templates that use named variables.

- chat.php sendTemplate(): collect params as [{name, value}] objects
  using data-var-index as the parameter name
- WhatsAppService::sendTemplateMessage(): handle {name, value} objects
  in the indexed-array path by emitting {type, parameter_name, text}
  (backward-compatible — plain strings still work as positional params)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 15:04:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f60df038d8 fix(chat): capture template name/lang before closing modal in sendTemplate
closeTemplatePicker() sets _tplSelected = null, so accessing
_tplSelected.template_name after the call threw a TypeError caught
as "Error de red al enviar plantilla".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:47:31 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 548ad3140d feat(turnero): template whitelist checklist + 2-step picker in chat
- configuracion.php (sesion tab): loads approved message_templates and
  enabled turnero_chat_plantillas; renders styled checklist; guardarPlantillasChat()
  POSTs to chat_save_plantillas.php
- chat.php: replaces free-text template modal with 2-step modal —
  step 1 lists enabled templates (chat_get_plantillas.php), step 2
  fetches variables via get_template_details.php and renders labeled
  inputs; sendTemplate() builds params array and POSTs to chat_send_message.php

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:37:21 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 00725ccd11 Redesign lugar.php UI/UX: patient-first, better cola, sticky actions
Bloque 1 - Cola:
- Separate sections for "en servicio" vs "en espera" with headers
- Time-in-queue badges (green <10min, amber <25min, red 25min+) from creado_at
- Prominent EN SERVICIO pill (solid green) instead of subtle border
- Larger touch targets on queue cards, bigger bell button
- Count chips show espera + servicio counts separately

Bloque 2 - Ficha header + action bar:
- Patient name is now the dominant element (1.35rem bold) with skeleton
  loader animation while async data loads; turno code becomes a secondary chip
- Action bar restructured: full-width primary button (blue "Anunciar turno" /
  green "Finalizar atención") + smaller secondary row (Re-llamar, Regresar, Ausente)
- Consent warning strip added inside action bar showing pending count;
  blocks Finalizar visually and functionally

Bloque 3 - Sections + mobile:
- Section headers use consistent .ficha-sec-hdr style with icon
- Mobile: fixed sticky "← Cola" top bar inside ficha panel (always visible,
  no hunting for back button)
- ficha-turno uses flex-column so action bar sticks to bottom without scroll

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:21:42 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 1e2cfbaa0b refactor(lugar): embebido como botón en fila de consentimiento, no sección separada
- Eliminar sec-form-embebido como sección independiente
- En modo embebido: fila de consentimiento muestra botón "Firmar" que abre modal directamente por token
- En modo link: botones "Firmar presencial" + "WA" como antes
- abrirModalConsentimientoPorToken(token) recibe token directo sin cache

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 12:37:50 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 a0189a61eb feat(lugar): formulario embebido como modal en lugar de iframe inline
- Botón "Abrir formulario de firma" en ficha abre modal overlay
- Modal con iframe cargando ver_formulario_enviado.php?embed=1
- postMessage 'turneroFirmado' cierra modal, refresca consentimientos y oculta botón
- Token pre-cargado al abrir ficha para apertura rápida del modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 12:16:22 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 27135e4d2f fix(formulario): múltiples correcciones modo embebido/turnero
- ver_formulario_enviado.php: usar opciones??options??items como fallback de claves del schema
- ver_formulario_enviado.php: marcar _esquemaTieneFirmaPaciente en cualquier campo firma (no solo sin SVG), evita mostrar firma paciente cuando schema solo tiene firma_profesional
- ver_formulario_enviado.php: modo embebido (?embed=1) con layout compacto para iframe
- lugar.php: pasar &embed=1 al iframe src

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