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:
co-authored by
Claude Opus 5
parent
ba35b24b30
commit
b0266f4b8b
@@ -19,9 +19,8 @@ $svg = $body['svg'] ?? '';
|
||||
$soloPro = !empty($body['solo_profesional']);
|
||||
// Siempre mergeamos datos_respuestas (incluye _pro_nombre/_pro_cedula)
|
||||
$datosResp = null;
|
||||
if (isset($body['datos_respuestas']) && is_array($body['datos_respuestas']) && !empty($body['datos_respuestas'])) {
|
||||
$datosResp = json_encode($body['datos_respuestas'], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$datosArr = (isset($body['datos_respuestas']) && is_array($body['datos_respuestas']))
|
||||
? $body['datos_respuestas'] : [];
|
||||
|
||||
if (!$turnoId) jsonError('turno_id requerido.');
|
||||
if (!$formularioId) jsonError('formulario_id requerido.');
|
||||
@@ -32,6 +31,32 @@ if (!preg_match('/^data:image\/(svg\+xml|png|jpeg|webp);base64,/i', $svg)) {
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Quién firma se resuelve aquí y no se toma del navegador. El cliente manda el
|
||||
// nombre que cargó al abrir la página, y si el personal cambia de turno sin
|
||||
// recargar, la firma quedaba a nombre de quien abrió el formulario en vez de
|
||||
// quien la está haciendo. La sesión del servidor es la única fuente confiable.
|
||||
// (guardar_toma.php ya lo hacía así para cada toma; esto alinea la firma final.)
|
||||
$__uid = adminId();
|
||||
if ($__uid) {
|
||||
$stmtPro = $pdo->prepare(
|
||||
"SELECT au.full_name, au.cedula AS au_cedula,
|
||||
le.nombre_completo AS pro_nombre, le.numero_documento AS pro_documento
|
||||
FROM admin_users au
|
||||
LEFT JOIN lab_enfermeras le ON le.id = au.enfermera_id
|
||||
WHERE au.id = ? LIMIT 1"
|
||||
);
|
||||
$stmtPro->execute([$__uid]);
|
||||
$pro = $stmtPro->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||
$proNombre = ($pro['pro_nombre'] ?? null) ?: ($pro['full_name'] ?? null);
|
||||
$proCedula = ($pro['au_cedula'] ?? null) ?: ($pro['pro_documento'] ?? null);
|
||||
if ($proNombre) $datosArr['_pro_nombre'] = $proNombre;
|
||||
if ($proCedula) $datosArr['_pro_cedula'] = $proCedula;
|
||||
$datosArr['_pro_id'] = $__uid;
|
||||
}
|
||||
if (!empty($datosArr)) {
|
||||
$datosResp = json_encode($datosArr, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT tc.id, tc.estado, t.sesion_id
|
||||
FROM turnero_consentimientos tc
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
require_once __DIR__ . '/../../../classes/lab/ActividadAdmin.php';
|
||||
requireMethod('POST');
|
||||
requireTurnero();
|
||||
|
||||
@@ -144,6 +145,20 @@ try {
|
||||
$pdo->commit();
|
||||
notificarSSE((int) $turnoActualizado['sesion_id']);
|
||||
|
||||
// Quién llamó no se guardaba en ninguna parte: el turno solo conserva la hora
|
||||
// del último llamado, así que en la bitácora aparecía «Llamado a X» sin autor.
|
||||
try {
|
||||
(new ActividadAdmin())->registrar(
|
||||
adminId(), 'turnero', 'llamar_turno', $turnoId,
|
||||
[
|
||||
'turno_id' => $turnoId,
|
||||
'turno_codigo' => $turnoActualizado['codigo'] ?? null,
|
||||
]
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[llamar_turno] auditoría: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
jsonOk(['turno' => $turnoActualizado], "Turno {$turnoActualizado['codigo']} llamado");
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
require_once __DIR__ . '/../../../classes/lab/ActividadAdmin.php';
|
||||
requireMethod('POST');
|
||||
requireTurnero();
|
||||
|
||||
@@ -48,4 +49,15 @@ $stmtT = $pdo->prepare(
|
||||
$stmtT->execute([$turnoId]);
|
||||
$turno = $stmtT->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
// El rellamado pisa llamado_recepcion_at, así que sin esto no queda rastro de
|
||||
// cuántas veces se llamó al paciente ni quién lo hizo.
|
||||
try {
|
||||
(new ActividadAdmin())->registrar(
|
||||
adminId(), 'turnero', 'rellamar', $turnoId,
|
||||
['turno_id' => $turnoId, 'turno_codigo' => $turno['codigo'] ?? null]
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[rellamar] auditoría: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
jsonOk(['turno' => $turno]);
|
||||
|
||||
Reference in New Issue
Block a user