diff --git a/modules/turnero/views/display_global.php b/modules/turnero/views/display_global.php
index c1b3b0d..d7b2d7f 100644
--- a/modules/turnero/views/display_global.php
+++ b/modules/turnero/views/display_global.php
@@ -195,42 +195,43 @@ $_hasVideo = (bool)$_tvVideo;
height: 100%;
}
.ultimos-llamados .ul-label {
- font-size: 1.2rem; font-weight: 700; text-transform: uppercase;
+ font-size: 1rem; font-weight: 700; text-transform: uppercase;
letter-spacing: 2px; color: #94a3b8;
- padding: .8rem .8rem .3rem; flex-shrink: 0;
+ padding: .65rem .85rem .3rem; flex-shrink: 0;
+ }
+ .ul-thead {
+ display: grid; grid-template-columns: 82px 1fr;
+ padding: .25rem .85rem;
+ background: color-mix(in srgb, var(--brand) 12%, #f1f5f9);
+ border-top: 1px solid #e2e8f0; border-bottom: 2px solid #e2e8f0;
+ font-size: .62rem; font-weight: 800;
+ text-transform: uppercase; letter-spacing: .1em; color: #64748b;
+ flex-shrink: 0;
}
.ultimos-llamados .ul-scroll {
- flex: 1; overflow-y: auto; padding: 0 .5rem .5rem;
+ flex: 1; overflow-y: auto; padding: .3rem .4rem .5rem;
scrollbar-width: thin; scrollbar-color: #e2e8f0 transparent;
}
.ul-item {
- display: flex; align-items: center; gap: .45rem;
- padding: .5rem .75rem; border-radius: 10px;
- margin-bottom: .35rem;
+ display: grid; grid-template-columns: 82px 1fr;
+ align-items: center;
+ padding: .38rem .45rem; border-radius: 8px;
+ margin-bottom: .25rem;
background: #fff; border: 1px solid #e2e8f0;
- border-left: 4px solid color-mix(in srgb, var(--brand, #1565c0) 50%, transparent);
+ border-left: 3px solid color-mix(in srgb, var(--brand, #1565c0) 55%, transparent);
}
.ul-item.activo {
background: var(--brand);
border-color: var(--brand);
+ border-left-color: rgba(255,255,255,.4);
color: #fff;
- border-left-color: #fff;
- }
- .ul-item.activo .ul-nom,
- .ul-item.activo .ul-dest { color: rgba(255,255,255,.75); }
- .ul-item .ul-left { flex: 0 0 auto; min-width: 0; }
- .ul-item .ul-cod { font-weight: 800; font-size: 1.55rem; display: block; }
- .ul-item .ul-nom {
- font-size: .8rem; font-weight: 500;
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
- display: block;
}
+ .ul-item .ul-cod { font-weight: 800; font-size: 1.45rem; line-height: 1; }
.ul-item .ul-dest {
- font-size: 1.1rem; font-weight: 700; color: #1e293b;
- flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+ font-size: .92rem; font-weight: 600; color: #1e293b;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
- .ul-item.activo .ul-dest { color: #fff; }
- .ul-item .ul-dest i { margin-right: 3px; }
+ .ul-item.activo .ul-dest { color: rgba(255,255,255,.85); }
/* ── Video reel ── */
.reel-wrap {
@@ -405,6 +406,10 @@ $_hasVideo = (bool)$_tvVideo;
Llamados
+
+ Turno
+ Lugar
+
@@ -565,8 +570,9 @@ function showAnnouncement({ codigo, destino, paciente, color }) {
}
/* ── State ── */
-let lastSeenKeys = new Set(); // clave por turno+destino+llamado_at
-let llamados = [];
+let lastSeenKeys = new Set();
+let llamados = [];
+let _firstLoad = true;
function renderSnapshot(snap) {
const codEl = document.getElementById('codigo-activo');
@@ -584,7 +590,7 @@ function renderSnapshot(snap) {
if (l.turno) allTurnos.push({ ...l.turno, destino: l.lugar_nombre });
});
- // Detectar turno nuevo en CUALQUIER estación y anunciar
+ // Detectar turnos nuevos y anunciar
const currentKeys = new Set();
let anuncioNuevo = false;
allTurnos.forEach(t => {
@@ -592,13 +598,29 @@ function renderSnapshot(snap) {
currentKeys.add(key);
if (!lastSeenKeys.has(key)) {
const c = t.prioridad_color || '= $_labColor ?>';
- queueAnnouncement({ _key: key, codigo: t.codigo, destino: t.destino, paciente: t.paciente_nombre, color: c });
+ // En primera carga silenciar todos; el más reciente se anuncia abajo
+ if (!_firstLoad) {
+ queueAnnouncement({ _key: key, codigo: t.codigo, destino: t.destino, paciente: t.paciente_nombre, color: c });
+ }
llamados.unshift({ codigo: t.codigo, destino: t.destino, paciente: t.paciente_nombre, color: c });
if (llamados.length > 30) llamados.pop();
anuncioNuevo = true;
}
});
lastSeenKeys = currentKeys;
+
+ if (_firstLoad) {
+ _firstLoad = false;
+ // Anunciar solo el turno más reciente al cargar la página
+ const sorted = allTurnos.slice().sort((a, b) => new Date(b.llamado_at || 0) - new Date(a.llamado_at || 0));
+ if (sorted.length > 0) {
+ const t = sorted[0];
+ const c = t.prioridad_color || '= $_labColor ?>';
+ const key = t.codigo + '|' + t.destino + '|' + (t.llamado_at || '');
+ queueAnnouncement({ _key: key, codigo: t.codigo, destino: t.destino, paciente: t.paciente_nombre, color: c });
+ }
+ }
+
if (anuncioNuevo) renderLlamados();
// zona-activo: mostrar el turno llamado más recientemente
@@ -659,16 +681,12 @@ function renderLlamados() {
if (!wrap || !sc) return;
if (llamados.length === 0) { wrap.style.display = 'none'; return; }
wrap.style.display = '';
- const activo = llamados[0];
sc.innerHTML = llamados.map((l, i) => {
const isActivo = i === 0;
const c = l.color || '= $_labColor ?>';
- return `
-
- ${esc(l.codigo)}
- ${l.paciente ? `${esc(l.paciente)}` : ''}
-
-
${esc(l.destino)}
+ return `
+ ${esc(l.codigo)}
+ ${esc(l.destino)}
`;
}).join('');
}
diff --git a/modules/turnero/views/lugar.php b/modules/turnero/views/lugar.php
index d509f53..f4e7b4a 100644
--- a/modules/turnero/views/lugar.php
+++ b/modules/turnero/views/lugar.php
@@ -385,57 +385,6 @@ const API = '= BASE_URL ?>modules/turnero/api/';
const BASE_WA = '= BASE_URL ?>';
const LUGAR_NOMBRE = document.getElementById('lbl-lugar-titulo')?.textContent || '= addslashes($lugarNombre) ?>';
-// ── Audio / anuncio ───────────────────────────────────────────
-let _audioCtx = null;
-let _sonidoListo = false;
-
-function _desbloquearAudio() {
- if (_sonidoListo) return;
- try {
- _audioCtx = new (window.AudioContext || window.webkitAudioContext)();
- const g = _audioCtx.createGain(); g.gain.value = 0.001;
- const o = _audioCtx.createOscillator();
- o.connect(g); g.connect(_audioCtx.destination);
- o.start(); o.stop(_audioCtx.currentTime + 0.05);
- } catch (_) {}
- _sonidoListo = true;
-}
-document.addEventListener('click', _desbloquearAudio, { once: false });
-document.addEventListener('touchstart', _desbloquearAudio, { once: false });
-
-function _playBeep() {
- if (!_audioCtx) return;
- try {
- const osc = _audioCtx.createOscillator();
- const gain = _audioCtx.createGain();
- osc.type = 'sine';
- osc.connect(gain); gain.connect(_audioCtx.destination);
- osc.frequency.setValueAtTime(880, _audioCtx.currentTime);
- osc.frequency.setValueAtTime(1100, _audioCtx.currentTime + 0.1);
- osc.frequency.setValueAtTime(880, _audioCtx.currentTime + 0.2);
- gain.gain.setValueAtTime(0.35, _audioCtx.currentTime);
- gain.gain.exponentialRampToValueAtTime(0.0001, _audioCtx.currentTime + 0.65);
- osc.start(_audioCtx.currentTime);
- osc.stop(_audioCtx.currentTime + 0.65);
- } catch (_) {}
-}
-
-function anunciarTurno(codigo) {
- _desbloquearAudio();
- _playBeep();
- if ('speechSynthesis' in window) {
- setTimeout(() => {
- window.speechSynthesis.cancel();
- const letras = codigo.split('').join(' ');
- const utt = new SpeechSynthesisUtterance(`Turno ${letras}, pase a ${LUGAR_NOMBRE}`);
- utt.lang = 'es-CO';
- utt.rate = 0.85;
- utt.pitch = 1.05;
- utt.volume = 1;
- window.speechSynthesis.speak(utt);
- }, 700);
- }
-}
// ── Arranque ──────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
@@ -564,7 +513,6 @@ async function rellamarDesdeCard(turnoId) {
});
const json = await res.json();
if (!json.ok || !json.turno) return;
- anunciarTurno(json.turno.codigo);
await abrirFicha(json.turno);
}
@@ -594,7 +542,6 @@ async function _llamar(extra) {
if (!json.ok) { mostrarError(json.error); return; }
if (!json.turno) { mostrarToast('Cola vacía — no hay turnos en espera.', 'warn', 3000); return; }
mostrarLlamando(json.turno.codigo);
- anunciarTurno(json.turno.codigo);
await abrirFicha(json.turno);
cargarCola();
} catch (err) {
@@ -639,10 +586,10 @@ async function cargarFichaSolicitud(turnoId) {
const json = await res.json();
if (!json.ok) return;
- const sol = json.solicitud;
- const pac = json.paciente;
- const exams = json.examenes || [];
- const consts = json.consentimientos || [];
+ const sol = json.solicitud;
+ const pac = json.paciente;
+ const exams = json.examenes || [];
+ let consts = json.consentimientos || [];
// Datos del paciente
if (pac) {
@@ -669,6 +616,42 @@ async function cargarFichaSolicitud(turnoId) {
listaEx.innerHTML = '
Sin exámenes registrados';
}
+ // Auto-crear tokens para formularios del lugar que aún no tengan registro
+ if (lugarId) {
+ const resF = await fetch(`${API}get_lugar_formularios.php?lugar_id=${lugarId}`);
+ const jsonF = await resF.json();
+ const forms = (jsonF.ok && jsonF.formularios) ? jsonF.formularios : [];
+
+ const existIds = new Set(consts.map(c => parseInt(c.formulario_id)));
+ const faltantes = forms.filter(f => !existIds.has(parseInt(f.id)));
+
+ if (faltantes.length > 0) {
+ await Promise.all(faltantes.map(f =>
+ fetch(API + 'create_consent_token.php', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ turno_id: turnoId, formulario_id: f.id }),
+ }).catch(() => {})
+ ));
+ // Recargar consents tras la creación
+ const res2 = await fetch(`${API}get_consentimientos.php?turno_id=${turnoId}`);
+ const json2 = await res2.json();
+ if (json2.ok) consts = json2.consentimientos || [];
+
+ // Si alguno no se pudo crear (sin paciente), mostrarlo igual como pendiente visual
+ const createdIds = new Set(consts.map(c => parseInt(c.formulario_id)));
+ faltantes.forEach(f => {
+ if (!createdIds.has(parseInt(f.id))) {
+ consts.push({
+ id: null, turno_id: turnoId,
+ formulario_id: f.id, formulario_nombre: f.nombre,
+ estado: 'pendiente', token: null,
+ });
+ }
+ });
+ }
+ }
+
// Consentimientos
renderConsentimientos(consts);
@@ -921,7 +904,7 @@ async function rellamarTurno() {
});
const json = await res.json();
if (!json.ok) mostrarError(json.error);
- else if (json.turno) { mostrarLlamando(json.turno.codigo); anunciarTurno(json.turno.codigo); }
+ else if (json.turno) { mostrarLlamando(json.turno.codigo); }
} catch (err) {
mostrarError(err.message);
} finally {