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:
co-authored by
Claude Opus 5
parent
d4674f087d
commit
487673dc0f
@@ -110,6 +110,14 @@ Layout::open('Historial de Turnos', 'fas fa-history');
|
||||
|
||||
/* ── Documentos firmados ── */
|
||||
.docs-section { margin-top:10px; padding-top:10px; border-top:1px solid #e2e8f0; }
|
||||
.traza { border-left:2px solid #e2e8f0; margin:2px 0 0 6px; padding-left:10px; }
|
||||
.traza-fila { display:flex; align-items:flex-start; gap:8px; padding:3px 0; font-size:.78rem; }
|
||||
.traza-hora { color:#94a3b8; font-variant-numeric:tabular-nums; min-width:42px; }
|
||||
.traza-ico { width:14px; text-align:center; margin-top:2px; font-size:.72rem; }
|
||||
.traza-txt { flex:1; color:#334155; }
|
||||
.traza-quien { color:#64748b; font-style:italic; }
|
||||
.traza-extra { color:#b45309; font-size:.74rem; margin-top:1px; }
|
||||
.traza-nota { color:#94a3b8; font-size:.7rem; margin-top:6px; font-style:italic; }
|
||||
.docs-lbl { font-size:.65rem; font-weight:700; text-transform:uppercase; letter-spacing:.07em; color:#94a3b8; margin-bottom:5px; }
|
||||
.doc-pill { display:inline-flex; align-items:center; gap:4px; padding:3px 10px; border-radius:99px;
|
||||
font-size:.73rem; font-weight:600; margin:2px 2px; text-decoration:none; transition:opacity .15s; }
|
||||
@@ -594,6 +602,10 @@ function renderDetalle(t) {
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
${relHtml}${examHtml}${comHtml}
|
||||
<div class="docs-section" id="traza-${t.id}">
|
||||
<div class="docs-lbl"><i class="fas fa-clock-rotate-left me-1"></i>Qué pasó con este turno</div>
|
||||
<span class="text-muted small"><i class="fas fa-spinner fa-spin me-1"></i>Cargando…</span>
|
||||
</div>
|
||||
<div class="docs-section" id="docs-${t.id}">
|
||||
<div class="docs-lbl"><i class="fas fa-file-signature me-1"></i>Documentos</div>
|
||||
<span class="text-muted small"><i class="fas fa-spinner fa-spin me-1"></i>Cargando…</span>
|
||||
@@ -614,11 +626,79 @@ function toggleDetalle(rowId, btn, turnoId) {
|
||||
docsEl.dataset.loaded = '1';
|
||||
cargarDocumentosTurno(turnoId, docsEl);
|
||||
}
|
||||
const trazaEl = document.getElementById(`traza-${turnoId}`);
|
||||
if (trazaEl && !trazaEl.dataset.loaded) {
|
||||
trazaEl.dataset.loaded = '1';
|
||||
cargarTrazaTurno(turnoId, trazaEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BASE_URL_ROOT = '<?= BASE_URL ?>';
|
||||
|
||||
// ── Bitácora del turno ────────────────────────────────────────
|
||||
// Junta en una sola línea de tiempo lo que antes estaba repartido: el recorrido
|
||||
// del turno, los documentos, los comentarios del personal, las muestras y las
|
||||
// acciones administrativas auditadas.
|
||||
const TRAZA_ICONO = {
|
||||
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 TRAZA_COLOR = {
|
||||
alerta:'#dc2626', doc_ok:'#16a34a', comentario:'#2563eb',
|
||||
fin:'#0f766e', admin:'#7c3aed',
|
||||
};
|
||||
|
||||
async function cargarTrazaTurno(turnoId, container) {
|
||||
const titulo = '<div class="docs-lbl"><i class="fas fa-clock-rotate-left me-1"></i>Qué pasó con este turno</div>';
|
||||
try {
|
||||
const res = await fetch(`${API}get_traza_turno.php?turno_id=${turnoId}`);
|
||||
const json = await res.json();
|
||||
if (!json.ok) { container.innerHTML = titulo + '<span class="docs-empty">No se pudo cargar</span>'; return; }
|
||||
|
||||
const ev = json.eventos || [];
|
||||
if (!ev.length) { container.innerHTML = titulo + '<span class="docs-empty">Sin registros</span>'; return; }
|
||||
|
||||
const hora = c => c ? new Date(c.replace(' ','T')).toLocaleTimeString('es-CO',{hour:'2-digit',minute:'2-digit'}) : '';
|
||||
const filas = ev.map(e => {
|
||||
const color = TRAZA_COLOR[e.tipo] || '#94a3b8';
|
||||
const ico = TRAZA_ICONO[e.tipo] || 'fa-circle';
|
||||
const quien = e.quien ? `<span class="traza-quien">${esc(e.quien)}</span>` : '';
|
||||
const extra = e.extra ? `<div class="traza-extra">${esc(e.extra)}</div>` : '';
|
||||
return `<div class="traza-fila">
|
||||
<span class="traza-hora">${hora(e.cuando)}</span>
|
||||
<i class="fas ${ico} traza-ico" style="color:${color}"></i>
|
||||
<div class="traza-txt"><span>${esc(e.texto)}</span> ${quien}${extra}</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// El chat no se vuelca aquí: son datos personales del paciente y esta es
|
||||
// una pantalla de consulta. Se indica que existe y se enlaza.
|
||||
const chat = json.chat
|
||||
? `<div class="traza-fila">
|
||||
<span class="traza-hora"></span>
|
||||
<i class="fab fa-whatsapp traza-ico" style="color:#25d366"></i>
|
||||
<div class="traza-txt">
|
||||
<a href="erp.php?m=turnero&v=chat&user_id=${json.chat.user_id}" target="_blank">
|
||||
${json.chat.mensajes} mensaje(s) de WhatsApp ese día — ver conversación
|
||||
</a>
|
||||
</div>
|
||||
</div>` : '';
|
||||
|
||||
// Se dice qué NO quedó registrado, para que la ausencia de un evento no
|
||||
// se lea como prueba de que no ocurrió.
|
||||
const nota = (json.sinRastro && json.sinRastro.length)
|
||||
? `<div class="traza-nota">El sistema aún no deja rastro de: ${json.sinRastro.map(esc).join(' · ')}.</div>`
|
||||
: '';
|
||||
|
||||
container.innerHTML = titulo + `<div class="traza">${filas}${chat}</div>` + nota;
|
||||
} catch (_) {
|
||||
container.innerHTML = titulo + '<span class="docs-empty">Error al cargar</span>';
|
||||
}
|
||||
}
|
||||
|
||||
async function cargarDocumentosTurno(turnoId, container) {
|
||||
try {
|
||||
const res = await fetch(`${API}get_consentimientos.php?turno_id=${turnoId}`);
|
||||
|
||||
Reference in New Issue
Block a user