From 848dd8e4d01320e34c395a5923a80b23e962d92c Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:05:56 -0500 Subject: [PATCH] =?UTF-8?q?Toma=20progresiva:=20auto-scroll,=20cierre=20au?= =?UTF-8?q?tom=C3=A1tico,=20countdown=20en=20cola=20y=20fix=20filtro=20por?= =?UTF-8?q?=20examen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - guardar_toma.php: filtrar firmas por condición del examen seleccionado; corrige countdown espurio y detección de 'completado' cuando hay múltiples tipos de examen. También descarta siguiente_toma_at si ya es pasado (minuto 0). - ver_formulario_enviado.php: auto-scroll a primera firma pendiente al cargar el modal; si el examen se selecciona y la firma no está en DOM, guarda y recarga el iframe. Al completar tomas, cierra el modal directamente sin botón "Finalizar". - get_cola.php: incluye siguiente_toma_at y en_toma_progresiva por turno desde turnero_consentimientos para mostrar cuenta regresiva en tarjetas. - lugar.php: muestra chip de cuenta regresiva en tarjetas de cola para pacientes en toma progresiva; color cambia a warn (<2 min) y crit (¡Ahora!). Co-Authored-By: Claude Sonnet 4.6 --- modules/turnero/api/get_cola.php | 23 +++++++++++ modules/turnero/api/guardar_toma.php | 41 ++++++++++++------- modules/turnero/views/lugar.php | 56 +++++++++++++++++++++---- ver_formulario_enviado.php | 61 +++++++++++++++++++++++----- 4 files changed, 149 insertions(+), 32 deletions(-) diff --git a/modules/turnero/api/get_cola.php b/modules/turnero/api/get_cola.php index 748c27d..2e275f2 100644 --- a/modules/turnero/api/get_cola.php +++ b/modules/turnero/api/get_cola.php @@ -99,6 +99,29 @@ if ($area === 'recepcion') { } $cola = $stmt->fetchAll(PDO::FETCH_ASSOC); +// Enriquecer con datos de toma progresiva (cuenta regresiva en tarjetas de cola) +if (!empty($cola)) { + $turnoIds = implode(',', array_map('intval', array_column($cola, 'id'))); + $tomaRows = $pdo->query( + "SELECT tc.turno_id, tc.siguiente_toma_at + FROM turnero_consentimientos tc + JOIN lab_formularios f ON f.id = tc.formulario_id AND f.es_toma_progresiva = 1 + WHERE tc.turno_id IN ($turnoIds) AND tc.estado = 'en_progreso' + ORDER BY tc.siguiente_toma_at ASC" + )->fetchAll(PDO::FETCH_ASSOC); + $tomaInfo = []; + foreach ($tomaRows as $row) { + if (!isset($tomaInfo[$row['turno_id']])) { + $tomaInfo[$row['turno_id']] = $row['siguiente_toma_at']; + } + } + foreach ($cola as &$t) { + $t['en_toma_progresiva'] = isset($tomaInfo[$t['id']]) ? 1 : 0; + $t['siguiente_toma_at'] = $tomaInfo[$t['id']] ?? null; + } + unset($t); +} + // ── Turno actualmente en pantalla (último llamado) ──────────── $campoLlamado = $area === 'recepcion' ? 'llamado_recepcion_at' : 'llamado_lugar_at'; $estadoActivo = $area === 'recepcion' ? "'en_recepcion'" : "'en_servicio'"; diff --git a/modules/turnero/api/guardar_toma.php b/modules/turnero/api/guardar_toma.php index bf110b6..21a6aa5 100644 --- a/modules/turnero/api/guardar_toma.php +++ b/modules/turnero/api/guardar_toma.php @@ -59,28 +59,40 @@ $datosInput[$campoId] = $svg; $datosMerge = array_merge($datosActuales, $datosInput); // ── Analizar esquema: campos firma_profesional en orden ────── -// Construir mapa: campo_id → minuto (o hora fija) leyendo el separador previo -$firmasCampos = []; // [ [id, minutos|null, hora_fija|null], ... ] en orden +// Solo incluye firmas cuya sección condicional coincide con el examen seleccionado. +$firmasCampos = []; $separadorLabel = null; +$curCondField = null; +$curCondVals = []; foreach ($esquema as $campo) { $tipo = $campo['tipo'] ?? ''; if ($tipo === 'separador') { $separadorLabel = $campo['label'] ?? ''; + $cond = $campo['condicion'] ?? null; + if ($cond) { + $curCondField = $cond['campo_id'] ?? null; + $curCondVals = $cond['valores'] ?? ($cond['valor'] ? [$cond['valor']] : []); + } else { + $curCondField = null; + $curCondVals = []; + } } if ($tipo === 'firma_profesional') { - $minutos = null; - $horaFija = null; + // Filtrar por condición del separador padre + if ($curCondField !== null && !empty($curCondVals)) { + $ctrlVal = $datosMerge[$curCondField] ?? null; + $ctrlArr = is_array($ctrlVal) ? $ctrlVal : ($ctrlVal !== null ? [$ctrlVal] : []); + if (empty(array_intersect($curCondVals, $ctrlArr))) continue; + } + $minutos = null; + $horaFija = null; if ($separadorLabel !== null) { - // "Taukit · Minuto 10" → 10; "Toma · Minuto 60" → 60 if (preg_match('/Minuto\s+(\d+)/i', $separadorLabel, $m)) { $minutos = (int)$m[1]; - } - // "3:00 p.m." / "4:00 p.m." → hora fija - elseif (preg_match('/(\d+):(\d+)\s*(a\.?m\.?|p\.?m\.?)/i', $separadorLabel, $m)) { - $h = (int)$m[1]; - $min = (int)$m[2]; - $pm = strtolower(preg_replace('/[^apm]/i', '', $m[3])) === 'pm'; + } elseif (preg_match('/(\d+):(\d+)\s*(a\.?m\.?|p\.?m\.?)/i', $separadorLabel, $m)) { + $h = (int)$m[1]; $min = (int)$m[2]; + $pm = strtolower(preg_replace('/[^apm]/i', '', $m[3])) === 'pm'; if ($pm && $h < 12) $h += 12; $horaFija = sprintf('%02d:%02d:00', $h, $min); } @@ -106,15 +118,16 @@ if (empty($tc['toma_inicio_at'])) { if ($todasFirmadas) { $nuevoEstado = 'firmado'; } else { - // Calcular cuándo es la siguiente toma $next = $pendientes[0]; if ($next['hora_fija'] !== null) { - // Hora fija en el día actual $siguienteToma = date('Y-m-d') . ' ' . $next['hora_fija']; } elseif ($next['minutos'] !== null && $tomaInicioAt) { - // Minuto relativo desde el inicio de la toma $siguienteToma = date('Y-m-d H:i:s', strtotime($tomaInicioAt) + $next['minutos'] * 60); } + // Si la siguiente toma ya pasó o es ahora (minuto 0), no enviar countdown + if ($siguienteToma !== null && $siguienteToma <= date('Y-m-d H:i:s')) { + $siguienteToma = null; + } } // ── Persistir ──────────────────────────────────────────────── diff --git a/modules/turnero/views/lugar.php b/modules/turnero/views/lugar.php index 3e51780..6c290c2 100644 --- a/modules/turnero/views/lugar.php +++ b/modules/turnero/views/lugar.php @@ -188,6 +188,16 @@ try { font-size: .6rem; font-weight: 700; color: #ea580c; background: #fff7ed; border-radius: 99px; padding: 0 6px; line-height: 1.7; } + .toma-prog-chip { + font-size: .6rem; font-weight: 700; border-radius: 99px; + padding: 0 6px; line-height: 1.7; white-space: nowrap; + } + .toma-prog-chip.ok { background: #eff6ff; color: #1d4ed8; } + .toma-prog-chip.warn { background: #fffbeb; color: #92400e; } + .toma-prog-chip.crit { background: #fef2f2; color: #991b1b; animation: parpadeo-alerta 1s infinite; } + @keyframes parpadeo-alerta { 0%,100%{opacity:1} 50%{opacity:.55} } + .cola-card.toma-prog { border-color: #f97316 !important; } + .cola-card.toma-prog.crit-card { border-color: #dc2626 !important; background: #fef2f2 !important; } .card-en-servicio-pill { flex-shrink: 0; display: flex; flex-direction: column; align-items: center; @@ -945,9 +955,11 @@ function renderCola(snap) { } function renderColaCard(t) { - const enServicio = t.estado === 'en_servicio'; - const esActivo = turnoActivo?.id == t.id; - const esMuestra = t.solo_muestras == 1; + const enServicio = t.estado === 'en_servicio'; + const esActivo = turnoActivo?.id == t.id; + const esMuestra = t.solo_muestras == 1; + const enTomaProg = t.en_toma_progresiva == 1; + const sigTomaAt = t.siguiente_toma_at || null; const tiempo = tiempoEspera(t.creado_at); const tiempoBadge = tiempo @@ -956,8 +968,13 @@ function renderColaCard(t) { const muestraBadge = esMuestra ? `MUESTRAS` : ''; - const badgeRow = (tiempoBadge || muestraBadge) - ? `
${tiempoBadge}${muestraBadge}
` + const tomaBadge = enTomaProg + ? ` + ${sigTomaAt ? '...' : '¡Ahora!'} + ` + : ''; + const badgeRow = (tiempoBadge || muestraBadge || tomaBadge) + ? `
${tiempoBadge}${muestraBadge}${tomaBadge}
` : ''; const rightEl = enServicio @@ -967,11 +984,12 @@ function renderColaCard(t) { `; - const bg = esMuestra ? 'border:2px solid #ea580c!important;background:#fff7ed!important' : ''; - const prioBg = esMuestra ? '#ea580c' : (t.prioridad_color || '#6366f1'); + let bg = esMuestra ? 'border:2px solid #ea580c!important;background:#fff7ed!important' : ''; + const prioBg = esMuestra ? '#ea580c' : (t.prioridad_color || '#6366f1'); const prioLbl = esMuestra ? '' : escHtml(t.prioridad_codigo || '?'); + const tomaClass = enTomaProg ? ' toma-prog' : ''; - return `
${prioLbl}
@@ -1180,6 +1198,28 @@ const CONSENT_LBL = { en_progreso:'En progreso', }; +// ── Countdown en tarjetas de cola (toma progresiva) ────────── +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'); + if (!at || !txt) return; + const diff = Math.floor((new Date(at.replace(' ','T')).getTime() - ahora) / 1000); + const card = chip.closest('.cola-card'); + if (diff <= 0) { + txt.textContent = '¡Ahora!'; + chip.className = 'toma-prog-chip crit'; + if (card) card.classList.add('crit-card'); + } else { + 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'; + if (card) card.classList.remove('crit-card'); + } + }); +}, 1000); + // ── Tomas progresivas: actualiza countdowns cada segundo ────── let _tomaTimers = {}; // consent_id → { siguiente_toma_at, es_alerta } setInterval(() => { diff --git a/ver_formulario_enviado.php b/ver_formulario_enviado.php index 263d3bf..5b6593c 100644 --- a/ver_formulario_enviado.php +++ b/ver_formulario_enviado.php @@ -1073,6 +1073,13 @@ function esc2(mixed $v): string { window._muestrasMap = ; window._mpSavedFirmas = {}; +// Auto-scroll a la primera firma pendiente al cargar (examen ya seleccionado) +document.addEventListener('DOMContentLoaded', function() { + setTimeout(function() { + var fw = document.querySelector('.firma-pro-widget:not([style*="display:none"])'); + if (fw) fw.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 350); +}); /* ── Firma del profesional: función compartida canvas/1-clic ────── */ function _guardarFirmaPro(widget, svg, msgEl) { @@ -1183,24 +1190,34 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) { widget.replaceWith(box); if (data.completado) { - _mpMostrarFinalizar(entry.exam_type); - // Cerrar modal en lugar.php y refrescar la lista + // 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); setTimeout(function() { try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {} }, 1200); } else if (data.siguiente_toma_at) { - // Countdown usando tiempo absoluto del servidor var targetMs = new Date(data.siguiente_toma_at.replace(' ', 'T')).getTime(); var minsLeft = Math.max(1, Math.round((targetMs - Date.now()) / 60000)); _mpIniciarCountdown(minsLeft, entry.next_label, entry.next_firma_id); } else if (!entry.is_last) { - var nw = entry.next_firma_id ? document.getElementById('fpw-' + entry.next_firma_id) : document.querySelector('.firma-pro-widget'); - if (nw) nw.scrollIntoView({ behavior:'smooth', block:'center' }); + var nw = entry.next_firma_id ? document.getElementById('fpw-' + entry.next_firma_id) : null; + if (!nw) nw = document.querySelector('.firma-pro-widget:not([style*="display:none"])'); + if (nw) { nw.style.display = ''; nw.scrollIntoView({ behavior:'smooth', block:'center' }); } } else { - _mpMostrarFinalizar(entry.exam_type); - setTimeout(function() { - try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {} - }, 1200); + // Último firma del grupo pero hay más exámenes pendientes; desbloquear siguiente + var nw = document.querySelector('.firma-pro-widget[style*="display:none"]'); + if (nw) { + nw.style.display = ''; + nw.scrollIntoView({ behavior:'smooth', block:'center' }); + } else { + // Sin más firmas pendientes → cerrar + setTimeout(function() { + try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {} + }, 1200); + } } }) .catch(function() { @@ -1681,7 +1698,7 @@ const topaz = (() => { var condicionales = secciones.filter(function(s) { return s.condicion; }); if (!condicionales.length) return; - function evaluar() { + function evaluar(evt) { condicionales.forEach(function(sec) { var cond = sec.condicion; var valoresCond = cond.valores ? cond.valores : (cond.valor ? [cond.valor] : []); @@ -1708,6 +1725,30 @@ const topaz = (() => { el.style.pointerEvents = activo ? '' : 'none'; }); }); + + // Toma progresiva: scroll a la primera firma pendiente + if (window._muestrasMap) { + setTimeout(function() { + var fw = document.querySelector('.firma-pro-widget:not([style*="display:none"])'); + if (fw) { + fw.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } else if (evt) { + // Firma no está en el DOM (examen recién seleccionado) → guardar y recargar + 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; + }); + fetch(window.location.href, { + method:'POST', headers:{'Content-Type':'application/json'}, + body: JSON.stringify({ datos_respuestas: dr }) + }).then(function() { window.location.reload(); }) + .catch(function() { window.location.reload(); }); + } + }, 400); + } } var ctrlIds = [...new Set(condicionales.map(function(s) { return s.condicion.campo_id; }))];