Cuando recepción marca "solo entrega de muestras", el puesto de toma no
mostraba ningún formulario. No era que no se generara: el backend ya lo
creaba por la vía del lugar —los ocho puestos de toma, Pediatría y
Ginecología tienen vinculado el formulario 16— y era la interfaz la que lo
escondía. En 30 días: 59 turnos de solo entrega, 34 con el F-LAB-08 ya
creado, 0 firmados.
Eran tres bloqueos, no uno:
- la sección de formularios se ocultaba por solo_muestras, y como los puestos
de toma van en modo embebido, esa sección es la única vía para abrirlo;
- no se cargaba el formulario embebido;
- ni se refrescaba la lista cada 5 s, así que al firmar no se habría visto
el cambio.
Se conserva la exención del bloqueo para finalizar (línea 1685): el
formulario aparece y se puede llenar, pero un turno de solo entrega no queda
atrapado si no se firma. Cerrar la cola por esto sería peor que el problema
que resuelve.
Estos turnos no traen exámenes, así que la lista solo puede contener los
formularios del puesto: no hay riesgo de que aparezcan consentimientos de
exámenes que no aplican.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dos defectos del mismo cambio de hoy, que explican lo que se oía.
La detección de "el navegador bloqueó el audio" daba 1,9 s de plazo y, si en
ese tiempo no había empezado a hablar, cancelaba y repetía con otra voz. Pero
una voz remota tarda en arrancar porque se baja de internet: el plazo la
declaraba muerta cuando iba a hablar, le cortaba la frase y la repetía con la
voz del navegador. De ahí que se oyera media frase y luego otra distinta, a
veces de hombre y a veces de mujer. Ahora se le pregunta al sintetizador si
está trabajando antes de darla por fallida, y el plazo sube a 3,2 s.
Y la voz se elegía al llegar el turno, no al hablar. La lista de voces la
carga el navegador de forma asíncrona, así que en los primeros llamados tras
abrir la página venía vacía y se hablaba con la voz por defecto. Ahora se
resuelve en el momento de hablar, cuando ya está cargada.
Comprobado: con voz instantánea, lenta (2 s) y muy lenta (4 s) ya no hay
corte y siempre habla Sabina; con el audio bloqueado se sigue avisando para
que el cartel no quede pegado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Regresión del cambio anterior. Al arrancar con sonidoActivo=true,
anunciarTurno() devolvía el objeto de voz aunque el navegador estuviera
bloqueando el audio; el cartel esperaba el evento 'end', que en ese caso no
llega nunca, y quedaba fijo hasta la red de seguridad de 25 segundos. Antes
devolvía null y cerraba a los 5.
Ahora avisa por callback tanto al terminar de hablar como al quedar claro que
no va a hablar, detectado porque 'start' no ocurre. speak() no lanza nada ni
emite 'error' cuando lo bloquean: el silencio es la única señal.
Además, si la voz elegida no arranca —el equipo la lista pero no la puede
usar— se reintenta una vez con la del navegador en vez de quedarse mudo.
Comprobado con tres escenarios: equipo normal (cierra a 4,7 s), audio
bloqueado (3,1 s) y voz que no suena (5,9 s, hablando en el reintento).
Aparte: la expresión de mayúsculas usaba \w, que no cuenta letras acentuadas
y trataba la tilde como separador. "maría fernanda gómez" se pronunciaba
"MaríA Fernanda GóMez".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La voz es-CO de Chrome es de Google y se baja de internet en cada llamado:
con wifi flojo se corta. Una colombiana local no existe, así que se prefieren
las mexicanas del propio equipo —Sabina en Windows, Paulina en macOS—, que
para leer letras, números y un nombre propio se oyen naturales aquí.
El orden queda: las dos por nombre, luego cualquier local latinoamericana,
luego cualquier local aunque sea de España (peor acento, pero no se corta), y
la remota solo si el equipo no tiene ninguna instalada.
Comprobado contra cinco inventarios de voces: Windows con Sabina, macOS con
Paulina, solo España local, sin ninguna local, y sin voces en español.
voces.php replica el mismo orden para señalar cuál está sonando.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sonidoActivo empezaba en false y solo un clic lo encendía, así que cualquier
recarga —corte de red, reinicio, refresco— dejaba el televisor mudo hasta que
una persona fuera físicamente a tocarlo. Peor: con esa lógica ni siquiera
poniendo --autoplay-policy=no-user-gesture-required habría sonado, porque la
página se bloqueaba a sí misma antes de que el navegador opinara.
Ahora arranca encendido e intenta abrir el audio al cargar. Si el navegador
lo permite, habla sin que nadie intervenga. Si lo bloquea, se muestra el
letrero para que alguien toque una vez, que es el único camino que dejan las
políticas de reproducción automática.
El pito de confirmación queda solo para el clic humano: al arrancar sola, la
pantalla no tiene por qué pitar cada vez que se recarga. Y el letrero ya no
depende de localStorage sino de si el audio quedó realmente suspendido,
atado a onstatechange porque resume() es asíncrono y antes parpadeaba.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El llamado se oía "entrecortado" por dos motivos distintos.
playBeep() y speak() salían en el mismo instante, y el pito dura 0,65 s con
volumen 0,35: "Turno A-01" se pronunciaba por debajo del tono y desde lejos
parecía que el anuncio empezaba ya empezado. Se vuelve a esperar los 700 ms
que el código tenía antes de que se quitaran.
El otro motivo es la voz. getVozES() pide es-CO, que en Chrome es la de
Google: remota. Cada llamado la baja de internet, así que con wifi flojo se
corta o no suena. Las voces disponibles las decide el televisor, no el ERP,
así que en vez de adivinar se agrega /erp.php?m=turnero&v=voces: lista las
voces del equipo, marca cuáles son locales y cuáles se bajan de internet,
señala la que está sonando hoy y deja oír cada una con el texto real de un
llamado. Hay que abrirla en el televisor.
Queda descartado el eco entre pantallas: hay un solo televisor con sonido.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rol nuevo para consultar turnos y tiempos de atención, de solo lectura y con
acceso únicamente a dashboard e historial. Se le permite exportar el historial,
que es la forma de analizar tiempos fuera del sistema.
Al crearlo salió que las vistas del turnero no verificaban nada por su cuenta:
el control del ERP es por módulo, así que cualquiera con acceso al turnero podía
abrir Configuración —lugares, dispositivos, plantillas— escribiendo la URL,
aunque el menú no se la mostrara. Aplicaba a todos los roles, no solo al nuevo.
Se agrega _acceso.php con una verificación de rol que usan las tres pantallas
que operan sobre la atención o cambian configuración. Verificado que cada rol
conserva lo que ya usaba: recepcionista entra a recepción, bacteriólogo a las
estaciones, supervisor y administradores a todo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un equipo registrado por IP o token queda atado a su estación: si pedía otra,
lugar.php lo devolvía a la suya sin mostrar nada, y parecía que el enlace estaba
roto. La restricción tiene sentido para las Tomas de Muestras, pero Pediatría y
Ginecología se atienden desde cualquier puesto.
Se agrega la marca acceso_libre en turnero_lugares en vez de codificar los
nombres, para que mañana se habilite otra estación sin tocar código. Las
marcadas así no redirigen y aparecen en el menú del equipo junto a la suya.
El bloqueo se mantiene en todo lo demás: el equipo sigue atado a su estación, el
botón de cambiar sigue oculto y no puede saltar a una Toma de Muestras ajena.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Marcar ausente solo pedía confirmación: el turno quedaba registrado sin ninguna
explicación, y hoy son 6 en un día. Ahora se pide el motivo y se guarda como
comentario de recepción, no como columna nueva: así aparece en todas las
pantallas que ya muestran comentarios, sin duplicar el dato ni tocar el esquema.
Dashboard e historial mostraban las notas sin indicar de dónde venían, así que no
se distinguía una observación de toma de muestras de una de recepción. Se agrega
la insignia de origen con los mismos colores que ya usa la bandeja, y el título
pasa a "Notas y observaciones" con su cantidad.
El historial además traía solo las 3 últimas por turno y en orden inverso. Con
recepción empezando a dejar notas eso habría escondido justamente el motivo de
una ausencia sin que nada indicara que faltaban; ahora trae hasta 20, en orden
cronológico.
De las 114 notas existentes, todas son de toma de muestras: recepción nunca dejó
ninguna porque no tenía dónde hacerlo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El troceo lo causaba el keepalive del sintetizador: corría cada 10 segundos sin
verificar si estaba hablando, y pause() a mitad de frase la corta. Como un
anuncio con nombre y destino supera los 10 segundos, lo alcanzaba siempre. Ahora
solo actúa con el sintetizador en reposo, que es para lo que existe: evitar que
Chrome lo suspenda tras un rato sin uso.
El cartel se cerraba a los 5 segundos aunque la voz siguiera sonando, y al
cerrarse pasaba al siguiente de la cola, que cancelaba el anuncio en curso. Si
dos pacientes se llamaban seguido, el primero quedaba a media frase. Ahora se
cierra cuando terminó de hablar y se cumplió el mínimo visual, con una red de
seguridad por si el navegador no emite el evento de fin.
Sobre la demora: se quita la espera fija de 300 ms antes de hablar —solo se
mantiene, y reducida, cuando de verdad hay que cancelar algo en curso— y el
sondeo baja de 2 s a 1 s. El retraso desde el clic pasa de 0,3–2,3 s a 0–1 s.
La velocidad sube de 0.85 a 0.95: acorta el anuncio sin perder claridad.
Validado con una simulación del reloj y del sintetizador: reproduce el corte
con el código anterior y no ocurre con el nuevo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El acceso quedaba solo en la tarjeta del paciente de la estación: al ver un dato
mal mientras se llenaba el formulario había que cerrarlo, buscar el botón y
volver a abrirlo. Ahora los datos del paciente dentro del formulario llevan un
lápiz que abre el mismo modal, ya en modo edición, sin cerrar lo que se está
llenando.
El formulario va embebido, así que no puede abrir el modal por su cuenta: le
avisa al contenedor por el mismo mecanismo que ya usa para informar una firma.
El modal queda por encima del formulario (z-index 9500 contra 9000).
De paso se corrige un error del commit anterior: el refresco tras guardar
buscaba el iframe con un selector equivocado —el modal es modal-consentimiento,
no modal-consent—, así que el formulario nunca tomaba el dato corregido sin
recargar a mano. Ahora se recargan los dos iframes posibles por su id real.
El lápiz aparece una sola vez, en el primer campo vinculado, y no se imprime.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los campos del formulario F-LAB-08 son vinculados: leen de la ficha del
paciente. Si el dato está mal, no había forma de arreglarlo desde la estación
—el modal era de solo lectura— y corregirlo únicamente en el documento habría
dejado la ficha equivocada, reapareciendo en el próximo turno, orden o
consentimiento. Por eso se corrige la ficha y no el documento: el dato queda
bien en todo el sistema y el formulario, al ser automático, lo toma solo.
La edición tiene dos niveles de fricción. Teléfono, dirección y EPS se editan
directo: cambian seguido y el error es de bajo riesgo. Nombre, tipo y número
de documento y fecha de nacimiento quedan tras un botón aparte con
confirmación, porque identifican al paciente en toda su historia clínica.
Si el turno ya tiene documentos firmados se advierte que corregir la ficha no
los modifica y que hay que emitirlos de nuevo, para que nadie espere que un
PDF ya firmado se actualice solo.
Paciente::actualizar registraba en la bitácora solo el valor nuevo, lo que
impedía reconstruir el anterior si la corrección resultaba equivocada —justo
cuando hace falta consultarlo. Ahora guarda antes y después, y únicamente de
los campos que realmente cambiaron.
La validación del servidor ya cubría nombre, celular, documento y correo, y la
cédula tiene índice único, así que no hizo falta agregar validación.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La insignia con la cantidad de exámenes solo se refrescaba desde los callbacks
onItemAdd/onItemRemove de TomSelect, y hay cuatro rutas que manipulan la
selección por código sin dispararlos:
- Importar desde RIPS usa addItem(id, true) —silencioso a propósito, para no
recalcular precios en cada examen—, así que la insignia quedaba en cero
aunque se hubieran cargado varios.
- El botón "Limpiar selección", toggleSoloMuestras y resetCheckboxes usan
clear(), que internamente quita los ítems en modo silencioso: la insignia
conservaba el número anterior.
Al revisarlo apareció un segundo efecto en toggleSoloMuestras: limpiaba los
exámenes sin recalcular, dejando el panel de precios mostrando exámenes que
ya no estaban seleccionados.
Se agrega limpiarExamenes(), que limpia y sincroniza contador y precios, y se
usa en las tres rutas de limpieza; la carga desde RIPS actualiza el contador
una vez al terminar, conservando el modo silencioso por examen.
Validado con node sobre un doble de TomSelect que replica el comportamiento
silencioso: los cuatro escenarios pasan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La proyección estimaba el cierre del día multiplicando el ritmo promedio por
las horas que faltaban hasta un cierre fijo a las 17:00. Eso sobreestimaba
(el laboratorio concentra las tomas en la mañana por el ayuno), dependía de
una hora de cierre escrita a mano, y en fechas pasadas usaba date('G') —la
hora actual real— así que proyectaba facturación futura sobre días ya
cerrados.
Ahora son dos cifras concretas, sin estimar: Facturado = turnos finalizados;
En proceso = turnos aún activos, cuyo total_cobrado ya está registrado pero
no han cerrado. Ausentes y cancelados quedan fuera de ambos. La gráfica por
hora usa la misma separación.
Hoy: $22.779.145 en 92 finalizados + $667.000 en 2 en proceso (antes
proyectaba $28.740.490).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cada pregunta se enviaba sola, sin los intercambios previos, así que un
seguimiento del tipo "¿y de esos cuántos en la mañana?" no tenía contexto.
Ahora el dashboard acumula los últimos 4 intercambios y los envía; el
backend los valida y antepone a la pregunta actual (recortados para no
inflar el consumo).
El prompt pedía "responde de forma concisa" en todos los casos, lo que
sumado al tope de tokens producía respuestas cortadas. Ahora ajusta el
detalle a lo que pide la pregunta y sabe que tiene historial disponible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
guardar_toma.php (única ruta de firma de F-LAB-28 Tomas Prolongadas) nunca
registró quién firmaba: solo guardaba el SVG por campo. De los 111 registros
del formulario, 0 tienen identidad del firmante y no hay traza en ninguna
tabla de auditoría, así que no es recuperable retroactivamente. Ahora el
nombre/cédula se resuelven desde la sesión en el servidor (adminId()) y se
guardan por toma — cada toma puede firmarla un profesional distinto, y esta
es la única fuente confiable. El intento anterior dependía de que el
navegador enviara window._profNombre.
historial.php abría el modal sin compact=1&zoom=1.1, que sí usa bandeja;
por eso el mismo documento se veía en lista larga en vez de la grilla con
tarjetas de toma.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nueva tabla turnero_tv_media (tipo, url, orden, duracion_segundos) para
soportar múltiples videos/imágenes reproducidos en secuencia y en bucle.
El video que ya estaba cargado se migró como primer ítem de la playlist.
- save_tv_media.php / delete_tv_media.php / reorder_tv_media.php
reemplazan a save_tv_video.php (eliminado).
- configuracion.php (tab TV): subida múltiple, lista reordenable por
arrastre, duración editable por imagen.
- display_global.php: el <video> único se reemplaza por un reproductor
JS que recorre la playlist — video hasta 'ended', imagen por su
duración configurada, y vuelve a empezar al terminar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tres bugs relacionados en el formulario "Control de Tomas de Muestras
Prolongadas" (F-LAB-28), detectados en el turno E017:
1. El modal de documentos del historial abría el documento sin &embed=1,
por lo que el filtrado de tomas irrelevantes (según examen seleccionado)
nunca se aplicaba — se mostraban las 16 secciones posibles en vez de
solo las 2-3 que aplicaban, con textos "Pendiente de firma" de más.
2. El guardado de firma en tomas progresivas usa un endpoint distinto
(_guardarFirmaMP → guardar_toma.php) que no pasaba por el fix de
nombre/cédula del firmante aplicado antes solo a _guardarFirmaPro.
Como además una toma progresiva puede pasar por varios profesionales
en el transcurso de horas, ahora se guarda identidad por-toma
(campoId + '_pro_nombre'/'_pro_cedula'), no solo global.
3. El historial no mostraba quién atendió cada toma. get_consentimientos.php
ahora expone tomas_detalle (etiqueta, hora, firmante) por consentimiento
de toma progresiva, y el panel de documentos del historial lo renderiza
en una tabla debajo del pill del formulario.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Las consultas de "muestras pendientes de visita anterior" solo incluían
estado='pendiente'; ahora también 'rechazada', así una muestra rechazada
en una visita aparece igual cuando el paciente vuelve, y se puede recibir
directo (sin el paso extra de revertir a pendiente primero).
En historial.php, la columna Servicio se resalta en rojo con ícono de
alerta cuando supera 20 minutos (SERVICIO_PROLONGADO_MIN), para detectar
turnos prolongados a simple vista.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El turno original no se modifica (queda finalizado igual que antes) — solo
se agrega trazabilidad: turnero_muestras.recibida_en_turno_id registra en
qué turno se completó una muestra que había quedado pendiente en otra
visita. Historial, bandeja y lugar.php ahora muestran un chip/botón "Turno
vinculado" en ambos sentidos (origen ⇄ seguimiento) con acceso directo.
Además, al abrir un formulario "Datos Toma de Muestras" pendiente para una
visita de seguimiento, aparece un botón para cargar las respuestas del
último formulario firmado de ese mismo paciente (sin firma ni identidad
del profesional anterior) — evita reescribir la historia clínica; la
firma y fecha siguen siendo las de hoy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cuando el paciente vuelve a completar una muestra pendiente, el panel de
lugar.php ahora lista los exámenes de la solicitud original debajo del
badge "visita anterior", con la misma clase visual que el motivo de
rechazo (hereda color según estado: pendiente/recibida/rechazada).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El desglose anterior se basaba en turnero_muestras, que no representa el
formulario "Control de Tomas de Muestras Prolongadas" que el laboratorio
ya usa para tomas seriadas (curvas de glicemia, etc.). Ahora el doc-pill
del historial muestra "X/Y tomas" usando es_toma_progresiva/tomas_firmadas
que get_consentimientos.php ya calculaba, sin duplicar lógica.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Muestra el detalle de cada toma (tipo, quien la recibió, tiempo) cuando un
turno tuvo varias, para explicar tiempos de servicio prolongados. Los
documentos firmados ahora se abren en un modal en vez de nueva pestaña,
preservando los filtros de búsqueda activos.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ai_chat.php: cuenta tokens reales desde Gemini usageMetadata, persiste en lab_config, bloquea con HTTP 402 al agotar 1.000.000
- dashboard.php: _actualizarTokens() actualiza barra visual, _bloquearLIA() deshabilita input+mic y muestra aviso de soporte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LIA: saludo de voz al abrir (1x/día), auto-escucha 3s, olas animadas
- Gráfica de barras (Chart.js) con facturación real + proyección lineal
- Exportar a Excel (.xls con HTML table, se abre directo en Excel)
- Botón "Informe WA": envía resumen del día por WhatsApp a número elegido
- Diseño accesible: texto más grande, botones claros para usuario adulto
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
API: agrega por_prioridad con último código llamado a muestras por letra.
Vista: badges con color de prioridad mostrando "A: A-023", "B: B-011", etc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- API: agrega tipo_documento y numero_documento al SELECT
- Vista: línea 1 = nombre BD + CC/doc, línea 2 gris = Kiosko: nombre
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- APIs: total_min = creado_at → fin_lugar_at (solo finalizados)
- Dashboard: KPI card 'Total prom. puerta a puerta' + columna Total en tabla
- Historial: columna Total en tabla
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- get_bandeja.php: contar estado='rechazada' (no 'pendiente')
- chip renombrado a "Muestras rechaz." con ícono fa-flask
- card muestra badge rojo con ícono y cantidad cuando hay rechazadas
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El paciente siempre tiene celular registrado; el error ocurría porque se
requería match en users. Ahora usa paciente_cel como fallback final.
Revierte la guarda del frontend del commit anterior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Si turnoActivo.paciente_cel está vacío, _ofrecerEncuesta retorna sin
mostrar la barra, evitando el error de WA al intentar enviar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Si el nombre registrado en BD difiere del ingresado en kiosko, muestra
ambos: el BD como principal y el kiosko en gris debajo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- esc() ahora escapa backticks (`) para evitar que nombres de paciente
con backticks rompan el template literal
- onclick simplificado: eliminado .replace(/'/g,\\'\\\\\\'\\'') que era JS
inválido (backslash fuera de string literal dentro de ${}); esc() ya
convierte comillas simples a '
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
El timer de 1s solo escaneaba chips en la cola principal. Los pacientes
en espera entre extracciones (muestra_espera_at IS NOT NULL) viven en el
panel de espera sin chip, por lo que nunca sonaba. Se añade un chip oculto
con data-toma-at en cada fila del panel; el timer existente lo detecta y
dispara banner + sonido en todas las tablets del grupo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Chip "Muestras pend." y nuevo "Prog. en espera" son toggles de filtro
- Se resaltan con borde al activarse; combinables entre sí y con el dropdown
- API retorna muestras_pendientes (turnos con ≥1 muestra pendiente) y
prolongadas_pendientes (toma progresiva en espera entre extracciones)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inputs desde/hasta pasan al API y filtran por rango de fechas
- Buscador cubre nombre, número de orden, código y examen
- API usa fecha cuando se pasa, sesión actual como fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Añade conteo de muestras_pendientes por turno en la lista y opción
de filtro en el selector de estados de la bandeja del día.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>