Files
whatsapp/modules/turnero/views/kiosko.php
T

655 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Kiosko de turnos — pantalla táctil pública
* Ruta: /modules/turnero/views/kiosko.php
* Sin login requerido. NUNCA expone datos de otros pacientes.
*/
require_once __DIR__ . '/../../../config/config.php';
// ── Configuración visual del laboratorio ──────────────────────
$_kioskoCfg = [];
try {
$_kpdo = Database::getInstance()->getConnection();
$_krows = $_kpdo->query(
"SELECT clave, valor FROM lab_config
WHERE clave IN ('empresa_nombre','doc_logo_base64','doc_color')"
)->fetchAll(PDO::FETCH_KEY_PAIR);
$_kioskoCfg = $_krows ?: [];
} catch (\Throwable $_) {}
$_kNombre = htmlspecialchars($_kioskoCfg['empresa_nombre'] ?? 'Laboratorio Clínico');
$_kLogo = $_kioskoCfg['doc_logo_base64'] ?? '';
$_kColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_kioskoCfg['doc_color'] ?? '')
? $_kioskoCfg['doc_color'] : '#1565c0';
// ── Cargar prioridades activas desde BD ───────────────────────
$prioridades = [];
try {
$_pdo = Database::getInstance()->getConnection();
$stmt = $_pdo->query(
"SELECT id, codigo, nombre, color, icono, descripcion, orden_peso
FROM turnero_prioridades
WHERE activo = 1
ORDER BY orden_peso ASC"
);
$prioridades = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (\Throwable $e) {
// Fallback con prioridades por defecto si la BD no está disponible
$prioridades = [
['codigo'=>'A','nombre'=>'Niños', 'color'=>'#ef4444','icono'=>'fas fa-child', 'descripcion'=>'Pacientes menores de edad'],
['codigo'=>'B','nombre'=>'Embarazadas', 'color'=>'#f97316','icono'=>'fas fa-heart', 'descripcion'=>'Mujeres en estado de embarazo'],
['codigo'=>'C','nombre'=>'Adulto mayor', 'color'=>'#eab308','icono'=>'fas fa-person-cane', 'descripcion'=>'Mayores de 60 años'],
['codigo'=>'D','nombre'=>'Discapacidad', 'color'=>'#8b5cf6','icono'=>'fas fa-wheelchair', 'descripcion'=>'Personas con discapacidad'],
['codigo'=>'E','nombre'=>'Paciente general', 'color'=>'#3b82f6','icono'=>'fas fa-user', 'descripcion'=>'Atención general'],
['codigo'=>'F','nombre'=>'Muestra pendiente','color'=>'#6b7280','icono'=>'fas fa-vial', 'descripcion'=>'Entrega de muestra tomada'],
];
}
// Iconos por defecto por código (si la BD no tiene el campo icono populado)
$iconosDefecto = [
'A' => 'fas fa-child',
'B' => 'fas fa-heart',
'C' => 'fas fa-person-cane',
'D' => 'fas fa-wheelchair',
'E' => 'fas fa-user',
'F' => 'fas fa-vial',
];
foreach ($prioridades as &$p) {
if (empty($p['icono'])) {
$p['icono'] = $iconosDefecto[$p['codigo']] ?? 'fas fa-ticket-alt';
}
}
unset($p);
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Kiosko <?= $_kNombre ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--brand: <?= $_kColor ?>;
--brand-dark: color-mix(in srgb, <?= $_kColor ?> 75%, #000);
--brand-light: color-mix(in srgb, <?= $_kColor ?> 10%, #fff);
--brand-mid: color-mix(in srgb, <?= $_kColor ?> 22%, #fff);
}
/* ── Reset fullscreen ── */
*, *::before, *::after { box-sizing: border-box; }
html, body {
margin: 0; padding: 0;
height: 100%; width: 100%;
overflow: hidden;
background: var(--brand-light);
color: #1e293b;
font-family: 'Segoe UI', system-ui, sans-serif;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
/* ── Layout general ── */
.kiosko-wrap {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
/* ── Header con logo ── */
.kiosko-topbar {
background: var(--brand);
padding: 14px 28px;
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.kiosko-topbar .logo-img {
height: 48px;
max-width: 140px;
object-fit: contain;
background: #fff;
border-radius: 8px;
padding: 4px 6px;
}
.kiosko-topbar .logo-fallback {
width: 46px; height: 46px;
background: rgba(255,255,255,.2);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; font-size: 1.3rem;
}
.kiosko-topbar .lab-nombre {
color: #fff;
font-size: clamp(1.15rem, 2.8vw, 1.55rem);
font-weight: 700;
letter-spacing: -.3px;
flex: 1;
}
.kiosko-topbar .turno-badge {
background: rgba(255,255,255,.15);
border: 1.5px solid rgba(255,255,255,.3);
color: #fff;
border-radius: 8px;
padding: 4px 12px;
font-size: .95rem;
font-weight: 600;
}
.btn-fullscreen {
background: rgba(255,255,255,.15);
border: 1.5px solid rgba(255,255,255,.3);
color: #fff;
border-radius: 8px;
padding: 6px 10px;
font-size: 1.15rem;
cursor: pointer;
transition: background .15s;
flex-shrink: 0;
line-height: 1;
}
.btn-fullscreen:hover { background: rgba(255,255,255,.28); }
/* ── Pantalla / paso ── */
.screen {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 1.5rem;
transition: opacity .3s ease, transform .3s ease;
overflow: hidden;
}
.screen.hidden { opacity: 0; pointer-events: none; transform: scale(.98); position: absolute; }
.screen.visible { opacity: 1; pointer-events: all; transform: scale(1); position: relative; }
/* ── Instrucción central ── */
.kiosko-titulo {
font-size: clamp(1.55rem, 3.8vw, 2.35rem);
font-weight: 700;
color: var(--brand-dark);
text-align: center;
margin-bottom: .4rem;
}
.kiosko-subtitulo {
font-size: clamp(1rem, 2.1vw, 1.2rem);
color: #64748b;
text-align: center;
margin-bottom: 2rem;
}
/* ── Grid de prioridades ── */
.prioridad-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
width: 100%;
max-width: 860px;
}
.btn-prioridad {
border: 2px solid transparent;
border-radius: 18px;
padding: 1.6rem 1.2rem;
cursor: pointer;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: .5rem;
transition: transform .12s, box-shadow .12s, filter .12s;
font-weight: 600;
box-shadow: 0 2px 12px rgba(0,0,0,.10);
-webkit-user-select: none; user-select: none;
background: #fff;
}
.btn-prioridad:active { transform: scale(.95); filter: brightness(.93); }
.btn-prioridad:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,.13); }
.btn-prioridad .icon-wrap {
width: 90px; height: 90px;
border-radius: 16px;
display: flex; align-items: center; justify-content: center;
font-size: 3rem;
color: #fff;
margin-bottom: .2rem;
}
.btn-prioridad .letra { font-size: clamp(1.15rem, 2.8vw, 1.55rem); font-weight: 800; color: #1e293b; }
.btn-prioridad .nombre { font-size: clamp(.97rem, 2.1vw, 1.15rem); color: #334155; text-align: center; }
.btn-prioridad .desc { font-size: clamp(.83rem, 1.6vw, .95rem); color: #94a3b8; text-align: center; }
/* ── Formulario de datos ── */
.form-kiosko { width: 100%; max-width: 480px; }
.badge-prio-mini {
display: inline-flex; align-items: center; gap: 8px;
padding: .45rem 1.2rem; border-radius: 99px;
font-size: 1.15rem; font-weight: 700; color: #fff;
margin-bottom: 1.5rem;
}
.form-kiosko label { color: #475569; font-size: 1.1rem; margin-bottom: .35rem; font-weight: 500; }
.form-kiosko .form-control {
background: #fff;
border: 1.5px solid #e2e8f0;
color: #1e293b;
border-radius: 14px;
padding: .85rem 1.1rem;
font-size: 1.2rem;
box-shadow: 0 1px 4px rgba(0,0,0,.05);
transition: border-color .2s, box-shadow .2s;
}
.form-kiosko .form-control:focus {
border-color: var(--brand);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 20%, transparent);
outline: none;
}
.form-kiosko .hint { color: #94a3b8; font-size: .97rem; margin-top: .35rem; }
.btn-kiosko-main {
border: none; border-radius: 14px;
padding: 1rem 2rem; font-size: 1.2rem; font-weight: 700;
cursor: pointer; color: #fff;
background: var(--brand);
box-shadow: 0 4px 16px color-mix(in srgb, var(--brand) 40%, transparent);
transition: transform .12s, box-shadow .12s, filter .12s;
width: 100%; margin-top: 1rem;
}
.btn-kiosko-main:active { transform: scale(.97); filter: brightness(.9); }
.btn-kiosko-back {
background: transparent;
border: 1.5px solid #e2e8f0;
color: #94a3b8; border-radius: 12px; padding: .7rem 1.5rem;
font-size: 1.07rem; cursor: pointer; margin-top: .6rem; width: 100%;
transition: background .12s, border-color .12s;
}
.btn-kiosko-back:hover { background: var(--brand-mid); border-color: var(--brand); color: var(--brand-dark); }
/* ── Print: ticket para impresora térmica SRP-B300 ── */
@media print {
@page { margin: 1mm 2mm; size: 80mm auto; }
body { background: #fff !important; font-family: Arial, Helvetica, sans-serif !important; -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; -webkit-font-smoothing: antialiased !important; }
.kiosko-topbar, .kiosko-footer, .spinner-overlay, .toast-error,
.btn-nuevo, #screen-prio, #screen-datos { display: none !important; }
#screen-ticket { display: block !important; opacity: 1 !important; position: static !important; }
.kiosko-wrap { height: auto !important; }
.screen { padding: 0 !important; min-height: 0 !important; }
.ticket-box {
width: 72mm !important; max-width: 72mm !important;
padding: 0 !important; margin: 0 auto !important;
box-shadow: none !important; border: none !important;
border-top: none !important; border-radius: 0 !important;
text-align: center !important;
}
.ticket-label { font-size: 10pt !important; font-weight: 600 !important; letter-spacing: 0.03em !important; margin-bottom: 0 !important; padding-top: 2pt !important; }
.ticket-codigo { font-size: 22pt !important; font-weight: 700 !important; letter-spacing: 0 !important; line-height: 1 !important; padding: 0 !important; }
.ticket-prio-badge { font-size: 10pt !important; font-weight: 600 !important; padding: 0 !important; margin: 0 !important; background: transparent !important; color: #000 !important; }
.ticket-pos { margin: 2pt 0 !important; padding: 0 !important; border-radius: 0 !important; background: transparent !important; }
.ticket-pos .lbl { font-size: 9pt !important; font-weight: 600 !important; letter-spacing: 0.03em !important; }
.ticket-pos .num { font-size: 12pt !important; font-weight: 700 !important; color: #000 !important; }
.ticket-instruc { font-size: 9pt !important; font-weight: 500 !important; margin: 2pt 0 !important; line-height: 1.3 !important; padding-bottom: 2pt !important; }
.ticket-codigo, .ticket-label, .ticket-prio-badge,
.ticket-pos, .ticket-pos .lbl, .ticket-pos .num,
.ticket-instruc { color: #000 !important; }
}
/* ── Ticket ── */
.ticket-box {
background: #fff;
border-radius: 24px;
padding: 2.5rem 3rem;
text-align: center;
box-shadow: 0 4px 30px rgba(0,0,0,.10);
max-width: 460px; width: 100%;
border-top: 6px solid var(--brand);
}
.ticket-label { color: #94a3b8; font-size: 1rem; text-transform: uppercase;
letter-spacing: .08em; margin-bottom: .3rem; }
.ticket-codigo {
font-size: clamp(4rem, 18vw, 9rem);
font-weight: 900; line-height: 1;
letter-spacing: -2px;
color: var(--brand);
}
.ticket-prio-badge {
display: inline-block; padding: .3rem 1.1rem;
border-radius: 99px; font-weight: 700; color: #fff;
font-size: 1.1rem; margin-top: .8rem;
}
.ticket-pos {
margin-top: 1.2rem;
background: var(--brand-light);
border-radius: 12px;
padding: .8rem;
}
.ticket-pos .lbl { font-size: .93rem; color: #64748b; text-transform: uppercase; letter-spacing: .06em; }
.ticket-pos .num { font-size: 1.95rem; font-weight: 800; color: var(--brand-dark); }
.ticket-instruc { margin-top: 1.4rem; color: #94a3b8; font-size: 1.03rem; line-height: 1.5; }
.btn-nuevo {
margin-top: 1.8rem; border: 2px solid var(--brand-mid);
border-radius: 12px; padding: .75rem 2rem;
font-size: 1.1rem; font-weight: 600; cursor: pointer;
color: var(--brand-dark); background: #fff;
transition: background .15s, border-color .15s;
}
.btn-nuevo:hover { background: var(--brand-light); border-color: var(--brand); }
/* ── Spinner ── */
.spinner-overlay {
position: fixed; inset: 0;
background: rgba(248,250,252,.8);
display: flex; align-items: center; justify-content: center;
z-index: 999; opacity: 0; pointer-events: none;
transition: opacity .2s;
}
.spinner-overlay.active { opacity: 1; pointer-events: all; }
.spinner {
width: 52px; height: 52px;
border: 4px solid var(--brand-mid);
border-top-color: var(--brand);
border-radius: 50%;
animation: spin .75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ── Toast de error ── */
.toast-error {
position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%);
background: #ef4444; color: #fff; padding: .75rem 1.5rem;
border-radius: 12px; font-size: 1.07rem; font-weight: 600;
box-shadow: 0 4px 20px rgba(0,0,0,.2);
opacity: 0; pointer-events: none; transition: opacity .25s;
z-index: 1000; white-space: nowrap;
}
.toast-error.show { opacity: 1; }
/* ── Footer discreto ── */
.kiosko-footer {
text-align: center;
padding: 8px;
font-size: .85rem;
color: color-mix(in srgb, var(--brand) 55%, #fff);
background: var(--brand-mid);
flex-shrink: 0;
}
/* ── Responsive ── */
@media (min-height: 800px) {
.prioridad-grid { gap: 1.3rem; }
.btn-prioridad { padding: 1.9rem 1.4rem; }
}
@media (max-width: 480px) {
.kiosko-topbar { padding: 10px 16px; }
.ticket-box { padding: 1.8rem 1.5rem; }
}
</style>
</head>
<body>
<div class="kiosko-wrap">
<!-- ══ Top bar con logo ════════════════════════════════════════ -->
<header class="kiosko-topbar">
<?php if ($_kLogo): ?>
<img src="<?= htmlspecialchars($_kLogo) ?>" alt="Logo" class="logo-img">
<?php else: ?>
<div class="logo-fallback"><i class="fas fa-flask"></i></div>
<?php endif; ?>
<div class="lab-nombre"><?= $_kNombre ?></div>
<div class="turno-badge"><i class="fas fa-ticket-alt me-1"></i>Turnero</div>
<button class="btn-fullscreen" id="btn-fs" onclick="toggleFullscreen()" title="Pantalla completa">
<i class="fas fa-expand" id="fs-icon"></i>
</button>
</header>
<!-- ══ PASO 1: Selección de prioridad ══════════════════════════ -->
<div id="screen-prio" class="screen visible">
<div class="kiosko-titulo">Seleccione su tipo de atención</div>
<div class="kiosko-subtitulo">Toque la categoría que le corresponde para obtener su turno</div>
<div class="prioridad-grid">
<?php foreach ($prioridades as $prio): ?>
<button
class="btn-prioridad"
style="border-color:<?= htmlspecialchars($prio['color']) ?>22"
data-codigo="<?= htmlspecialchars($prio['codigo']) ?>"
data-nombre="<?= htmlspecialchars($prio['nombre']) ?>"
data-color="<?= htmlspecialchars($prio['color']) ?>"
onclick="seleccionarPrioridad(this)"
>
<div class="icon-wrap" style="background:<?= htmlspecialchars($prio['color']) ?>">
<i class="<?= htmlspecialchars($prio['icono']) ?>"></i>
</div>
<span class="letra" style="color:<?= htmlspecialchars($prio['color']) ?>"><?= htmlspecialchars($prio['codigo']) ?></span>
<span class="nombre"><?= htmlspecialchars($prio['nombre']) ?></span>
<?php if (!empty($prio['descripcion'])): ?>
<span class="desc"><?= htmlspecialchars($prio['descripcion']) ?></span>
<?php endif; ?>
</button>
<?php endforeach; ?>
</div>
</div>
<!-- ══ PASO 2: Datos del paciente ═══════════════════════════════ -->
<div id="screen-datos" class="screen hidden">
<div id="badge-prio" class="badge-prio-mini"></div>
<div class="kiosko-titulo">Ingrese su número de cédula</div>
<div class="kiosko-subtitulo">Le ayudará a identificar su turno correctamente</div>
<div class="form-kiosko">
<div class="mb-3">
<label for="inp-cedula">Número de cédula <span style="color:#ef4444">*</span></label>
<input type="text" id="inp-cedula" class="form-control"
placeholder="Ej: 1234567890" maxlength="20"
autocomplete="off" inputmode="numeric">
</div>
<div class="mb-3">
<label for="inp-cel">WhatsApp
<span style="color:#94a3b8;font-size:.82rem;font-weight:400"> (opcional)</span>
</label>
<input type="tel" id="inp-cel" class="form-control"
placeholder="Ej: 3001234567" maxlength="20"
autocomplete="off" inputmode="numeric">
<div class="hint"><i class="fas fa-bell me-1"></i>Para recibir notificaciones de su turno</div>
</div>
<button class="btn-kiosko-main" onclick="confirmarTurno()">
<i class="fas fa-ticket-alt me-2"></i>Obtener mi turno
</button>
<button class="btn-kiosko-back" onclick="volverPrioridades()">
<i class="fas fa-arrow-left me-1"></i>Cambiar tipo de atención
</button>
</div>
</div>
<!-- ══ PASO 3: Ticket asignado ══════════════════════════════════ -->
<div id="screen-ticket" class="screen hidden">
<div class="ticket-box">
<div class="ticket-label">Su número de turno es</div>
<div id="tick-codigo" class="ticket-codigo">—</div>
<div id="tick-prio-badge"></div>
<div id="tick-nombre" style="font-size:0.95rem;margin:4pt 0 2pt;font-weight:600;"></div>
<div class="ticket-instruc">
Por favor espere a ser llamado.<br>
Recuerde traer su documento de identidad y la orden médica.
</div>
</div>
<button class="btn-nuevo" onclick="seleccionarOtraPrioridad()">
<i class="fas fa-plus me-1"></i>Nuevo turno
</button>
</div>
<!-- ══ Footer ═════════════════════════════════════════════════ -->
<footer class="kiosko-footer"><?= $_kNombre ?> · Sistema de Turnos</footer>
</div><!-- /kiosko-wrap -->
<!-- ══ Spinner ════════════════════════════════════════════════ -->
<div class="spinner-overlay" id="spinner"><div class="spinner"></div></div>
<!-- ══ Toast de error ════════════════════════════════════════ -->
<div class="toast-error" id="toast-error"></div>
<script>
let prioCodigo = '';
let prioNombre = '';
let prioColor = '';
const API_URL = '<?= BASE_URL ?>modules/turnero/api/create_turno.php';
function mostrar(id) {
document.querySelectorAll('.screen').forEach(s => {
s.classList.toggle('visible', s.id === id);
s.classList.toggle('hidden', s.id !== id);
});
}
// ── Detector de escáner ──────────────────────────────────────────
// Escribir a mano NUNCA dispara el turno (la persona oprime el botón).
// Solo el lector de cédula lo dispara: mete los dígitos en ráfaga
// (<35 ms entre cada uno, imposible a mano) y/o termina con Enter.
let _keyTimes = []; // timestamps de cada pulsación
let _timerRafaga = null;
let _timerInactividad = null;
const _inpCedula = document.getElementById('inp-cedula');
// Solo es ráfaga de escáner si TODAS las pulsaciones (≥5) llegaron <35 ms
function esRafagaScanner() {
if (_keyTimes.length < 5) return false;
for (let i = 1; i < _keyTimes.length; i++) {
if (_keyTimes[i] - _keyTimes[i - 1] > 35) return false;
}
return true;
}
_inpCedula.addEventListener('input', () => {
resetInactividad();
_keyTimes.push(Date.now());
if (_keyTimes.length > 25) _keyTimes.shift();
// Tras una breve pausa revisamos: si fue ráfaga → escáner → disparar.
// Si fue escritura a mano (gaps grandes) esRafagaScanner() = false → no dispara.
clearTimeout(_timerRafaga);
_timerRafaga = setTimeout(() => {
const val = _inpCedula.value.trim();
if (/^\d{5,15}$/.test(val) && esRafagaScanner()) confirmarTurno();
}, 120);
});
// El lector casi siempre envía Enter al final → disparar de inmediato.
_inpCedula.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const val = _inpCedula.value.trim();
if (/^\d{5,15}$/.test(val)) confirmarTurno();
}
});
function resetInactividad() {
clearTimeout(_timerInactividad);
_timerInactividad = setTimeout(() => {
seleccionarOtraPrioridad();
}, 90000); // 90 s sin actividad → vuelve al inicio
}
function seleccionarPrioridad(btn) {
prioCodigo = btn.dataset.codigo;
prioNombre = btn.dataset.nombre;
prioColor = btn.dataset.color;
const badge = document.getElementById('badge-prio');
badge.textContent = prioCodigo + ' — ' + prioNombre;
badge.style.background = prioColor;
mostrar('screen-datos');
const inpCedula = document.getElementById('inp-cedula');
inpCedula.focus();
inpCedula.value = '';
_keyTimes = [];
clearTimeout(_timerRafaga);
resetInactividad();
}
function volverPrioridades() { clearTimeout(_timerInactividad); mostrar('screen-prio'); }
let _enviando = false;
async function confirmarTurno() {
if (_enviando) return; // evita doble disparo (ráfaga + Enter)
clearTimeout(_timerRafaga);
clearTimeout(_timerInactividad);
const cedula = document.getElementById('inp-cedula').value.trim();
const cel = document.getElementById('inp-cel').value.trim();
if (!cedula) { mostrarError('Por favor ingrese su número de cédula.'); return; }
if (!/^\d{5,15}$/.test(cedula)) { mostrarError('La cédula debe tener solo dígitos (mínimo 5).'); return; }
if (cel && !/^\+?\d{7,15}$/.test(cel.replace(/\s/g,''))) { mostrarError('Ingrese un número de WhatsApp válido.'); return; }
_enviando = true;
setSpinner(true);
try {
const res = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prioridad_codigo: prioCodigo, paciente_nombre: cedula, paciente_cel: cel || null }),
});
const json = await res.json();
if (!json.ok) throw new Error(json.error ?? 'Error al crear turno');
mostrarTicket(json.turno ?? json.data?.turno);
} catch (err) {
mostrarError(err.message);
setSpinner(false);
} finally {
_enviando = false;
}
}
function mostrarTicket(t) {
setSpinner(false);
document.getElementById('tick-codigo').textContent = t.codigo;
document.getElementById('tick-codigo').style.color = prioColor;
document.getElementById('tick-nombre').textContent = t.paciente_nombre || '';
document.getElementById('tick-prio-badge').innerHTML =
`<span class="ticket-prio-badge" style="background:${prioColor}">${prioCodigo} — ${prioNombre}</span>`;
mostrar('screen-ticket');
// Imprimir automáticamente
setTimeout(() => window.print(), 300);
clearTimeout(window._timer);
window._timer = setTimeout(seleccionarOtraPrioridad, 5000);
}
function seleccionarOtraPrioridad() {
clearTimeout(window._timer);
clearTimeout(_timerInactividad);
document.getElementById('inp-cedula').value = '';
document.getElementById('inp-cel').value = '';
prioCodigo = prioNombre = prioColor = '';
mostrar('screen-prio');
}
function setSpinner(on) { document.getElementById('spinner').classList.toggle('active', on); }
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen().catch(() => {});
}
}
document.addEventListener('fullscreenchange', () => {
const icon = document.getElementById('fs-icon');
if (document.fullscreenElement) {
icon.classList.replace('fa-expand', 'fa-compress');
} else {
icon.classList.replace('fa-compress', 'fa-expand');
}
});
function mostrarError(msg) {
const el = document.getElementById('toast-error');
el.textContent = msg;
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 4000);
}
</script>
</body>
</html>