This commit is contained in:
Lizandro Guarnizo
2026-06-16 07:02:04 -05:00
parent 64f21b083a
commit 35b13468b6
5 changed files with 81 additions and 22 deletions
+4
View File
@@ -31,6 +31,10 @@ if ($stmt->rowCount() === 0) {
jsonError('El turno no está en estado en_recepcion o no existe.', 404);
}
// Notificar SSE para que las pantallas se actualicen inmediatamente
$stmtS = $pdo->prepare('UPDATE turnero_sesiones SET sse_ping_at = NOW() WHERE id = (SELECT sesion_id FROM turnero_turnos WHERE id = ?)');
$stmtS->execute([$turnoId]);
// Devolver el turno actualizado para que recepcion.php abra la ficha
$stmtT = $pdo->prepare(
"SELECT t.*,
+1 -1
View File
@@ -537,7 +537,7 @@ async function cargarSnapshot() {
// Arrancar polling cada 4 s (funciona aunque SSE falle por proxy)
function iniciarPolling() {
cargarSnapshot();
pollTimer = setInterval(cargarSnapshot, 4000);
pollTimer = setInterval(cargarSnapshot, 2000);
}
// ── SSE ───────────────────────────────────────────────────────
+1 -1
View File
@@ -684,7 +684,7 @@ async function cargarSnapshot() {
}
cargarSnapshot();
setInterval(cargarSnapshot, 4000);
setInterval(cargarSnapshot, 2000);
</script>
</body>
</html>
+1 -1
View File
@@ -372,7 +372,7 @@ async function cargar() {
}
cargar();
setInterval(cargar, 4000);
setInterval(cargar, 2000);
</script>
</body>
</html>
+74 -19
View File
@@ -197,6 +197,31 @@ $adminNombre = $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['
border: 1px solid #bfdbfe; border-radius: 8px;
padding: .25rem .75rem; font-size: .82rem; font-weight: 700;
}
/* ── Toast ── */
.rec-toast {
position: fixed; bottom: 2rem; right: 2rem; z-index: 9999;
display: flex; align-items: center; gap: .7rem;
padding: .8rem 1.2rem; border-radius: 12px;
background: #fff; border: 1px solid #e2e8f0;
box-shadow: 0 8px 30px rgba(0,0,0,.12);
font-size: .9rem; font-weight: 500;
transform: translateY(120%); opacity: 0;
transition: transform .3s ease, opacity .3s ease;
pointer-events: none;
}
.rec-toast.show {
transform: translateY(0); opacity: 1;
}
.rec-toast.success { border-left: 4px solid #22c55e; }
.rec-toast.info { border-left: 4px solid #3b82f6; }
.rec-toast.warn { border-left: 4px solid #f59e0b; }
.rec-toast.error { border-left: 4px solid #ef4444; }
.rec-toast .ico { font-size: 1.2rem; }
.rec-toast.success .ico { color: #22c55e; }
.rec-toast.info .ico { color: #3b82f6; }
.rec-toast.warn .ico { color: #f59e0b; }
.rec-toast.error .ico { color: #ef4444; }
</style>
</head>
<body>
@@ -404,6 +429,11 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
</div><!-- /rec-layout -->
</main>
<div class="rec-toast" id="rec-toast">
<span class="ico" id="toast-ico"><i class="fas fa-check-circle"></i></span>
<span id="toast-msg"></span>
</div>
<!-- ══ Modal firma presencial ═════════════════════════════════ -->
<div class="modal-firma-backdrop" id="modal-firma-rec">
<div class="modal-firma-box">
@@ -432,7 +462,7 @@ const DESK_ID = <?= $deskId ?: 'null' ?>;
// ── Arranque ──────────────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
cargarCola();
pollingColaId = setInterval(cargarCola, 8000);
pollingColaId = setInterval(cargarCola, 3000);
document.getElementById('inp-buscar-pac')
.addEventListener('keydown', e => { if (e.key === 'Enter') buscarPaciente(); });
});
@@ -460,7 +490,7 @@ function renderCola(snap) {
return;
}
lista.innerHTML = espera.map(t => `
<div class="cola-card ${turnoActivo?.id === t.id ? 'activo' : ''}">
<div class="cola-card ${turnoActivo?.id === t.id ? 'activo' : ''}" data-id="${t.id}">
<div class="prio-dot" style="background:${t.prioridad_color};cursor:pointer" onclick="seleccionarTurno(${t.id})">${t.prioridad_codigo}</div>
<div class="turno-info" style="cursor:pointer" onclick="seleccionarTurno(${t.id})">
<div class="cod">${escHtml(t.codigo)}</div>
@@ -479,6 +509,7 @@ function renderCola(snap) {
async function llamarSiguiente() {
const btn = document.getElementById('btn-llamar');
btn.disabled = true;
mostrarToast('Consultando siguiente turno…', 'info', 1500);
try {
const res = await fetch(API + 'llamar_turno.php', {
method: 'POST',
@@ -491,8 +522,10 @@ async function llamarSiguiente() {
const turnoLlamado = json.turno ?? json.data?.turno;
if (!turnoLlamado) {
mostrarAviso('Cola vacía', 'No hay turnos en espera.');
resetPlaceholder();
return;
}
mostrarLlamando(turnoLlamado.codigo);
abrirFicha(turnoLlamado);
cargarCola();
} catch (err) {
@@ -516,6 +549,12 @@ async function seleccionarTurno(turnoId) {
// ── Llamar turno específico (cambia estado → en_recepcion) ─────────
async function llamarTurnoEspecifico(turnoId, estadoActual) {
// Mostrar feedback inmediato
const card = document.querySelector(`.cola-card[data-id="${turnoId}"]`);
if (card) {
const cod = card.querySelector('.cod')?.textContent || '';
mostrarLlamando(cod);
}
// Si ya está en recepción: re-llamar (actualiza llamado_at para que la pantalla TV reactive)
if (estadoActual === 'en_recepcion') {
try {
@@ -860,22 +899,20 @@ async function pasarALugar() {
solicitudActiva = null;
document.getElementById('ficha-turno').classList.add('d-none');
document.getElementById('badge-turno-activo').classList.add('d-none');
cargarCola();
// Mostrar aviso con link al lugar
const ph = document.getElementById('ficha-placeholder');
ph.classList.remove('d-none');
ph.innerHTML = `
<i class="fas fa-check-circle fa-3x mb-3" style="color:#22c55e"></i>
<p class="fw-semibold mb-1">Turno pasado al lugar correctamente</p>
<small class="text-muted d-block mb-3">El paciente aparece en la cola del puesto de toma de muestras</small>
<a href="<?= BASE_URL ?>erp.php?m=turnero&v=lugar" target="_blank"
class="btn btn-primary btn-sm">
<i class="fas fa-flask me-1"></i>Ir a Vista Lugar
</a>
<button class="btn btn-outline-secondary btn-sm ms-2" onclick="resetPlaceholder()">
Siguiente turno
</button>`;
mostrarToast('Turno pasado al lugar. Llamando siguiente…', 'success', 2000);
// Llamar siguiente automáticamente después de 1.5 s
setTimeout(() => {
cargarCola();
resetPlaceholder();
// Auto-llamar siguiente turno si hay cola
document.getElementById('ficha-placeholder').classList.remove('d-none');
document.getElementById('ficha-placeholder').innerHTML = `
<i class="fas fa-hourglass-half fa-3x mb-3" style="color:#3b82f6"></i>
<p class="fw-semibold mb-0">Llamando siguiente turno…</p>`;
llamarSiguiente();
}, 1500);
} catch (err) {
mostrarError(err.message);
@@ -925,10 +962,28 @@ function resetPlaceholder() {
}
function mostrarError(msg) {
alert('Error: ' + msg);
mostrarToast(msg, 'error', 4000);
}
function mostrarAviso(titulo, msg) {
alert(titulo + '\n' + msg);
mostrarToast(titulo + ': ' + msg, 'warn', 3500);
}
function mostrarLlamando(codigo) {
mostrarToast('Llamando turno ' + codigo + '…', 'info', 2000);
}
// ── Toast ────────────────────────────────────────────────────
let toastTimer = null;
function mostrarToast(msg, type = 'info', duration = 2500) {
const el = document.getElementById('rec-toast');
const ico = document.getElementById('toast-ico');
const txt = document.getElementById('toast-msg');
const icons = { success: 'fa-check-circle', info: 'fa-info-circle', warn: 'fa-exclamation-triangle', error: 'fa-times-circle' };
el.className = 'rec-toast ' + type;
ico.innerHTML = '<i class="fas ' + (icons[type] || icons.info) + '"></i>';
txt.textContent = msg;
el.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => el.classList.remove('show'), duration);
}
</script>
</body>