Historial y bandeja: mostrar qué pasó con cada turno

Para saber quién retiró un formulario de un turno había que consultar la base
a mano. La información existía, pero repartida en cinco tablas y sin ninguna
pantalla que la juntara.

get_traza_turno.php arma una sola línea de tiempo con el recorrido del turno
(con quién atendió en cada punto), los documentos enviados y firmados, los
comentarios del personal —donde caen las ausencias con su motivo—, las
muestras recibidas y rechazadas, y las acciones administrativas auditadas.
Se muestra en el detalle desplegable de Historial y en el panel de Bandeja.

Del chat de WhatsApp solo se indica cuántos mensajes hubo ese día, con enlace
a la conversación. Volcar los mensajes sería esparcir datos personales del
paciente por una pantalla de consulta.

Y la parte que faltaba: cinco acciones no dejaban ningún rastro. Ahora se
auditan cambiar_estado, resetear_consentimiento, resetear_toma,
vincular_paciente y cancelar_toma_pendiente — las que borran una firma,
descartan datos de un protocolo o mandan la atención a otra historia clínica.
La auditoría va en try/catch: si falla, no tumba la operación.

La respuesta declara en `sinRastro` lo que el sistema todavía no registra,
para que la ausencia de un evento no se lea como prueba de que no ocurrió.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-19 12:33:55 -05:00
co-authored by Claude Opus 5
parent d4674f087d
commit 487673dc0f
8 changed files with 456 additions and 5 deletions
+47 -1
View File
@@ -251,6 +251,9 @@ Layout::open('Bandeja del día', 'fas fa-layer-group');
.bnd-com-user { font-weight: 600; color: #1e293b; }
.bnd-com-date { font-size: 0.7rem; color: #94a3b8; margin-left: auto; }
.bnd-com-text { color: #334155; }
.bnd-traza { border-left:2px solid #e2e8f0; margin-left:4px; padding-left:10px; }
.bnd-traza-fila { display:flex; align-items:flex-start; gap:8px; padding:3px 0; font-size:.8rem; color:#334155; }
.bnd-traza-hora { color:#94a3b8; font-variant-numeric:tabular-nums; min-width:42px; }
/* Agregar comentario */
.bnd-add-com { display: flex; gap: 8px; align-items: flex-end; }
.bnd-add-com textarea {
@@ -702,7 +705,50 @@ function _renderDetalle(d) {
<button onclick="_bndEnviarCom(${t.id})"><i class="fas fa-paper-plane"></i> Enviar</button>
</div></div>`;
document.getElementById('bnd-detail').innerHTML = hdr + secEx + secM + secRel + secC + secCom;
const secTraza = `<div class="bnd-sec">
<div class="bnd-sec-title"><i class="fas fa-clock-rotate-left"></i> Qué pasó con este turno</div>
<div id="bnd-traza"><span style="color:#94a3b8;font-size:.82rem">
<i class="fas fa-spinner fa-spin"></i> Cargando…</span></div>
</div>`;
document.getElementById('bnd-detail').innerHTML = hdr + secEx + secM + secRel + secC + secCom + secTraza;
_bndCargarTraza(t.id);
}
// ── Bitácora del turno ────────────────────────────────────────
// Los comentarios ya salen arriba; aquí va el recorrido completo, incluidas las
// acciones administrativas (por ejemplo el retiro de un formulario, con motivo).
const BND_TRAZA_ICO = {
turno:'fa-plus-circle', llamado:'fa-bullhorn', atencion:'fa-user-check',
muestra:'fa-vial', fin:'fa-flag-checkered', doc:'fa-paper-plane',
doc_ok:'fa-file-signature', comentario:'fa-comment',
alerta:'fa-triangle-exclamation', admin:'fa-user-shield',
};
const BND_TRAZA_COLOR = { alerta:'#dc2626', doc_ok:'#16a34a', comentario:'#2563eb', fin:'#0f766e', admin:'#7c3aed' };
async function _bndCargarTraza(turnoId) {
const cont = document.getElementById('bnd-traza');
if (!cont) return;
const esc = x => String(x ?? '').replace(/[<>&"]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c]));
try {
const j = await fetch(`modules/turnero/api/get_traza_turno.php?turno_id=${turnoId}`).then(r=>r.json());
if (!j.ok || !j.eventos?.length) {
cont.innerHTML = '<span style="color:#94a3b8;font-size:.82rem">Sin registros</span>'; return;
}
const hora = c => c ? new Date(c.replace(' ','T')).toLocaleTimeString('es-CO',{hour:'2-digit',minute:'2-digit'}) : '';
cont.innerHTML = '<div class="bnd-traza">' + j.eventos.map(e => `
<div class="bnd-traza-fila">
<span class="bnd-traza-hora">${hora(e.cuando)}</span>
<i class="fas ${BND_TRAZA_ICO[e.tipo]||'fa-circle'}"
style="color:${BND_TRAZA_COLOR[e.tipo]||'#94a3b8'};width:14px;font-size:.72rem;margin-top:2px"></i>
<div style="flex:1">${esc(e.texto)}
${e.quien ? `<span style="color:#64748b;font-style:italic"> ${esc(e.quien)}</span>` : ''}
${e.extra ? `<div style="color:#b45309;font-size:.74rem">${esc(e.extra)}</div>` : ''}
</div>
</div>`).join('') + '</div>';
} catch (_) {
cont.innerHTML = '<span style="color:#94a3b8;font-size:.82rem">Error al cargar</span>';
}
}
// ── Marcar visto ──────────────────────────────────────────────