feat: métricas completas, export Excel domicilios con filtros, hora llegada/salida visible
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* GET /api/lab/get_metricas.php
|
||||
* Estadísticas de período para el módulo de reportes.
|
||||
* Parámetros: desde (Y-m-d), hasta (Y-m-d)
|
||||
*/
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('GET');
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$desde = $_GET['desde'] ?? date('Y-m-01');
|
||||
$hasta = $_GET['hasta'] ?? date('Y-m-d');
|
||||
|
||||
// ── 1. Domicilios por día ────────────────────────────────────────────────
|
||||
$porDia = $db->fetchAll(
|
||||
"SELECT DATE(fecha_programada) AS dia,
|
||||
COUNT(*) AS total,
|
||||
SUM(estado = 'completado') AS completados,
|
||||
SUM(estado = 'cancelado') AS cancelados,
|
||||
SUM(estado = 'reprogramado') AS reprogramados
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
GROUP BY DATE(fecha_programada)
|
||||
ORDER BY dia ASC",
|
||||
[$desde, $hasta]
|
||||
);
|
||||
|
||||
// ── 2. KPIs de tasa de éxito ────────────────────────────────────────────
|
||||
$kpi = $db->fetch(
|
||||
"SELECT COUNT(*) AS total,
|
||||
SUM(estado = 'completado') AS completados,
|
||||
SUM(estado = 'cancelado') AS cancelados,
|
||||
SUM(estado = 'reprogramado') AS reprogramados,
|
||||
SUM(estado NOT IN ('completado','cancelado','reprogramado')) AS en_curso
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?",
|
||||
[$desde, $hasta]
|
||||
) ?: [];
|
||||
|
||||
// ── 3. Tiempo promedio en domicilio ──────────────────────────────────────
|
||||
$tiempos = $db->fetch(
|
||||
"SELECT ROUND(AVG(TIMESTAMPDIFF(MINUTE, hora_llegada, hora_salida)), 1) AS promedio_min,
|
||||
MIN(TIMESTAMPDIFF(MINUTE, hora_llegada, hora_salida)) AS min_min,
|
||||
MAX(TIMESTAMPDIFF(MINUTE, hora_llegada, hora_salida)) AS max_min,
|
||||
COUNT(*) AS con_tiempos
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
AND hora_llegada IS NOT NULL AND hora_salida IS NOT NULL
|
||||
AND hora_salida > hora_llegada",
|
||||
[$desde, $hasta]
|
||||
) ?: [];
|
||||
|
||||
// ── 4. Tiempo promedio desde agendamiento hasta realización ─────────────
|
||||
$tiempoAgen = $db->fetch(
|
||||
"SELECT ROUND(AVG(DATEDIFF(fecha_programada, DATE(created_at))), 1) AS dias_promedio,
|
||||
MIN(DATEDIFF(fecha_programada, DATE(created_at))) AS dias_min,
|
||||
MAX(DATEDIFF(fecha_programada, DATE(created_at))) AS dias_max
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
AND estado = 'completado'",
|
||||
[$desde, $hasta]
|
||||
) ?: [];
|
||||
|
||||
// ── 5. Ranking de enfermeras ────────────────────────────────────────────
|
||||
$rankingEnf = $db->fetchAll(
|
||||
"SELECT e.id, e.nombre_completo,
|
||||
COUNT(a.id) AS asignados,
|
||||
SUM(d.estado = 'completado') AS completados,
|
||||
SUM(d.estado = 'cancelado') AS cancelados,
|
||||
ROUND(SUM(d.estado='completado') / NULLIF(COUNT(a.id),0) * 100, 1) AS tasa_pct
|
||||
FROM lab_asignaciones a
|
||||
JOIN lab_enfermeras e ON e.id = a.enfermera_id
|
||||
JOIN lab_domicilios d ON d.id = a.domicilio_id
|
||||
WHERE d.fecha_programada BETWEEN ? AND ?
|
||||
GROUP BY e.id, e.nombre_completo
|
||||
ORDER BY completados DESC, asignados DESC",
|
||||
[$desde, $hasta]
|
||||
);
|
||||
|
||||
// ── 6. Financiero ────────────────────────────────────────────────────────
|
||||
$financiero = $db->fetch(
|
||||
"SELECT SUM(COALESCE(valor_domicilio,0) + COALESCE(valor_copago,0)) AS total_bruto,
|
||||
SUM(CASE WHEN pago_estado='pagado' THEN COALESCE(valor_domicilio,0)+COALESCE(valor_copago,0) ELSE 0 END) AS cobrado,
|
||||
SUM(CASE WHEN pago_estado='pendiente' THEN COALESCE(valor_domicilio,0)+COALESCE(valor_copago,0) ELSE 0 END) AS pendiente,
|
||||
SUM(CASE WHEN pago_estado='exento' THEN COALESCE(valor_domicilio,0)+COALESCE(valor_copago,0) ELSE 0 END) AS exento,
|
||||
COUNT(CASE WHEN pago_estado='pagado' THEN 1 END) AS n_pagados,
|
||||
COUNT(CASE WHEN pago_estado='pendiente' THEN 1 END) AS n_pendientes,
|
||||
COUNT(CASE WHEN pago_estado='exento' THEN 1 END) AS n_exentos
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?",
|
||||
[$desde, $hasta]
|
||||
) ?: [];
|
||||
|
||||
// ── 7. Por modo de pago ─────────────────────────────────────────────────
|
||||
$porModoPago = $db->fetchAll(
|
||||
"SELECT COALESCE(pago_modo, 'no_especificado') AS modo,
|
||||
COUNT(*) AS n,
|
||||
SUM(COALESCE(valor_domicilio,0)+COALESCE(valor_copago,0)) AS total
|
||||
FROM lab_domicilios
|
||||
WHERE pago_estado = 'pagado'
|
||||
AND fecha_programada BETWEEN ? AND ?
|
||||
GROUP BY pago_modo
|
||||
ORDER BY n DESC",
|
||||
[$desde, $hasta]
|
||||
);
|
||||
|
||||
// ── 8. Top zonas (barrio / ciudad) ──────────────────────────────────────
|
||||
$topZonas = $db->fetchAll(
|
||||
"SELECT COALESCE(NULLIF(TRIM(barrio),''), NULLIF(TRIM(ciudad),''), 'Sin datos') AS zona,
|
||||
COUNT(*) AS n
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
GROUP BY zona
|
||||
ORDER BY n DESC
|
||||
LIMIT 10",
|
||||
[$desde, $hasta]
|
||||
);
|
||||
|
||||
// ── 9. Domicilios por tipo de cliente ────────────────────────────────────
|
||||
$porTipoCliente = $db->fetchAll(
|
||||
"SELECT COALESCE(tipo_cliente,'particular') AS tipo,
|
||||
COALESCE(seguro_nombre,'') AS seguro,
|
||||
COUNT(*) AS n
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
GROUP BY tipo, seguro
|
||||
ORDER BY n DESC",
|
||||
[$desde, $hasta]
|
||||
);
|
||||
|
||||
// ── 10. Top exámenes solicitados (simple word-frequency) ─────────────────
|
||||
// Traemos todos los valores y hacemos el conteo en PHP para no depender de
|
||||
// funciones de texto avanzadas de MySQL que podrían variar según servidor.
|
||||
$examRows = $db->fetchAll(
|
||||
"SELECT examenes_solicitados FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
AND examenes_solicitados IS NOT NULL AND examenes_solicitados != ''",
|
||||
[$desde, $hasta]
|
||||
);
|
||||
$examConteo = [];
|
||||
foreach ($examRows as $row) {
|
||||
// Dividir por coma, punto y coma o salto de línea
|
||||
$items = preg_split('/[,;\n]+/', $row['examenes_solicitados'] ?? '');
|
||||
foreach ($items as $item) {
|
||||
$item = trim($item);
|
||||
if (strlen($item) > 2) {
|
||||
$k = mb_strtolower($item);
|
||||
$examConteo[$k] = ($examConteo[$k] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
arsort($examConteo);
|
||||
$topExamenes = array_slice(
|
||||
array_map(fn($k, $v) => ['examen' => $k, 'n' => $v], array_keys($examConteo), $examConteo),
|
||||
0, 10
|
||||
);
|
||||
|
||||
// ── 11. Horas del día con más agendamientos ─────────────────────────────
|
||||
$porHora = $db->fetchAll(
|
||||
"SELECT HOUR(hora_programada) AS hora, COUNT(*) AS n
|
||||
FROM lab_domicilios
|
||||
WHERE fecha_programada BETWEEN ? AND ?
|
||||
AND hora_programada IS NOT NULL
|
||||
GROUP BY HOUR(hora_programada)
|
||||
ORDER BY hora ASC",
|
||||
[$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,
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[get_metricas] ' . $e->getMessage());
|
||||
jsonError($e->getMessage(), 500);
|
||||
}
|
||||
Reference in New Issue
Block a user