feat(turnero): tomas de muestras prolongadas con temporizador y progreso
- DB: ALTER TABLE turnero_consentimientos ADD siguiente_toma_at, toma_inicio_at; MODIFY estado para incluir 'en_progreso' - guardar_toma.php (nuevo): guarda firma parcial por campo_id, parsea el esquema para calcular siguiente_toma_at desde 'Minuto X' o hora fija, marca 'en_progreso' o 'firmado' según tomas restantes - get_consentimientos.php: expone siguiente_toma_at, es_toma_progresiva, tomas_total, tomas_firmadas, campos_firma_pro en cada consentimiento - lugar.php: estado en_progreso con badge X/Y, countdown en tiempo real, animación de alerta cuando el tiempo llega, botón 'Siguiente toma' que abre el modal embebido en modo toma_progresiva Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
cf8d011f09
commit
ea1f0e30c9
@@ -265,11 +265,18 @@ try {
|
||||
padding: .5rem .7rem; border-radius: 10px; margin-bottom: .35rem;
|
||||
font-size: .84rem; border: 1px solid transparent; min-height: 46px;
|
||||
}
|
||||
.consent-row.firmado { background: #f0fdf4; color: #166534; border-color: #bbf7d0; }
|
||||
.consent-row.rechazado { background: #f8fafc; color: #64748b; border-color: #e2e8f0; }
|
||||
.consent-row.enviado { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; }
|
||||
.consent-row.pendiente { background: #fffbeb; color: #92400e; border-color: #fde68a; }
|
||||
.consent-row.visto { background: #faf5ff; color: #6b21a8; border-color: #ddd6fe; }
|
||||
.consent-row.firmado { background: #f0fdf4; color: #166534; border-color: #bbf7d0; }
|
||||
.consent-row.rechazado { background: #f8fafc; color: #64748b; border-color: #e2e8f0; }
|
||||
.consent-row.enviado { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; }
|
||||
.consent-row.pendiente { background: #fffbeb; color: #92400e; border-color: #fde68a; }
|
||||
.consent-row.visto { background: #faf5ff; color: #6b21a8; border-color: #ddd6fe; }
|
||||
.consent-row.en_progreso { background: #fff7ed; color: #9a3412; border-color: #fed7aa; }
|
||||
.consent-row.en_progreso.alerta { background: #fef2f2; color: #991b1b; border-color: #fca5a5;
|
||||
animation: parpadeo-alerta 1s infinite; }
|
||||
@keyframes parpadeo-alerta { 0%,100%{opacity:1} 50%{opacity:.6} }
|
||||
.toma-progreso { font-size:.67rem; background:#ea580c; color:#fff;
|
||||
border-radius:99px; padding:1px 7px; font-weight:700; white-space:nowrap; }
|
||||
.toma-countdown { font-size:.67rem; color:inherit; opacity:.75; white-space:nowrap; }
|
||||
.consent-row .nom-form { flex: 1; font-weight: 500; }
|
||||
.consent-row .c-badge { font-size: .67rem; padding: 1px 8px; border-radius: 99px;
|
||||
border: 1px solid currentColor; font-weight: 700; opacity: .85; }
|
||||
@@ -1090,17 +1097,46 @@ async function cargarFichaSolicitud(turnoId) {
|
||||
const CONSENT_IC = {
|
||||
firmado:'fa-check-circle', rechazado:'fa-ban',
|
||||
enviado:'fa-envelope', visto:'fa-eye', pendiente:'fa-clock',
|
||||
en_progreso:'fa-hourglass-half',
|
||||
};
|
||||
const CONSENT_LBL = {
|
||||
firmado:'Firmado', rechazado:'Rechazado', enviado:'Enviado', visto:'Visto', pendiente:'Pendiente',
|
||||
en_progreso:'En progreso',
|
||||
};
|
||||
|
||||
// ── Tomas progresivas: actualiza countdowns cada segundo ──────
|
||||
let _tomaTimers = {}; // consent_id → { siguiente_toma_at, es_alerta }
|
||||
setInterval(() => {
|
||||
const ahora = Date.now();
|
||||
for (const [cid, info] of Object.entries(_tomaTimers)) {
|
||||
const row = document.querySelector(`[data-consent-id="${cid}"]`);
|
||||
if (!row) continue;
|
||||
const cdEl = row.querySelector('.toma-countdown');
|
||||
if (!cdEl) continue;
|
||||
if (!info.siguiente_toma_at) { cdEl.textContent = ''; continue; }
|
||||
const diff = Math.floor((info.siguiente_toma_at - ahora) / 1000);
|
||||
if (diff <= 0) {
|
||||
cdEl.textContent = '¡Hora de toma!';
|
||||
row.classList.add('alerta');
|
||||
if (!info.alertado) {
|
||||
info.alertado = true;
|
||||
try { new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAA...').play().catch(()=>{}); } catch(_) {}
|
||||
}
|
||||
} else {
|
||||
row.classList.remove('alerta');
|
||||
const m = Math.floor(diff / 60), s = diff % 60;
|
||||
cdEl.textContent = `próx. toma en ${m}:${String(s).padStart(2,'0')}`;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
function renderConsentimientos(lista) {
|
||||
// Mostrar consentimientos de examen (origen null) + los de esta estación
|
||||
lista = lista.filter(c => !c.origen_lugar_id || c.origen_lugar_id == lugarId);
|
||||
tieneConsent = lista.length > 0;
|
||||
hayPendientes = lista.some(c => !['firmado','rechazado'].includes(c.estado));
|
||||
const pendCount = lista.filter(c => !['firmado','rechazado'].includes(c.estado)).length;
|
||||
// en_progreso bloquea finalizar pero no muestra aviso de "pendiente sin acción"
|
||||
const pendCount = lista.filter(c => ['pendiente','enviado','visto'].includes(c.estado)).length;
|
||||
|
||||
const listEl = document.getElementById('lista-consent');
|
||||
const sinEl = document.getElementById('sin-consent');
|
||||
@@ -1131,6 +1167,17 @@ function renderConsentimientos(lista) {
|
||||
banner.classList.toggle('d-none', !hayPendientes);
|
||||
if (btnFin) btnFin.disabled = hayPendientes;
|
||||
|
||||
// Actualizar mapa de timers para tomas progresivas
|
||||
_tomaTimers = {};
|
||||
lista.forEach(c => {
|
||||
if (c.es_toma_progresiva && c.estado === 'en_progreso') {
|
||||
_tomaTimers[c.id] = {
|
||||
siguiente_toma_at: c.siguiente_toma_at ? new Date(c.siguiente_toma_at).getTime() : null,
|
||||
alertado: false,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
listEl.innerHTML = lista.map(c => {
|
||||
const ya = ['firmado','rechazado'].includes(c.estado);
|
||||
const ico = CONSENT_IC[c.estado] || 'fa-clock';
|
||||
@@ -1139,6 +1186,15 @@ function renderConsentimientos(lista) {
|
||||
const nomJs = JSON.stringify(c.formulario_nombre || 'Consentimiento');
|
||||
const idJs = parseInt(c.id) || 0;
|
||||
const tId = parseInt(c.turno_id) || (turnoActivo?.id) || 0;
|
||||
const fId = parseInt(c.formulario_id) || 0;
|
||||
|
||||
// Badge de progreso para tomas progresivas
|
||||
let progresoBadge = '';
|
||||
if (c.es_toma_progresiva && c.tomas_total > 0) {
|
||||
progresoBadge = `<span class="toma-progreso">${c.tomas_firmadas}/${c.tomas_total}</span>`;
|
||||
}
|
||||
const countdownEl = (c.es_toma_progresiva && c.estado === 'en_progreso')
|
||||
? `<span class="toma-countdown"></span>` : '';
|
||||
|
||||
// Botón ver
|
||||
const btnVer = (ya && c.token)
|
||||
@@ -1149,8 +1205,15 @@ function renderConsentimientos(lista) {
|
||||
|
||||
// Acción de firma según modo
|
||||
let btnAccion = '';
|
||||
if (!ya && c.token) {
|
||||
if (LUGAR_FORM_MODO === 'embebido') {
|
||||
const puedeFirmar = !ya || (c.es_toma_progresiva && c.estado === 'en_progreso');
|
||||
if (puedeFirmar && c.token) {
|
||||
if (c.es_toma_progresiva) {
|
||||
// Toma progresiva: siempre abre el modal embebido para firmar la siguiente toma
|
||||
btnAccion = `<button class="btn btn-primary" title="Registrar siguiente toma"
|
||||
onclick="abrirTomaProgresiva('${token}', ${tId}, ${fId})">
|
||||
<i class="fas fa-flask me-1"></i>Siguiente toma
|
||||
</button>`;
|
||||
} else if (LUGAR_FORM_MODO === 'embebido') {
|
||||
btnAccion = `<button class="btn btn-primary" title="Abrir formulario"
|
||||
onclick="abrirModalConsentimientoPorToken('${token}')">
|
||||
<i class="fas fa-pen me-1"></i>Firmar
|
||||
@@ -1170,6 +1233,8 @@ function renderConsentimientos(lista) {
|
||||
return `<div class="consent-row ${c.estado}" data-consent-id="${c.id}">
|
||||
<i class="fas ${ico}"></i>
|
||||
<span class="nom-form">${escHtml(c.formulario_nombre || 'Consentimiento')}</span>
|
||||
${progresoBadge}
|
||||
${countdownEl}
|
||||
<span class="c-badge">${label}</span>
|
||||
<div class="acciones-consent">${btnAccion}${btnVer}</div>
|
||||
</div>`;
|
||||
@@ -1234,6 +1299,22 @@ function abrirModalConsentimientoPorToken(token) {
|
||||
_abrirModalConToken(token);
|
||||
}
|
||||
|
||||
// ── Toma progresiva: abre el modal con modo especial ──────────
|
||||
function abrirTomaProgresiva(token, turnoId, formularioId) {
|
||||
const modal = document.getElementById('modal-consentimiento');
|
||||
const iframe = document.getElementById('modal-consent-iframe');
|
||||
const load = document.getElementById('modal-consent-loading');
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = '';
|
||||
load.style.display = '';
|
||||
modal.style.display = 'flex';
|
||||
// Pasa modo=toma_progresiva para que el renderer solo muestre
|
||||
// los campos del siguiente tiempo pendiente y el botón de guardar parcial
|
||||
setTimeout(() => {
|
||||
iframe.src = `${BASE_WA}ver_formulario_enviado.php?token=${encodeURIComponent(token)}&embed=1&compact=1&modo=toma_progresiva`;
|
||||
}, 80);
|
||||
}
|
||||
|
||||
function cerrarModalConsentimiento() {
|
||||
const modal = document.getElementById('modal-consentimiento');
|
||||
const iframe = document.getElementById('modal-consent-iframe');
|
||||
|
||||
Reference in New Issue
Block a user