fix: 5 bugs en modal de tomas progresivas F-LAB-28

- Fix 1: countdown se recupera al reabrir el modal (lee siguiente_toma_at de BD)
- Fix 2: _mpIniciarCountdown recibe timestamp exacto en vez de minutos redondeados
- Fix 3: window._fpwPreFirma se emite una sola vez (no N veces por campo firma)
- Fix 4: postMessage('turneroFirmado') unificado a 2000ms en todos los paths
- Fix 5: resumen de tomas usa hora cacheada al guardar; elimina traversal DOM frágil

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-16 09:45:55 -05:00
co-authored by Claude Sonnet 4.6
parent 388f6ff7c9
commit 34a0ae2c35
+46 -29
View File
@@ -31,6 +31,7 @@ if ($modoTurnero) {
"SELECT tc.id, tc.turno_id, tc.formulario_id, tc.token, tc.estado,
tc.enviado_at, tc.firmado_at, tc.ip_firma, tc.ua_firma, tc.firma_svg,
tc.firma_profesional_svg, tc.firmado_profesional_at, tc.datos_respuestas,
tc.siguiente_toma_at, tc.toma_inicio_at,
f.nombre AS form_nombre, f.categoria, f.descripcion AS form_descripcion,
f.esquema, f.es_toma_progresiva, f.doc_encabezado, f.doc_subtitulo, f.doc_logo_base64, f.doc_color, f.doc_pie_pagina,
p.nombre_completo AS paciente_nombre,
@@ -321,6 +322,7 @@ if ($modoTurnero && $embebido && $_soloFirmaPro) {
for ($i = 0, $n = count($_secs); $i < $n; $i++) {
$_s = $_secs[$i]; $_nx = $_secs[$i + 1] ?? null;
$_mpMap[$_s['firma']] = [
'label' => $_s['label'],
'hora_campo' => $_s['hora'],
'next_label' => $_nx ? $_nx['label'] : null,
'next_firma_id' => $_nx ? $_nx['firma'] : null,
@@ -342,6 +344,15 @@ if (!empty($_mpMap)) {
}
}
// siguiente_toma_at para recuperar countdown si el modal fue cerrado y reabierto
$_mpSiguienteTomAt = null;
if ($modoTurnero && $embebido && !empty($_mpMap) && $_mpPrimeraPendiente !== null) {
$raw = $tcRow['siguiente_toma_at'] ?? null;
if ($raw && $raw > date('Y-m-d H:i:s')) {
$_mpSiguienteTomAt = $raw;
}
}
// Mapa id → label
$labelMap = [];
foreach ($esquema as $c) {
@@ -880,9 +891,6 @@ function _addExamWizardHtml(string $cid): string {
($modoTurnero && $embebido && isUserLoggedIn())
);
if ($_mostrarCanvasPro): ?>
<?php if ($firmaProfPreguardada): ?>
<script>window._fpwPreFirma = <?= json_encode($firmaProfPreguardada) ?>;</script>
<?php endif; ?>
<!-- Canvas del profesional -->
<?php
$_mpBloqueado = !empty($_mpMap)
@@ -1506,8 +1514,9 @@ function aewSave(cid) {
} catch(e) {}
})();
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
window._muestrasMap = <?= json_encode($_mpMap, JSON_UNESCAPED_UNICODE) ?>;
window._mpSavedFirmas = {};
window._mpTomaData = {};
function _mpAutoFillHora(firmaId) {
var entry = window._muestrasMap && window._muestrasMap[firmaId];
@@ -1529,6 +1538,19 @@ document.addEventListener('DOMContentLoaded', function() {
}, 350);
});
<?php endif; ?>
window._fpwPreFirma = <?= json_encode($firmaProfPreguardada) ?>;
<?php if (!empty($_mpSiguienteTomAt)): ?>
// Fix 1: recuperar countdown si el modal fue cerrado y reabierto mientras una toma estaba en curso
(function() {
var targetMs = new Date('<?= str_replace(' ', 'T', $_mpSiguienteTomAt) ?>').getTime();
if (targetMs <= Date.now()) return;
var pendingId = <?= json_encode($_mpPrimeraPendiente) ?>;
var entry = window._muestrasMap && window._muestrasMap[pendingId];
document.addEventListener('DOMContentLoaded', function() {
_mpIniciarCountdown(targetMs, entry ? (entry.label || null) : null, pendingId);
});
})();
<?php endif; ?>
/* ── Firma del profesional: función compartida canvas/1-clic ────── */
function _guardarFirmaPro(widget, svg, msgEl) {
const turnoId = widget.dataset.turno;
@@ -1605,6 +1627,10 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) {
// Guardar con campoId directo (sin _svg) para que get_consentimientos lo cuente
dr[campoId] = svg;
(window._mpSavedFirmas = window._mpSavedFirmas||{})[campoId] = svg;
// Fix 5: cachear hora en el momento de guardar para el resumen final
(window._mpTomaData = window._mpTomaData||{})[campoId] = {
hora: entry.hora_campo ? (dr[entry.hora_campo] || '') : ''
};
var turnoId = parseInt(widget.dataset.turno, 10);
var formularioId = parseInt(widget.dataset.formulario, 10);
@@ -1644,11 +1670,11 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) {
_mpMostrarResumen(entry.exam_type || 'Examen');
setTimeout(function() {
try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {}
}, 2200);
}, 2000);
} else if (data.siguiente_toma_at) {
// Fix 2: pasar timestamp exacto; evita el redondeo de Math.round
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);
_mpIniciarCountdown(targetMs, 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) : null;
if (!nw) nw = document.querySelector('.firma-pro-widget:not([style*="display:none"])');
@@ -1662,9 +1688,10 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) {
_mpAutoFillHora(nw.dataset.campo);
} else {
// Sin más firmas pendientes → cerrar
// Fix 4: unificar delay con el caso data.completado (ambos 2000ms)
setTimeout(function() {
try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {}
}, 1200);
}, 2000);
}
}
})
@@ -1688,8 +1715,9 @@ function _mpPlayBeep() {
} catch(e) {}
}
function _mpIniciarCountdown(minutos, nextLabel, nextFirmaId) {
var targetMs = Date.now() + minutos * 60000;
// Fix 2: acepta timestamp (ms > 1e10) o minutos (número pequeño)
function _mpIniciarCountdown(targetMsOrMins, nextLabel, nextFirmaId) {
var targetMs = targetMsOrMins > 1e10 ? targetMsOrMins : Date.now() + targetMsOrMins * 60000;
var box = document.getElementById('mp-countdown');
if (!box) {
box = document.createElement('div');
@@ -1747,24 +1775,14 @@ function _mpMostrarResumen(examType) {
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;
}
// Fix 5: usar hora cacheada al momento de guardar; evita traversal DOM frágil
var cached = window._mpTomaData && window._mpTomaData[fid];
var horaVal = (cached && cached.hora) || '';
if (!horaVal && entry.hora_campo) {
var horaEl = document.querySelector('[name="' + entry.hora_campo + '"]');
horaVal = horaEl ? horaEl.value : '';
}
// 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]) });
rows.push({ hora: horaVal, firmado: !!(window._mpSavedFirmas && window._mpSavedFirmas[fid]) });
});
}
var wrap = document.createElement('div');
@@ -1772,10 +1790,9 @@ function _mpMostrarResumen(examType) {
wrap.className = 'alert alert-success mt-4';
var rowsHtml = rows.length
? '<table class="table table-sm mt-2 mb-0" style="font-size:.82rem"><thead><tr>'
+ '<th>Hora</th><th>Resultado</th><th></th></tr></thead><tbody>'
+ '<th>Hora</th><th></th></tr></thead><tbody>'
+ rows.map(function(r) {
return '<tr><td>' + (r.hora || '—') + '</td>'
+ '<td>' + (r.resultado || '—') + '</td>'
+ '<td style="color:#198754"><i class="fas fa-check-circle"></i></td></tr>';
}).join('') + '</tbody></table>' : '';
wrap.innerHTML = '<div class="d-flex align-items-center gap-2"><i class="fas fa-check-double fs-5"></i>'