@@ -1199,11 +1228,39 @@ const CONSENT_LBL = { }; // ── Countdown en tarjetas de cola (toma progresiva) ────────── +const _tomaAlertados = new Set(); +let _bannerTurnoId = null; + +function _playBeepLugar() { + try { + const ctx = new (window.AudioContext || window.webkitAudioContext)(); + [880, 1100, 880, 1100].forEach(function(freq, i) { + const osc = ctx.createOscillator(), gain = ctx.createGain(); + osc.connect(gain); gain.connect(ctx.destination); + osc.frequency.value = freq; + const t0 = ctx.currentTime + i * 0.22; + gain.gain.setValueAtTime(0.7, t0); + gain.gain.exponentialRampToValueAtTime(0.001, t0 + 0.18); + osc.start(t0); osc.stop(t0 + 0.18); + }); + } catch(e) {} +} + +function verPacienteToma() { + cerrarBannerToma(); + if (_bannerTurnoId) seleccionarSinLlamar(_bannerTurnoId); +} +function cerrarBannerToma() { + const b = document.getElementById('banner-toma-alerta'); + if (b) b.classList.remove('show'); +} + setInterval(function() { const ahora = Date.now(); document.querySelectorAll('.toma-prog-chip[data-toma-at]').forEach(function(chip) { - const at = chip.dataset.tomaAt; - const txt = chip.querySelector('.toma-cd-txt'); + const at = chip.dataset.tomaAt; + const turnoId = chip.dataset.turnoId ? parseInt(chip.dataset.turnoId) : null; + const txt = chip.querySelector('.toma-cd-txt'); if (!at || !txt) return; const diff = Math.floor((new Date(at.replace(' ','T')).getTime() - ahora) / 1000); const card = chip.closest('.cola-card'); @@ -1211,7 +1268,23 @@ setInterval(function() { txt.textContent = '¡Ahora!'; chip.className = 'toma-prog-chip crit'; if (card) card.classList.add('crit-card'); + // Alerta única por turno + if (turnoId && !_tomaAlertados.has(turnoId)) { + _tomaAlertados.add(turnoId); + _bannerTurnoId = turnoId; + const pacNombre = card?.querySelector('.pac')?.textContent || 'Paciente'; + const banner = document.getElementById('banner-toma-alerta'); + const bannerTxt = document.getElementById('banner-toma-txt'); + if (banner && bannerTxt) { + bannerTxt.textContent = '¡Hora de la siguiente muestra! — ' + pacNombre; + banner.classList.add('show'); + } + _playBeepLugar(); + if (navigator.vibrate) navigator.vibrate([400, 150, 400]); + } } else { + // Si el tiempo se actualizó (nueva toma programada), resetear la alerta + if (turnoId) _tomaAlertados.delete(turnoId); const m = Math.floor(diff / 60), s = diff % 60; txt.textContent = m + ':' + String(s).padStart(2,'0'); chip.className = diff < 120 ? 'toma-prog-chip warn' : 'toma-prog-chip ok'; @@ -1236,7 +1309,7 @@ setInterval(() => { row.classList.add('alerta'); if (!info.alertado) { info.alertado = true; - try { new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAA...').play().catch(()=>{}); } catch(_) {} + _playBeepLugar(); } } else { row.classList.remove('alerta'); @@ -1466,6 +1539,8 @@ window.addEventListener('message', function(e) { _tomaProgresivaActiva = false; _setBtnCerrarConsent(true); cerrarModalConsentimiento(true); + cerrarBannerToma(); + _tomaAlertados.clear(); mostrarToast('Tomas completadas ✓', 'success', 3000); if (turnoActivo) actualizarConsentimientos(turnoActivo.id); } diff --git a/ver_formulario_enviado.php b/ver_formulario_enviado.php index 5b6593c..63937b4 100644 --- a/ver_formulario_enviado.php +++ b/ver_formulario_enviado.php @@ -246,21 +246,46 @@ $_soloFirmaPro = ($modoTurnero && !empty($tcRow['es_toma_progresiva'])) || (!empty(array_filter($esquema, fn($c) => ($c['tipo'] ?? '') === 'firma_profesional')) && empty(array_filter($esquema, fn($c) => ($c['tipo'] ?? '') === 'firma'))); -// Muestras Prolongadas: detectar secciones hora+firma_profesional con intervalos de minutos -// Agrupa por prefijo de sección (ej. "Taukit", "Heliprobe") para calcular esperas correctas +// Muestras Prolongadas: detectar secciones hora+firma_profesional con intervalos de minutos. +// Agrupa por condición del separador para no mezclar Glicemia y Cortisol (ambas usan prefijo "Toma"). $_mpMap = []; if ($modoTurnero && $embebido && $_soloFirmaPro) { - $_mpSecs = []; $_mpLabel = null; $_mpMin = null; $_mpHora = null; + $_mpSecs = []; $_mpLabel = null; $_mpMin = null; $_mpHoraFija = null; $_mpHora = null; + $_mpCondKey = null; $_mpCondDisplay = null; foreach ($esquema as $_c) { $_t = $_c['tipo'] ?? ''; if ($_t === 'separador') { - $_mpLabel = $_c['label'] ?? ''; $_mpMin = null; $_mpHora = null; - if (preg_match('/[Mm]inuto\s+(\d+)/u', $_mpLabel, $_mx)) $_mpMin = (int)$_mx[1]; - } elseif ($_t === 'hora' && $_mpMin !== null) { + $_mpLabel = $_c['label'] ?? ''; $_mpMin = null; $_mpHoraFija = null; $_mpHora = null; + $_mpCond = $_c['condicion'] ?? null; + // Clave de agrupación: usa valores de condición para distinguir Glicemia vs Cortisol + if ($_mpCond) { + $_cv = $_mpCond['valores'] ?? ($_mpCond['valor'] ? [$_mpCond['valor']] : []); + $_mpCondKey = implode('|', $_cv); + $_mpCondDisplay = trim(explode('·', $_mpLabel)[0] ?? ''); + } else { + $_mpCondKey = $_mpCondDisplay = trim(explode('·', $_mpLabel)[0] ?? ''); + } + if (preg_match('/[Mm]inuto\s+(\d+)/u', $_mpLabel, $_mx)) { + $_mpMin = (int)$_mx[1]; + } elseif (preg_match('/(\d{1,2}):(\d{2})\s*(a\.?m\.?|p\.?m\.?)/i', $_mpLabel, $_mx)) { + $_h = (int)$_mx[1]; $_m2 = (int)$_mx[2]; + $_pm = strtolower(preg_replace('/[^apm]/i', '', $_mx[3])) === 'pm'; + if ($_pm && $_h < 12) $_h += 12; + $_mpHoraFija = sprintf('%02d:%02d', $_h, $_m2); + $_mpMin = PHP_INT_MAX; // sentinel: va después de todos los Minuto X + } + } elseif ($_t === 'hora' && ($_mpMin !== null || $_mpHoraFija !== null)) { $_mpHora = $_c['id'] ?? null; - } elseif ($_t === 'firma_profesional' && $_mpHora !== null && $_mpMin !== null) { - $_prefix = trim(explode('·', $_mpLabel)[0] ?? ''); - $_mpSecs[] = ['label' => $_mpLabel, 'min' => $_mpMin, 'hora' => $_mpHora, 'firma' => $_c['id'], 'tipo' => $_prefix]; + } elseif ($_t === 'firma_profesional' && $_mpHora !== null && ($_mpMin !== null || $_mpHoraFija !== null)) { + $_mpSecs[] = [ + 'label' => $_mpLabel, + 'min' => $_mpMin ?? PHP_INT_MAX, + 'hora_fija' => $_mpHoraFija, + 'hora' => $_mpHora, + 'firma' => $_c['id'], + 'tipo' => $_mpCondKey, + 'display' => $_mpCondDisplay, + ]; $_mpHora = null; } } @@ -271,11 +296,10 @@ if ($modoTurnero && $embebido && $_soloFirmaPro) { $_s = $_secs[$i]; $_nx = $_secs[$i + 1] ?? null; $_mpMap[$_s['firma']] = [ 'hora_campo' => $_s['hora'], - 'esperar_min' => $_nx ? ($_nx['min'] - $_s['min']) : 0, 'next_label' => $_nx ? $_nx['label'] : null, 'next_firma_id' => $_nx ? $_nx['firma'] : null, 'is_last' => !$_nx, - 'exam_type' => $_tipo, + 'exam_type' => $_s['display'], ]; } } @@ -1190,14 +1214,13 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) { widget.replaceWith(box); if (data.completado) { - // Toma completada: mostrar mensaje y cerrar modal automáticamente - var ok = document.createElement('div'); - ok.className = 'alert alert-success mt-4 d-flex align-items-center gap-2'; - ok.innerHTML = '
Toma ' + (entry.exam_type || '') + ' completada.
Cerrando...
'; - document.querySelector('.doc-body').appendChild(ok); + // Toma completada: mostrar resumen de tomas y cerrar modal + var cd = document.getElementById('mp-countdown'); + if (cd) { clearInterval(cd._mpTick); cd.remove(); } + _mpMostrarResumen(entry.exam_type || 'Examen'); setTimeout(function() { try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {} - }, 1200); + }, 2200); } else if (data.siguiente_toma_at) { var targetMs = new Date(data.siguiente_toma_at.replace(' ', 'T')).getTime(); var minsLeft = Math.max(1, Math.round((targetMs - Date.now()) / 60000)); @@ -1290,43 +1313,52 @@ function _mpIniciarCountdown(minutos, nextLabel, nextFirmaId) { }, 1000); } -function _mpMostrarFinalizar(examType) { - if (document.getElementById('mp-finalizar')) return; +function _mpMostrarResumen(examType) { + if (document.getElementById('mp-resumen')) return; + // Recopilar horas y resultados de las tomas visibles + var rows = []; + if (window._muestrasMap) { + Object.keys(window._muestrasMap).forEach(function(fid) { + var entry = window._muestrasMap[fid]; + if (!entry || entry.exam_type !== examType) return; + var horaEl = entry.hora_campo ? document.querySelector('[name="' + entry.hora_campo + '"]') : null; + var horaVal = horaEl ? horaEl.value : ''; + // Buscar campo resultado adyacente (previo al firma widget) + var fpw = document.getElementById('fpw-' + fid); + var resVal = ''; + if (fpw) { + var prev = fpw.previousElementSibling; + while (prev) { + var inp = prev.querySelector && prev.querySelector('input[type="number"],input[type="text"]'); + if (inp) { resVal = inp.value; break; } + prev = prev.previousElementSibling; + } + } + // Separador label para esta firma + var secLabel = entry.next_label || ('Toma ' + fid); + // Si tiene next_label del ANTERIOR, buscamos la label de esta entrada en el DOM + var sepEl = document.querySelector('[data-campo-id]'); + rows.push({ hora: horaVal, resultado: resVal, firmado: !!(window._mpSavedFirmas && window._mpSavedFirmas[fid]) }); + }); + } + var wrap = document.createElement('div'); + wrap.id = 'mp-resumen'; + wrap.className = 'alert alert-success mt-4'; + var rowsHtml = rows.length + ? '' + + '' + + rows.map(function(r) { + return '' + + '' + + ''; + }).join('') + '
HoraResultado
' + (r.hora || '—') + '' + (r.resultado || '—') + '
' : ''; + wrap.innerHTML = '
' + + '
' + (examType || 'Examen') + ' completado.
' + + 'Todas las tomas registradas. Cerrando...
' + rowsHtml; + document.querySelector('.doc-body').appendChild(wrap); + // Ocultar countdown si queda var cd = document.getElementById('mp-countdown'); if (cd) { clearInterval(cd._mpTick); cd.remove(); } - var wrap = document.createElement('div'); - wrap.id = 'mp-finalizar'; - wrap.style.cssText = 'position:sticky;bottom:12px;margin:12px 0;text-align:center;z-index:50'; - var btn = document.createElement('button'); - btn.className = 'btn btn-success fw-bold px-4 py-2'; - btn.style.fontSize = '1rem'; - btn.innerHTML = 'Finalizar toma · ' + (examType || 'Muestras'); - btn.addEventListener('click', function() { - btn.disabled = true; - btn.innerHTML = 'Guardando...'; - var dr = {}; - document.querySelectorAll('[name]').forEach(function(el) { - var n = el.name.endsWith('[]') ? el.name.slice(0,-2) : el.name; - if (el.type==='checkbox'){ if(el.checked){ if(!Array.isArray(dr[n]))dr[n]=[]; dr[n].push(el.value); } } - else if (el.type==='radio'){ if(el.checked) dr[n]=el.value; } - else if (el.value!=='') dr[n]=el.value; - }); - Object.assign(dr, window._mpSavedFirmas || {}); - fetch(window.location.href, { - method:'POST', headers:{'Content-Type':'application/json'}, - body: JSON.stringify({ mp_completar: true, datos_respuestas: dr }) - }) - .then(function(r){ return r.json(); }) - .then(function(data) { - if (data.ok) { - wrap.innerHTML = '
Toma completada correctamente.
'; - try { window.parent.postMessage({ type:'turneroFirmado' }, '*'); } catch(e) {} - } - }) - .catch(function(){}); - }); - wrap.appendChild(btn); - document.querySelector('.doc-body').appendChild(wrap); } /* ── Canvas firma del profesional ───────────────────────────────── */