From 51f98dd2fb145b2a17869a5e9ac3dcb694913f74 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 21 Apr 2026 13:27:37 -0500 Subject: [PATCH] up --- modules/turnero/api/get_turno.php | 34 ++++++++++ modules/turnero/views/display.php | 97 ++++++++++++++++++++++------- modules/turnero/views/recepcion.php | 16 +++-- 3 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 modules/turnero/api/get_turno.php diff --git a/modules/turnero/api/get_turno.php b/modules/turnero/api/get_turno.php new file mode 100644 index 0000000..9d80cdd --- /dev/null +++ b/modules/turnero/api/get_turno.php @@ -0,0 +1,34 @@ +prepare( + "SELECT t.*, + p.codigo AS prioridad_codigo, + p.nombre AS prioridad_nombre, + p.color AS prioridad_color, + p.orden_peso + FROM turnero_turnos t + JOIN turnero_prioridades p ON p.id = t.prioridad_id + WHERE t.id = ? + LIMIT 1" +); +$stmt->execute([$turnoId]); +$turno = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$turno) { + jsonError('Turno no encontrado', 404); +} + +jsonOk(['turno' => $turno]); diff --git a/modules/turnero/views/display.php b/modules/turnero/views/display.php index 7ec841b..a9b6348 100644 --- a/modules/turnero/views/display.php +++ b/modules/turnero/views/display.php @@ -187,6 +187,12 @@ Conectando… +
--:--:--
@@ -259,28 +265,77 @@ function actualizarReloj() { actualizarReloj(); setInterval(actualizarReloj, 1000); -// ── Audio (Web Audio API — sin archivos externos) ───────────── -let audioCtx = null; -function playBeep() { +// ── Audio ───────────────────────────────────────────────────── +let audioCtx = null; +let sonidoActivo = false; + +function activarSonido() { try { - if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)(); - const osc = audioCtx.createOscillator(); - const gain = audioCtx.createGain(); - osc.connect(gain); - gain.connect(audioCtx.destination); - osc.type = 'sine'; - osc.frequency.setValueAtTime(880, audioCtx.currentTime); - osc.frequency.setValueAtTime(660, audioCtx.currentTime + 0.12); - gain.gain.setValueAtTime(0.25, audioCtx.currentTime); - gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.5); - osc.start(audioCtx.currentTime); - osc.stop(audioCtx.currentTime + 0.5); - } catch (_) { /* Sin audio si el contexto no está disponible */ } + audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + // Beep silencioso para desbloquear el contexto en el mismo gesto + const g = audioCtx.createGain(); + g.gain.setValueAtTime(0.001, audioCtx.currentTime); + const o = audioCtx.createOscillator(); + o.connect(g); g.connect(audioCtx.destination); + o.start(); o.stop(audioCtx.currentTime + 0.05); + } catch (_) {} + sonidoActivo = true; + const btn = document.getElementById('btn-activar-sonido'); + if (btn) { + btn.innerHTML = ' Sonido ON'; + btn.style.background = 'rgba(34,197,94,0.2)'; + btn.style.borderColor = 'rgba(34,197,94,0.5)'; + btn.style.color = '#86efac'; + btn.onclick = null; + btn.style.cursor = 'default'; + // Reproducir beep de prueba al activar + setTimeout(playBeep, 100); + } +} + +// Desbloquear también si el usuario toca/clica cualquier parte de la pantalla +document.addEventListener('click', () => { if (!sonidoActivo) activarSonido(); }, { once: true }); +document.addEventListener('touchstart', () => { if (!sonidoActivo) activarSonido(); }, { once: true }); + +function playBeep() { + if (sonidoActivo && audioCtx) { + 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, nombre) { + // 1. Beep electrónico + playBeep(); + // 2. Voz sintética — funciona sin interacción previa en la mayoría de navegadores + if ('speechSynthesis' in window) { + // Esperar a que el beep termine antes de hablar + setTimeout(() => { + window.speechSynthesis.cancel(); + const letras = codigo.split('').join(' '); // "A 0 1" para mejor pronunciación + let texto = `Turno ${letras}.`; + if (nombre) texto += ` ${nombre}, por favor pase a recepción.`; + const utt = new SpeechSynthesisUtterance(texto); + utt.lang = 'es-CO'; + utt.rate = 0.88; + utt.pitch = 1.05; + utt.volume = 1; + window.speechSynthesis.speak(utt); + }, 700); + } } -// Desbloquear AudioContext ante primer tap/clic (requerido por navegadores) -document.addEventListener('click', () => { - if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume(); -}, { once: true }); // ── Estado previo ───────────────────────────────────────────── let lastCodigoActivo = null; @@ -302,7 +357,7 @@ function renderSnapshot(snap) { // ¿Cambió el turno? → animar + sonido if (activo.codigo !== lastCodigoActivo) { - playBeep(); + anunciarTurno(activo.codigo, activo.paciente_nombre || ''); elRing.style.color = color; elRing.classList.remove('animar'); // Forzar reflow para reiniciar animación diff --git a/modules/turnero/views/recepcion.php b/modules/turnero/views/recepcion.php index bcd3f45..d0f2e19 100644 --- a/modules/turnero/views/recepcion.php +++ b/modules/turnero/views/recepcion.php @@ -434,8 +434,16 @@ async function llamarSiguiente() { // ── Seleccionar turno de la cola (para ver ficha de uno ya llamado) ─ async function seleccionarTurno(turnoId) { - // No abrir si ya hay uno en recepción (confuso) + // No abrir si ya hay uno activo en recepción if (turnoActivo && turnoActivo.estado === 'en_recepcion') return; + try { + const res = await fetch(API + 'get_turno.php?id=' + turnoId); + const json = await res.json(); + if (!json.ok) { mostrarError(json.error || 'No se pudo cargar el turno'); return; } + abrirFicha(json.turno ?? json.data?.turno); + } catch (err) { + mostrarError('Error al cargar turno: ' + err.message); + } } // ── Abrir ficha ─────────────────────────────────────────────── @@ -566,8 +574,8 @@ async function guardarSolicitud() { const json = await res.json(); if (!json.ok) { mostrarError(json.error); return; } - solicitudActiva = json.data.solicitud; - consentimientos = json.data.consentimientos_requeridos || []; + solicitudActiva = json.solicitud ?? json.data?.solicitud; + consentimientos = json.consentimientos_requeridos ?? json.data?.consentimientos_requeridos ?? []; btn.innerHTML = 'Guardado'; @@ -619,7 +627,7 @@ async function enviarConsentimientos() { const json = await res.json(); if (!json.ok) { mostrarError(json.error); return; } - consentimientos = json.data.consentimientos || consentimientos; + consentimientos = json.consentimientos ?? json.data?.consentimientos ?? consentimientos; renderConsentimientos(consentimientos); btn.innerHTML = 'Enviado'; } catch (err) {