Commit Graph
231 Commits
Author SHA1 Message Date
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 b9bb4fc628 feat(lab_empresas): módulo CRUD de empresas, convenios y tarifas
- Nuevo módulo lab_empresas: lista paginada, modal create/edit empresa,
  gestión de subgrupos inline y CRUD de catálogo lab_tarifas_id
- APIs: empresas.php (list/get/save/toggle/delete),
  empresa_subgrupos.php, tarifas_id.php
- Migración 09 registra el módulo en system_modules y asigna permiso admin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 22:05:14 -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 9afefcf3e7 fix(turnero): turno de recepcion 1 aparece en recepcion 2 en display
get_display_global.php: la query de cada escritorio tenía la condición
  OR t.recepcion_desk_id IS NULL
lo que hacía que un turno llamado sin desk_id (NULL) apareciera como activo
en TODOS los escritorios simultáneamente, anunciando el destino incorrecto.
Se eliminó esa condición para usar match exacto por escritorio.

llamar_turno.php: cambiar COALESCE(?, recepcion_desk_id) por ? directo.
El COALESCE era innecesario porque el UPDATE solo aplica sobre estado='espera'
(primer llamado). Ahora siempre escribe el valor recibido.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-04 12:15:05 -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 99bfaa4c2f fix: convertir audio webm → ogg con ffmpeg antes de subir a WhatsApp en turnero
El archivo grabado por el browser es WebM (no soportado por WhatsApp).
conversations.php usa ffmpeg para convertirlo; chat_upload_media.php solo
cambiaba el string del MIME pero enviaba el archivo WebM real, causando que
el audio no llegara al destinatario. Ahora se convierte con los mismos
parámetros que upload_media.php (libopus 32k mono 48kHz).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:56:15 -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 e88fb4cc86 fix: mensajes automáticos del turnero registrados en canal principal
create_turno.php y send_consentimiento.php enviaban por WhatsAppService('turnero')
(número correcto) pero sin pasar meta canal='turnero', por lo que saveOutgoingMessage
defaulteaba a canal='bot' y quedaban registrados en las conversaciones del principal.

Agregado $waMeta = ['canal'=>'turnero'] en ambos archivos para sendTemplateMessage
y sendTextMessage (fallback).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:42:39 -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 34c6d2e643 fix: documentos recibidos en chat devuelven 401
Las URLs de CDN de WhatsApp (lookaside.fbsbx.com) requieren Authorization Bearer.
chat_get_messages.php las exponía directamente al browser, que no puede añadir
ese header, resultando en 401 "Authentication Error".

Ahora siempre se enrutan por /api/version/media-url.php (proxy existente) que:
- Sirve el archivo local si existe en disco
- Hace fetch con el token de WA si no está en local
- No requiere sesión para peticiones GET del browser

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 17:30:02 -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 a8380ddc81 debug(template): expose Meta error subcode/details, add canal meta
- WhatsAppService::makeRequest: include error_subcode and error_data.details
  in the thrown exception message to surface the real reason for #100 errors
- chat_send_message: log template name/lang/params before sending;
  pass canal+operator_id meta so outgoing templates are tagged correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:54:10 -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 ccdc35932d feat(turnero): add chat plantillas API endpoints
- chat_get_plantillas.php: GET — returns enabled templates from
  turnero_chat_plantillas JOIN message_templates; auto-creates table
- chat_save_plantillas.php: POST {ids:[...]} — replaces all rows in
  turnero_chat_plantillas using DELETE + INSERT IGNORE in a transaction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:41:24 -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 8a7e7a6658 Fix chat media upload: extract id from uploadMedia result, remap audio/webm
uploadMedia() returns the full API response array, not just the id string.
chat_upload_media.php was passing the whole array as $mediaId, producing
{"image": {"id": {"id": "...", "messaging_product": "..."}}} which
violated WhatsApp JSON schema for the media id field.

Also remap audio/webm (browser MediaRecorder default) to audio/ogg at
upload time since WhatsApp's media API rejects webm but accepts ogg/opus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:28:33 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 0cef678848 Add server-side pagination to medicos list (25 per page)
list.php now accepts page/limit params and returns total count.
View shows X-Y de N label, page buttons with ellipsis for large ranges,
and prev/next arrows. Search resets to page 1; save/delete stay on
current page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 14:24:55 -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 3dbf40e4fa feat(formulario): firma profesional directa en modal embebido (formularios solo-pro)
- ver_formulario_enviado.php: pre-scan $_soloFirmaPro; muestra canvas pro cuando modoTurnero+embebido+sesión activa+sin firma paciente
- fpw-save JS: recopila campos del form y envía solo_profesional=true; al guardar muestra éxito y postMessage turneroFirmado
- firmar_profesional_consentimiento.php: si solo_profesional=true guarda datos_respuestas y marca estado=firmado

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 12:49:39 -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
Lizandro GuarnizoandClaude Sonnet 4.6 7b734195e0 fix(lugar): incluir formulario_modo en query de lugares
La columna no estaba en el SELECT, siempre quedaba 'link'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 12:01:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 5dc2b0efa8 fix(turnero): corregir envío WA desde recepcion
- recepcion.php: quitar guard !solicitudActiva en enviarConsentimientosTodos (bloqueaba silenciosamente antes de guardar solicitud)
- send_consentimiento.php: eliminar requisito de solicitud y exámenes para poder reenviar; usar WhatsAppService('turnero') con rawComponents correctos (1 body param=codigo, button param=token); JOIN lab_pacientes también via t.paciente_id como fallback de teléfono

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 11:59:40 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 2dec300f3a Fix professional signature row only showing when form schema requires it
Only display the 'Falta firma enfermero' row and button when the assigned
consent form actually contains a firma_profesional field in its esquema.
get_consentimientos.php now computes requiere_firma_profesional from the
JSON schema and recepcion.php gates the row on that flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 11:55:20 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f09fcd5b4e fix(turno): no enviar WA si el lugar de recepción está en modo embebido
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 11:44:59 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 f8245b4d6f feat: modo embebido de formulario por lugar (link vs iframe)
- Nueva columna turnero_lugares.formulario_modo (link|embebido)
- Config: radio en modal de edición para elegir el modo
- lugar.php: muestra iframe con ver_formulario_enviado al activar turno
- API get_consent_token.php: crea/obtiene token para turno+lugar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 11:35:45 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 538af86407 fix(wa): enviar solo el token como parámetro del botón URL, no la URL completa
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 11:11:54 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 b47f6019cd fix(turno): no prepender +57 si el tel ya tiene código de país
573168950803 quedaba como +57573168950803 — WhatsAppService lo normaliza solo.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:47:51 -05:00
Lizandro GuarnizoandClaude Sonnet 4.6 735d70dc50 fix(wa): enviar botón URL como componente button/url, no como header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-03 10:34:55 -05:00