Mejoras F-LAB-28 tomas prolongadas: bugfixes + UX + datos

- guardar_toma.php: reconoce firma campo_id_svg (compat. registros históricos)
- get_consentimientos.php: mismo fix para conteo tomas_firmadas
- ver_formulario_enviado.php: $_mpMap incluye hora fija (Cortisol 3pm/4pm),
  agrupa por condición de separador para no mezclar Glicemia/Cortisol,
  muestra resumen de tomas al completar antes de cerrar el modal
- lugar.php: banner rojo + beep cuando el countdown de una toma llega a cero,
  resetea alerta al cerrar el modal; función _playBeepLugar() compartida

DB Form 15: +10 campos (firma_profesional Minuto 60 Glicemia + campo
Resultado numérico en cada toma Glicemia y Cortisol)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-09 20:26:12 -05:00
co-authored by Claude Sonnet 4.6
parent 848dd8e4d0
commit fdc04d615c
4 changed files with 169 additions and 59 deletions
+2 -1
View File
@@ -118,7 +118,8 @@ foreach ($consentimientos as &$c) {
$tomasTotal = count($relevantIds);
foreach ($relevantIds as $fid) {
if (!empty($dr[$fid]) && strlen($dr[$fid]) > 10) $tomasFirm++;
if ((!empty($dr[$fid]) && strlen($dr[$fid]) > 10)
|| (!empty($dr[$fid.'_svg']) && strlen($dr[$fid.'_svg']) > 10)) $tomasFirm++;
}
}
+5 -3
View File
@@ -102,9 +102,11 @@ foreach ($esquema as $campo) {
}
// ── Determinar estado y siguiente_toma_at ────────────────────
$firmados = array_filter($firmasCampos, fn($f) => isset($datosMerge[$f['id']]) && strlen($datosMerge[$f['id']]) > 10);
$pendientes = array_filter($firmasCampos, fn($f) => !isset($datosMerge[$f['id']]) || strlen($datosMerge[$f['id']]) <= 10);
$pendientes = array_values($pendientes);
// Acepta firma guardada con clave campo_id O campo_id_svg (compatibilidad registros históricos)
$hasFirma = fn($f) => (isset($datosMerge[$f['id']]) && strlen($datosMerge[$f['id']]) > 10)
|| (isset($datosMerge[$f['id'].'_svg']) && strlen($datosMerge[$f['id'].'_svg']) > 10);
$firmados = array_filter($firmasCampos, $hasFirma);
$pendientes = array_values(array_filter($firmasCampos, fn($f) => !$hasFirma($f)));
$todasFirmadas = empty($pendientes);
$siguienteToma = null;
+78 -3
View File
@@ -433,6 +433,27 @@ try {
.mtl-lugar { font-size:.73rem; color:#64748b; }
.mtl-eb { font-size:.62rem; padding:1px 7px; border-radius:99px; font-weight:700; display:inline-block; margin-top:2px; }
/* ── Banner toma alerta ── */
#banner-toma-alerta {
display: none; position: fixed; top: 52px; left: 0; right: 0; z-index: 8900;
background: #dc2626; color: #fff;
padding: .65rem 1.2rem; display: none;
align-items: center; gap: .75rem; font-size: .9rem; font-weight: 600;
box-shadow: 0 4px 20px rgba(220,38,38,.45);
animation: parpadeo-alerta 1s infinite;
}
#banner-toma-alerta.show { display: flex; }
#banner-toma-alerta .btn-banner-pac {
background: rgba(255,255,255,.2); border: 1.5px solid rgba(255,255,255,.5);
color: #fff; border-radius: 8px; padding: 3px 12px; font-size: .8rem;
cursor: pointer; font-weight: 700; transition: background .12s;
}
#banner-toma-alerta .btn-banner-pac:hover { background: rgba(255,255,255,.35); }
#banner-toma-alerta .btn-banner-x {
margin-left: auto; background: none; border: none; color: rgba(255,255,255,.8);
font-size: 1.1rem; cursor: pointer; padding: 0 4px;
}
/* ── Toast ── */
.rec-toast {
position: fixed; bottom: 2rem; right: 2rem; z-index: 9999;
@@ -519,6 +540,14 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
</div>
</div>
<!-- ── Banner alerta toma progresiva ─────────────────────────────── -->
<div id="banner-toma-alerta" role="alert">
<i class="fas fa-syringe fa-lg"></i>
<span id="banner-toma-txt">¡Hora de la siguiente muestra!</span>
<button class="btn-banner-pac" id="banner-toma-btn" onclick="verPacienteToma()">Ver paciente</button>
<button class="btn-banner-x" onclick="cerrarBannerToma()" title="Cerrar"><i class="fas fa-times"></i></button>
</div>
<main class="main-content" style="padding:0">
<!-- ── Top bar ──────────────────────────────────────────────── -->
@@ -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);
}