up
This commit is contained in:
@@ -107,16 +107,15 @@ try {
|
|||||||
$stmtItem->execute([$solicitudId, $examId]);
|
$stmtItem->execute([$solicitudId, $examId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Calcular consentimientos necesarios (sin crearlos todavía) ──
|
// ── Consentimientos: todos los formularios activos del laboratorio ──
|
||||||
// Deduplica: varios exámenes que apuntan al mismo formulario → 1 solo ítem.
|
// (no solo los vinculados a exámenes — el recepcionista elige cuáles aplican)
|
||||||
$in = implode(',', array_fill(0, count($examIds), '?'));
|
$stmt = $pdo->prepare(
|
||||||
$stmt = $pdo->prepare(
|
"SELECT id AS formulario_id, nombre AS formulario_nombre
|
||||||
"SELECT DISTINCT etc.formulario_id, f.nombre AS formulario_nombre
|
FROM lab_formularios
|
||||||
FROM exam_tipo_consentimientos etc
|
WHERE is_active = 1
|
||||||
JOIN lab_formularios f ON f.id = etc.formulario_id
|
ORDER BY nombre ASC"
|
||||||
WHERE etc.exam_tipo_id IN ({$in})"
|
|
||||||
);
|
);
|
||||||
$stmt->execute($examIds);
|
$stmt->execute();
|
||||||
$consentimientosRequeridos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$consentimientosRequeridos = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
// Agregar estado actual de cada consentimiento (si ya fue enviado antes)
|
// Agregar estado actual de cada consentimiento (si ya fue enviado antes)
|
||||||
|
|||||||
@@ -245,9 +245,7 @@ $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '')
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<span id="chip-estado" class="estado-chip conectando">
|
<span id="chip-estado" class="estado-chip" style="display:none"></span>
|
||||||
<i class="fas fa-circle-notch fa-spin"></i>Conectando…
|
|
||||||
</span>
|
|
||||||
<button id="btn-activar-sonido" onclick="activarSonido()" title="Activar sonido">
|
<button id="btn-activar-sonido" onclick="activarSonido()" title="Activar sonido">
|
||||||
<i class="fas fa-volume-mute"></i> Sonido
|
<i class="fas fa-volume-mute"></i> Sonido
|
||||||
</button>
|
</button>
|
||||||
@@ -309,7 +307,7 @@ $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '')
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ── Configuración desde URL ───────────────────────────────────
|
// ── Config desde URL ─────────────────────────────────────────
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const area = params.get('display') || 'recepcion';
|
const area = params.get('display') || 'recepcion';
|
||||||
const lugarId = params.get('lugar_id') || '';
|
const lugarId = params.get('lugar_id') || '';
|
||||||
@@ -319,7 +317,29 @@ const BASE_API = '<?= BASE_URL ?>modules/turnero/api/';
|
|||||||
document.getElementById('lbl-area').textContent =
|
document.getElementById('lbl-area').textContent =
|
||||||
area === 'lugar' && lugarId ? `Lugar #${lugarId} — Turnos` : 'Recepción — Turnos';
|
area === 'lugar' && lugarId ? `Lugar #${lugarId} — Turnos` : 'Recepción — Turnos';
|
||||||
|
|
||||||
// ── Reloj ─────────────────────────────────────────────────────
|
// ── Video publicitario desde URL ?video=URL ───────────────────
|
||||||
|
(function initVideo() {
|
||||||
|
const videoUrl = params.get('video');
|
||||||
|
if (!videoUrl) return;
|
||||||
|
const panel = document.getElementById('video-panel');
|
||||||
|
const src = document.getElementById('video-src');
|
||||||
|
const vid = document.getElementById('video-pub');
|
||||||
|
const body = document.getElementById('display-body');
|
||||||
|
src.src = videoUrl;
|
||||||
|
vid.load();
|
||||||
|
panel.style.display = 'block';
|
||||||
|
// 3 columnas: turno | video | cola
|
||||||
|
body.style.gridTemplateColumns = '1fr 320px 280px';
|
||||||
|
})();
|
||||||
|
|
||||||
|
// ── Helper: oscurecer color hex ───────────────────────────────
|
||||||
|
function shadeColor(hex, pct) {
|
||||||
|
const num = parseInt(hex.replace('#',''), 16);
|
||||||
|
const r = Math.max(0, Math.min(255, (num >> 16) + pct));
|
||||||
|
const g = Math.max(0, Math.min(255, ((num >> 8) & 0xff) + pct));
|
||||||
|
const b = Math.max(0, Math.min(255, (num & 0xff) + pct));
|
||||||
|
return `rgb(${r},${g},${b})`;
|
||||||
|
}
|
||||||
function actualizarReloj() {
|
function actualizarReloj() {
|
||||||
document.getElementById('reloj').textContent =
|
document.getElementById('reloj').textContent =
|
||||||
new Date().toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
new Date().toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||||
@@ -381,17 +401,14 @@ function playBeep() {
|
|||||||
function anunciarTurno(codigo, nombre) {
|
function anunciarTurno(codigo, nombre) {
|
||||||
// 1. Beep electrónico
|
// 1. Beep electrónico
|
||||||
playBeep();
|
playBeep();
|
||||||
// 2. Voz sintética — funciona sin interacción previa en la mayoría de navegadores
|
// 2. Voz sintética — solo el número del turno, sin el nombre
|
||||||
if ('speechSynthesis' in window) {
|
if ('speechSynthesis' in window) {
|
||||||
// Esperar a que el beep termine antes de hablar
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.speechSynthesis.cancel();
|
window.speechSynthesis.cancel();
|
||||||
const letras = codigo.split('').join(' '); // "A 0 1" para mejor pronunciación
|
const letras = codigo.split('').join(' '); // "A 0 1" para mejor pronunciación
|
||||||
let texto = `Turno ${letras}.`;
|
const utt = new SpeechSynthesisUtterance(`Turno ${letras}`);
|
||||||
if (nombre) texto += ` ${nombre}, por favor pase a recepción.`;
|
|
||||||
const utt = new SpeechSynthesisUtterance(texto);
|
|
||||||
utt.lang = 'es-CO';
|
utt.lang = 'es-CO';
|
||||||
utt.rate = 0.88;
|
utt.rate = 0.85;
|
||||||
utt.pitch = 1.05;
|
utt.pitch = 1.05;
|
||||||
utt.volume = 1;
|
utt.volume = 1;
|
||||||
window.speechSynthesis.speak(utt);
|
window.speechSynthesis.speak(utt);
|
||||||
@@ -425,7 +442,9 @@ function renderSnapshot(snap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
elCodigo.textContent = activo.codigo;
|
elCodigo.textContent = activo.codigo;
|
||||||
elCodigo.style.color = activo.prioridad_color || '';
|
elCodigo.style.color = activo.prioridad_color
|
||||||
|
? shadeColor(activo.prioridad_color, -30)
|
||||||
|
: 'var(--brand-dark)';
|
||||||
elNombre.textContent = activo.paciente_nombre || '';
|
elNombre.textContent = activo.paciente_nombre || '';
|
||||||
elPrio.style.background = (activo.prioridad_color || '#1565c0') + '22';
|
elPrio.style.background = (activo.prioridad_color || '#1565c0') + '22';
|
||||||
elPrio.style.color = activo.prioridad_color || '#1565c0';
|
elPrio.style.color = activo.prioridad_color || '#1565c0';
|
||||||
@@ -520,8 +539,8 @@ function conectarSSE() {
|
|||||||
esSource.addEventListener('open', () => {
|
esSource.addEventListener('open', () => {
|
||||||
reconnectDelay = 2000;
|
reconnectDelay = 2000;
|
||||||
sseViva = true;
|
sseViva = true;
|
||||||
chipEstado.className = 'estado-chip conectado';
|
// SSE conectado → ocultar chip (el polling garantiza actualización)
|
||||||
chipEstado.innerHTML = '<i class="fas fa-circle me-1" style="font-size:.5rem"></i>En vivo';
|
chipEstado.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
esSource.addEventListener('cola_update', (e) => {
|
esSource.addEventListener('cola_update', (e) => {
|
||||||
@@ -533,8 +552,8 @@ function conectarSSE() {
|
|||||||
|
|
||||||
esSource.addEventListener('error', () => {
|
esSource.addEventListener('error', () => {
|
||||||
sseViva = false;
|
sseViva = false;
|
||||||
chipEstado.className = 'estado-chip error';
|
// Solo mostramos chip cuando SSE falla (el polling sigue funcionando)
|
||||||
chipEstado.innerHTML = '<i class="fas fa-exclamation-circle me-1"></i>Reconectando…';
|
chipEstado.style.display = 'none'; // silencioso — polling cubre
|
||||||
esSource.close();
|
esSource.close();
|
||||||
setTimeout(conectarSSE, reconnectDelay);
|
setTimeout(conectarSSE, reconnectDelay);
|
||||||
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
reconnectDelay = Math.min(reconnectDelay * 2, 30000);
|
||||||
|
|||||||
@@ -229,23 +229,29 @@ unset($p);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ══ PASO 2: Datos opcionales ════════════════════════════════ -->
|
<!-- ══ PASO 2: Datos del paciente ═══════════════════════════════ -->
|
||||||
<div id="screen-datos" class="screen hidden">
|
<div id="screen-datos" class="screen hidden">
|
||||||
<div class="kiosko-header">
|
<div class="kiosko-header">
|
||||||
<div id="badge-prio" style="display:inline-block; padding:.5rem 1.4rem; border-radius:40px; font-size:1.2rem; font-weight:700; margin-bottom:1rem;"></div>
|
<div id="badge-prio" style="display:inline-block; padding:.5rem 1.4rem; border-radius:40px; font-size:1.2rem; font-weight:700; margin-bottom:1rem;"></div>
|
||||||
<h1>Datos de contacto</h1>
|
<h1>Ingresa tu cédula</h1>
|
||||||
<p>Opcional — para notificaciones por WhatsApp</p>
|
<p>Escribe tu número de documento de identidad</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-kiosko">
|
<div class="form-kiosko">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="inp-nombre">Nombre completo</label>
|
<label for="inp-cedula">Número de cédula <span style="color:#ef4444">*</span></label>
|
||||||
<input type="text" id="inp-nombre" class="form-control" placeholder="Ej: Juan Pérez" maxlength="120" autocomplete="off">
|
<input type="text" id="inp-cedula" class="form-control"
|
||||||
|
placeholder="Ej: 1234567890" maxlength="20"
|
||||||
|
autocomplete="off" inputmode="numeric">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="inp-cel">Celular (WhatsApp)</label>
|
<label for="inp-cel">WhatsApp
|
||||||
<input type="tel" id="inp-cel" class="form-control" placeholder="Ej: 3001234567" maxlength="20" autocomplete="off" inputmode="numeric">
|
<span style="color:#64748b;font-size:.85rem;font-weight:400"> (opcional)</span>
|
||||||
<div class="hint">Incluya código de país si es diferente a Colombia (+57)</div>
|
</label>
|
||||||
|
<input type="tel" id="inp-cel" class="form-control"
|
||||||
|
placeholder="Ej: 3001234567" maxlength="20"
|
||||||
|
autocomplete="off" inputmode="numeric">
|
||||||
|
<div class="hint">Para recibir notificaciones de su turno</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-kiosko-main" onclick="confirmarTurno()">
|
<button class="btn-kiosko-main" onclick="confirmarTurno()">
|
||||||
<i class="fas fa-ticket-alt me-2"></i>Obtener mi turno
|
<i class="fas fa-ticket-alt me-2"></i>Obtener mi turno
|
||||||
@@ -307,7 +313,7 @@ unset($p);
|
|||||||
document.getElementById('badge-prio').style.background = prioColor;
|
document.getElementById('badge-prio').style.background = prioColor;
|
||||||
|
|
||||||
mostrar('screen-datos');
|
mostrar('screen-datos');
|
||||||
document.getElementById('inp-nombre').focus();
|
document.getElementById('inp-cedula').focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function volverPrioridades() {
|
function volverPrioridades() {
|
||||||
@@ -316,12 +322,19 @@ unset($p);
|
|||||||
|
|
||||||
// ── Crear turno ───────────────────────────────────────────
|
// ── Crear turno ───────────────────────────────────────────
|
||||||
async function confirmarTurno() {
|
async function confirmarTurno() {
|
||||||
const nombre = document.getElementById('inp-nombre').value.trim();
|
const cedula = document.getElementById('inp-cedula').value.trim();
|
||||||
const cel = document.getElementById('inp-cel').value.trim();
|
const cel = document.getElementById('inp-cel').value.trim();
|
||||||
|
|
||||||
// Validación mínima de celular
|
if (!cedula) {
|
||||||
|
mostrarError('Por favor ingresa tu número de cédula.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^\d{5,15}$/.test(cedula)) {
|
||||||
|
mostrarError('La cédula debe contener solo dígitos (mínimo 5).');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cel && !/^\+?\d{7,15}$/.test(cel.replace(/\s/g, ''))) {
|
if (cel && !/^\+?\d{7,15}$/.test(cel.replace(/\s/g, ''))) {
|
||||||
mostrarError('Ingrese un número de celular válido (solo dígitos).');
|
mostrarError('Ingresa un número de WhatsApp válido.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,8 +346,8 @@ unset($p);
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
prioridad_codigo: prioCodigo,
|
prioridad_codigo: prioCodigo,
|
||||||
paciente_nombre: nombre || null,
|
paciente_nombre: cedula, // se guarda como referencia de identificación
|
||||||
paciente_cel: cel || null,
|
paciente_cel: cel || null,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -372,7 +385,7 @@ unset($p);
|
|||||||
// ── Reinicio ──────────────────────────────────────────────
|
// ── Reinicio ──────────────────────────────────────────────
|
||||||
function reiniciar() {
|
function reiniciar() {
|
||||||
clearTimeout(window._reinicioTimer);
|
clearTimeout(window._reinicioTimer);
|
||||||
document.getElementById('inp-nombre').value = '';
|
document.getElementById('inp-cedula').value = '';
|
||||||
document.getElementById('inp-cel').value = '';
|
document.getElementById('inp-cel').value = '';
|
||||||
prioCodigo = prioNombre = prioColor = '';
|
prioCodigo = prioNombre = prioColor = '';
|
||||||
mostrar('screen-prio');
|
mostrar('screen-prio');
|
||||||
|
|||||||
@@ -602,12 +602,10 @@ async function guardarSolicitud() {
|
|||||||
|
|
||||||
btn.innerHTML = '<i class="fas fa-check me-1"></i>Guardado';
|
btn.innerHTML = '<i class="fas fa-check me-1"></i>Guardado';
|
||||||
|
|
||||||
// Mostrar sección de consentimientos si aplica
|
// Mostrar consentimientos (siempre — son todos los formularios del lab)
|
||||||
if (consentimientos.length > 0) {
|
renderConsentimientos(consentimientos);
|
||||||
renderConsentimientos(consentimientos);
|
document.getElementById('sec-consentimientos').style.display = '';
|
||||||
document.getElementById('sec-consentimientos').style.display = '';
|
document.getElementById('btn-enviar-consent').classList.remove('d-none');
|
||||||
document.getElementById('btn-enviar-consent').classList.remove('d-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('btn-pasar-lugar').classList.remove('d-none');
|
document.getElementById('btn-pasar-lugar').classList.remove('d-none');
|
||||||
document.getElementById('btn-guardar').classList.add('d-none');
|
document.getElementById('btn-guardar').classList.add('d-none');
|
||||||
|
|||||||
Reference in New Issue
Block a user