From 00232b351380e7dbeb76101927dd52a2d3067c9b Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Mon, 11 May 2026 13:45:36 -0500 Subject: [PATCH] up --- api/lab/get_metricas.php | 74 +++++++++++++++++++++++---- lab_reportes.php | 108 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 11 deletions(-) diff --git a/api/lab/get_metricas.php b/api/lab/get_metricas.php index 83f7e19..c487899 100644 --- a/api/lab/get_metricas.php +++ b/api/lab/get_metricas.php @@ -167,18 +167,70 @@ try { [$desde, $hasta] ); + // ── 12. Tiempo de traslado: desde 'en_camino' hasta 'en_domicilio' ──────── + // Fuente: tabla lab_actividad_admin, accion='en_camino' y accion='en_domicilio' + // Se empareja por entidad_id (domicilio_id) y se exige que en_domicilio + // ocurra después de en_camino y dentro de un margen de 4 horas. + $traslado = $db->fetch( + "SELECT + ROUND(AVG(TIMESTAMPDIFF(MINUTE, tc.created_at, td.created_at)), 1) AS promedio_min, + MIN(TIMESTAMPDIFF(MINUTE, tc.created_at, td.created_at)) AS min_min, + MAX(TIMESTAMPDIFF(MINUTE, tc.created_at, td.created_at)) AS max_min, + COUNT(*) AS con_datos, + SUM(TIMESTAMPDIFF(MINUTE, tc.created_at, td.created_at) <= 15) AS dentro_15min, + SUM(TIMESTAMPDIFF(MINUTE, tc.created_at, td.created_at) <= 30) AS dentro_30min + FROM lab_actividad_admin tc + JOIN lab_actividad_admin td + ON td.entidad_id = tc.entidad_id + AND td.modulo = 'domicilios' + AND td.accion = 'en_domicilio' + AND td.created_at > tc.created_at + AND td.created_at <= DATE_ADD(tc.created_at, INTERVAL 4 HOUR) + JOIN lab_domicilios d ON d.id = tc.entidad_id + WHERE tc.modulo = 'domicilios' + AND tc.accion = 'en_camino' + AND d.fecha_programada BETWEEN ? AND ?", + [$desde, $hasta] + ) ?: []; + + // ── 13. Tiempo de traslado por enfermera (top 10 más rápidas) ───────────── + $trasladoPorEnfermera = $db->fetchAll( + "SELECT e.nombre_completo, + ROUND(AVG(TIMESTAMPDIFF(MINUTE, tc.created_at, td.created_at)), 1) AS promedio_min, + COUNT(*) AS viajes + FROM lab_actividad_admin tc + JOIN lab_actividad_admin td + ON td.entidad_id = tc.entidad_id + AND td.modulo = 'domicilios' + AND td.accion = 'en_domicilio' + AND td.created_at > tc.created_at + AND td.created_at <= DATE_ADD(tc.created_at, INTERVAL 4 HOUR) + JOIN lab_domicilios d ON d.id = tc.entidad_id + JOIN lab_asignaciones a ON a.domicilio_id = d.id AND a.estado NOT IN ('liberada') + JOIN lab_enfermeras e ON e.id = a.enfermera_id + WHERE tc.modulo = 'domicilios' + AND tc.accion = 'en_camino' + AND d.fecha_programada BETWEEN ? AND ? + GROUP BY e.id, e.nombre_completo + ORDER BY promedio_min ASC + LIMIT 10", + [$desde, $hasta] + ); + jsonOk([ - 'por_dia' => $porDia, - 'kpi' => $kpi, - 'tiempos' => $tiempos, - 'tiempo_agendamiento' => $tiempoAgen, - 'ranking_enfermeras' => $rankingEnf, - 'financiero' => $financiero, - 'por_modo_pago' => $porModoPago, - 'top_zonas' => $topZonas, - 'por_tipo_cliente' => $porTipoCliente, - 'top_examenes' => $topExamenes, - 'por_hora' => $porHora, + 'por_dia' => $porDia, + 'kpi' => $kpi, + 'tiempos' => $tiempos, + 'tiempo_agendamiento' => $tiempoAgen, + 'ranking_enfermeras' => $rankingEnf, + 'financiero' => $financiero, + 'por_modo_pago' => $porModoPago, + 'top_zonas' => $topZonas, + 'por_tipo_cliente' => $porTipoCliente, + 'top_examenes' => $topExamenes, + 'por_hora' => $porHora, + 'traslado' => $traslado, + 'traslado_por_enfermera' => $trasladoPorEnfermera, ]); } catch (Exception $e) { diff --git a/lab_reportes.php b/lab_reportes.php index 2d7c4df..df87106 100644 --- a/lab_reportes.php +++ b/lab_reportes.php @@ -400,6 +400,34 @@ require_once __DIR__ . '/shared/components/sidebar.php'; + + + +
+
+
+
+
+ +
+
Tiempo de traslado del enfermero
+
+ Mide cuánto tarda el enfermero desde que marca + En camino + hasta que registra + En domicilio +  ·  Fuente: log de cambios de estado (lab_actividad_admin) +
+
+
+
+
+
+
+
+
+
+ @@ -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) { `).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 = `
+ + Sin datos en el período. Se necesita que el enfermero haya marcado + En camino y luego En domicilio para el mismo domicilio. +
`; + 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 = ` +
+ +
+
+
+
${isNaN(prom) ? '—' : fmtMin(prom)}
+
Promedio
+
+
+
${t.min_min != null ? fmtMin(t.min_min) : '—'}
+
Mínimo
+
+
+
${t.max_min != null ? fmtMin(t.max_min) : '—'}
+
Máximo
+
+
+
+ ${pct15}% llegó en ≤15 min + ${pct30}% llegó en ≤30 min +
+
+ ${con} trayecto(s) medido(s) en el período +
+
+ +
+ ${porEnf.length ? ` +
Tiempo promedio por enfermero/a
+
+ + + + + + + + + ${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 ` + + + + + `; + }).join('')} + +
Enfermero/aProm. trasladoViajes
${esc(e.nombre_completo)}${fmtMin(p)}${e.viajes}
+
+
+
` : '

Sin desglose por enfermero/a

'} +
+
`; +} + function renderBarrasSimples(elId, entries, color) { const el = document.getElementById(elId); if (!el) return;