Bitácora: atribuir bien las firmas y registrar quién llama

Tres correcciones sobre lo reportado en E042.

1. La bitácora daba por hecho que firmado_at era del paciente, y lo anunciaba
   así en documentos que el paciente nunca firma: el F-LAB-08 solo lo firma el
   profesional y el F-LAB-28 lleva una firma por toma. Ahora mira qué firma hay
   realmente guardada; si no hay ninguna dice «completado» o «protocolo
   cerrado», según el caso.

2. Los protocolos prolongados desglosan cada toma con su hora y su firmante,
   que es el dato que interesa: en E042 se ve que el minuto 0 lo firmó Yesica y
   el minuto 150 Omaira. La firma general no distinguía eso.

3. firmar_profesional_consentimiento.php aceptaba el nombre del firmante que
   mandaba el navegador, y ese valor se carga al abrir la página: si el
   personal cambiaba de turno sin recargar, la firma final quedaba a nombre de
   quien abrió el formulario. Ahora se resuelve desde la sesión en el servidor,
   igual que ya hacía guardar_toma.php para cada toma.

Además, llamar y rellamar no dejaban rastro —el turno solo guarda la hora del
último llamado, que el rellamado sobrescribe—, así que la bitácora mostraba
«Llamado a X» sin autor. Ahora se auditan ambos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-19 17:10:48 -05:00
co-authored by Claude Opus 5
parent ba35b24b30
commit b0266f4b8b
4 changed files with 119 additions and 9 deletions
+64 -6
View File
@@ -60,16 +60,67 @@ $add($turno['fin_lugar_at'], 'fin', 'Finaliza la atención');
// ── 2. Documentos ─────────────────────────────────────────────────────────
$q = $pdo->prepare(
"SELECT tc.enviado_at, tc.firmado_at, tc.firmado_profesional_at, tc.estado,
f.nombre AS formulario
tc.firma_svg, tc.firma_profesional_svg, tc.datos_respuestas,
f.nombre AS formulario, f.es_toma_progresiva
FROM turnero_consentimientos tc
JOIN lab_formularios f ON f.id = tc.formulario_id
WHERE tc.turno_id = ?"
);
$q->execute([$turnoId]);
foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $c) {
$add($c['enviado_at'], 'doc', 'Enviado: ' . $c['formulario']);
$add($c['firmado_at'], 'doc_ok', 'Firmado por el paciente: ' . $c['formulario']);
$add($c['firmado_profesional_at'], 'doc_ok', 'Firmado por el profesional: ' . $c['formulario']);
$add($c['enviado_at'], 'doc', 'Enviado: ' . $c['formulario']);
// No todos los formularios los firma el paciente: el F-LAB-08 solo lo firma
// el profesional y el F-LAB-28 lleva una firma por toma. Se mira qué firma
// hay guardada en vez de suponer que firmado_at es del paciente, que era lo
// que antes hacía decir «firmado por el paciente» en documentos que el
// paciente nunca firmó.
$firmoPaciente = !empty($c['firma_svg']);
$firmoProfesional = !empty($c['firma_profesional_svg']);
if ($firmoPaciente) {
$add($c['firmado_at'], 'doc_ok', 'Firmado por el paciente: ' . $c['formulario']);
}
if ($firmoProfesional) {
$add($c['firmado_profesional_at'] ?: $c['firmado_at'], 'doc_ok',
'Firmado por el profesional: ' . $c['formulario']);
}
// Protocolo prolongado: cada toma lleva su hora y su firmante, que es el
// dato que de verdad interesa. La firma general no dice quién pinchó.
if (!empty($c['es_toma_progresiva'])) {
$dr = json_decode($c['datos_respuestas'] ?? '{}', true) ?: [];
$tomas = [];
foreach ($dr as $clave => $valor) {
// Las firmas de toma terminan en _f y traen su hora en _h
if (!preg_match('/^(_tm[a-z0-9]+)_f$/', $clave, $m)) continue;
if (!is_string($valor) || strlen($valor) < 10) continue;
$base = $m[1];
$tomas[] = [
'hora' => $dr[$base . '_h'] ?? null,
// _tm00 → «Minuto 0», _tm150 → «Minuto 150»
'etiqueta' => 'Minuto ' . (int) substr($base, 3),
'firmante' => $dr[$clave . '_pro_nombre'] ?? null,
];
}
// Se ordenan por hora para que la línea de tiempo quede coherente
usort($tomas, fn($a, $b) => strcmp((string)$a['hora'], (string)$b['hora']));
foreach ($tomas as $t) {
if (!$t['hora']) continue;
// La hora viene como "HH:MM"; se ancla al día del turno
$cuando = substr($turno['creado_at'], 0, 10) . ' ' . $t['hora'] . ':00';
$add($cuando, 'muestra',
'Toma tomada y firmada (' . $t['etiqueta'] . ')',
$t['firmante']);
}
}
if (!$firmoPaciente && !$firmoProfesional && $c['firmado_at'] && empty($c['es_toma_progresiva'])) {
$add($c['firmado_at'], 'doc_ok', 'Completado: ' . $c['formulario']);
}
if (!empty($c['es_toma_progresiva']) && $c['firmado_at']) {
$add($c['firmado_at'], 'fin', 'Protocolo cerrado: ' . $c['formulario']);
}
}
// ── 3. Comentarios del personal ───────────────────────────────────────────
@@ -153,6 +204,14 @@ foreach ($q->fetchAll(PDO::FETCH_ASSOC) as $a) {
$d['formulario_nombre'] ?? null);
break;
case 'llamar_turno':
$add($a['created_at'], 'llamado', 'Llamó al paciente', $a['admin_nombre']);
break;
case 'rellamar':
$add($a['created_at'], 'llamado', 'Volvió a llamar al paciente', $a['admin_nombre']);
break;
case 'vincular_paciente':
$add($a['created_at'], 'admin',
'Paciente vinculado: ' . ($d['paciente_nombre'] ?? '#' . ($d['paciente_id'] ?? '?')),
@@ -199,7 +258,6 @@ jsonOk([
// Lo que el sistema sigue sin registrar. Se declara para que la ausencia de
// un evento no se lea como prueba de que no ocurrió.
'sinRastro' => [
'rellamados',
'quién guardó cada toma de un protocolo',
'el llamado original de los turnos anteriores a hoy, que el rellamado sobrescribía',
],
]);