This commit is contained in:
Lizandro Guarnizo
2026-05-11 13:45:36 -05:00
parent f197118acb
commit 00232b3513
2 changed files with 171 additions and 11 deletions
+108
View File
@@ -400,6 +400,34 @@ require_once __DIR__ . '/shared/components/sidebar.php';
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- Fila 1.5: Tiempo de traslado (en_camino → en_domicilio) -->
<!-- ══════════════════════════════════════════════════════════════ -->
<div class="row g-3 mb-3">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0 pb-1">
<div class="d-flex align-items-start gap-2">
<i class="fas fa-route mt-1" style="color:#00897b"></i>
<div>
<h6 class="fw-semibold mb-0">Tiempo de traslado del enfermero</h6>
<div class="text-muted" style="font-size:.78rem">
Mide cuánto tarda el enfermero desde que marca
<span class="badge" style="background:#fff3e0;color:#e65100;font-size:.72rem">En camino</span>
hasta que registra
<span class="badge" style="background:#e8f5e9;color:#2e7d32;font-size:.72rem">En domicilio</span>
&nbsp;·&nbsp; Fuente: log de cambios de estado (<code style="font-size:.72rem">lab_actividad_admin</code>)
</div>
</div>
</div>
</div>
<div class="card-body" id="traslado-card">
<div class="text-center py-4 text-muted"><i class="fas fa-spinner fa-spin"></i></div>
</div>
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════════════════ -->
<!-- Fila 2: Tiempo en domicilio + Hora pico -->
<!-- ══════════════════════════════════════════════════════════════ -->
@@ -968,6 +996,9 @@ async function cargarMetricas(desde, hasta) {
tcMap[k] = (tcMap[k] || 0) + parseInt(tc.n);
});
renderBarrasSimples('tipo-cliente-chart', Object.entries(tcMap), '#6d4c41');
// ── Traslado ──
renderTraslado(m.traslado || {}, m.traslado_por_enfermera || []);
}
// ── Órdenes (usa get_stats existente) ──────────────────────────────────────
@@ -1133,6 +1164,83 @@ function renderBarras(elId, datos) {
</div>`).join('');
}
function renderTraslado(t, porEnf) {
const el = document.getElementById('traslado-card');
if (!el) return;
const prom = parseFloat(t.promedio_min);
const con = parseInt(t.con_datos) || 0;
if (!con) {
el.innerHTML = `<div class="text-center py-3 text-muted small">
<i class="fas fa-info-circle me-1"></i>
Sin datos en el período. Se necesita que el enfermero haya marcado
<strong>En camino</strong> y luego <strong>En domicilio</strong> para el mismo domicilio.
</div>`;
return;
}
const pct15 = con ? Math.round((parseInt(t.dentro_15min)||0) / con * 100) : 0;
const pct30 = con ? Math.round((parseInt(t.dentro_30min)||0) / con * 100) : 0;
el.innerHTML = `
<div class="row g-3 align-items-start">
<!-- KPIs -->
<div class="col-lg-5">
<div class="row g-2 text-center mb-2">
<div class="col-4">
<div class="fw-bold fs-4" style="color:#00897b">${isNaN(prom) ? '—' : fmtMin(prom)}</div>
<div class="small text-muted">Promedio</div>
</div>
<div class="col-4">
<div class="fw-bold fs-4 text-success">${t.min_min != null ? fmtMin(t.min_min) : '—'}</div>
<div class="small text-muted">Mínimo</div>
</div>
<div class="col-4">
<div class="fw-bold fs-4 text-warning">${t.max_min != null ? fmtMin(t.max_min) : '—'}</div>
<div class="small text-muted">Máximo</div>
</div>
</div>
<div class="d-flex gap-2 justify-content-center mb-2" style="font-size:.82rem">
<span class="badge" style="background:#e8f5e9;color:#2e7d32">${pct15}% llegó en ≤15 min</span>
<span class="badge" style="background:#fff8e1;color:#f57f17">${pct30}% llegó en ≤30 min</span>
</div>
<div class="text-muted text-center" style="font-size:.75rem">
<i class="fas fa-database me-1"></i>${con} trayecto(s) medido(s) en el período
</div>
</div>
<!-- Tabla por enfermera -->
<div class="col-lg-7">
${porEnf.length ? `
<div class="small text-muted text-uppercase mb-1" style="font-size:.72rem">Tiempo promedio por enfermero/a</div>
<div style="overflow-x:auto">
<table class="table table-sm table-hover mb-0" style="font-size:.82rem">
<thead class="table-light"><tr>
<th>Enfermero/a</th>
<th class="text-center">Prom. traslado</th>
<th class="text-center">Viajes</th>
<th style="min-width:100px"></th>
</tr></thead>
<tbody>
${porEnf.map(e => {
const p = parseFloat(e.promedio_min);
const color = p <= 15 ? '#43a047' : p <= 30 ? '#fb8c00' : '#e53935';
const pct = Math.min(Math.round((p / 60) * 100), 100);
return `<tr>
<td class="text-truncate" style="max-width:160px" title="${esc(e.nombre_completo)}">${esc(e.nombre_completo)}</td>
<td class="text-center fw-semibold" style="color:${color}">${fmtMin(p)}</td>
<td class="text-center text-muted">${e.viajes}</td>
<td><div style="height:6px;background:#f0f0f0;border-radius:3px">
<div style="width:${pct}%;height:100%;background:${color};border-radius:3px"></div>
</div></td>
</tr>`;
}).join('')}
</tbody>
</table>
</div>` : '<p class="text-muted small text-center py-2">Sin desglose por enfermero/a</p>'}
</div>
</div>`;
}
function renderBarrasSimples(elId, entries, color) {
const el = document.getElementById(elId);
if (!el) return;