Files
whatsapp/modules/turnero/api/get_dashboard.php
T

286 lines
11 KiB
PHP

<?php
/**
* modules/turnero/api/get_dashboard.php
* GET ?fecha=YYYY-MM-DD (por defecto = hoy)
*
* Devuelve métricas del día para el dashboard del turnero:
* - resumen global (total, atendidos, pendientes, ausentes, tiempo promedio)
* - desglose por prioridad
* - desglose por lugar
* - desglose por recepcionista y por bacteriólogo (quién atendió a cuántos)
* - lista de turnos del día (para tabla detalle)
*/
require_once __DIR__ . '/_helpers.php';
requireTurnero();
requireMethod('GET');
$fecha = trim($_GET['fecha'] ?? '');
if ($fecha === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) {
$fecha = date('Y-m-d');
}
$pdo = db();
// ── 1. Sesión del día ─────────────────────────────────────────
$stmtSesion = $pdo->prepare(
'SELECT id, inicio_at, fin_at FROM turnero_sesiones WHERE fecha = ? LIMIT 1'
);
$stmtSesion->execute([$fecha]);
$sesion = $stmtSesion->fetch(PDO::FETCH_ASSOC);
if (!$sesion) {
jsonOk([
'fecha' => $fecha,
'sesion' => null,
'resumen' => [
'total' => 0,
'atendidos' => 0,
'en_espera' => 0,
'ausentes' => 0,
'cancelados' => 0,
'tiempo_espera_promedio_min' => null,
'tiempo_servicio_promedio_min' => null,
],
'por_prioridad' => [],
'por_lugar' => [],
'por_recepcionista' => [],
'por_bacteriologo' => [],
'consent_stats' => [],
'turnos' => [],
]);
}
$sesionId = (int)$sesion['id'];
// ── 2. Resumen global ─────────────────────────────────────────
$stmtRes = $pdo->prepare(
"SELECT
COUNT(*) AS total,
SUM(estado IN ('finalizado','en_servicio')) AS atendidos,
SUM(estado IN ('espera','en_recepcion','en_espera_lugar')) AS en_espera,
SUM(estado = 'ausente') AS ausentes,
SUM(estado = 'cancelado') AS cancelados,
ROUND(
AVG(
CASE
WHEN inicio_recepcion_at IS NOT NULL AND creado_at IS NOT NULL
THEN TIMESTAMPDIFF(SECOND, creado_at, inicio_recepcion_at) / 60.0
END
), 1
) AS tiempo_espera_promedio_min,
ROUND(
AVG(
CASE
WHEN fin_lugar_at IS NOT NULL AND inicio_lugar_at IS NOT NULL
THEN TIMESTAMPDIFF(SECOND, inicio_lugar_at, fin_lugar_at) / 60.0
END
), 1
) AS tiempo_servicio_promedio_min,
ROUND(
AVG(
CASE
WHEN fin_lugar_at IS NOT NULL AND creado_at IS NOT NULL
THEN TIMESTAMPDIFF(SECOND, creado_at, fin_lugar_at) / 60.0
END
), 1
) AS tiempo_total_promedio_min
FROM turnero_turnos
WHERE sesion_id = ?"
);
$stmtRes->execute([$sesionId]);
$resumen = $stmtRes->fetch(PDO::FETCH_ASSOC);
// ── 3. Desglose por prioridad ─────────────────────────────────
$stmtPri = $pdo->prepare(
"SELECT p.codigo, p.nombre, p.color,
COUNT(t.id) AS total,
SUM(t.estado IN ('finalizado','en_servicio')) AS atendidos,
SUM(t.estado IN ('espera','en_recepcion','en_espera_lugar')) AS pendientes,
SUM(t.estado = 'ausente') AS ausentes
FROM turnero_turnos t
JOIN turnero_prioridades p ON p.id = t.prioridad_id
WHERE t.sesion_id = ?
GROUP BY p.id
ORDER BY p.orden_peso ASC"
);
$stmtPri->execute([$sesionId]);
$porPrioridad = $stmtPri->fetchAll(PDO::FETCH_ASSOC);
// ── 4. Desglose por lugar ─────────────────────────────────────
$stmtLug = $pdo->prepare(
"SELECT l.nombre AS lugar,
COUNT(t.id) AS total,
SUM(t.estado IN ('finalizado','en_servicio')) AS atendidos,
SUM(t.estado IN ('en_espera_lugar')) AS en_espera,
ROUND(AVG(
CASE
WHEN t.fin_lugar_at IS NOT NULL AND t.inicio_lugar_at IS NOT NULL
THEN TIMESTAMPDIFF(SECOND, t.inicio_lugar_at, t.fin_lugar_at) / 60.0
END
), 1) AS tiempo_servicio_promedio_min
FROM turnero_turnos t
JOIN turnero_lugares l ON l.id = t.lugar_destino_id
WHERE t.sesion_id = ?
GROUP BY l.id
ORDER BY l.sort_order ASC, l.nombre ASC"
);
$stmtLug->execute([$sesionId]);
$porLugar = $stmtLug->fetchAll(PDO::FETCH_ASSOC);
// ── 4b. Desglose por recepcionista ────────────────────────────
// Mismo molde que el ranking de enfermeras de lab_domicilios
// (api/lab/get_metricas.php). atendido_recepcion_por guarda al PRIMER
// recepcionista que tomó el turno (COALESCE en cambiar_estado.php).
$stmtRec = $pdo->prepare(
"SELECT u.id,
COALESCE(NULLIF(u.full_name, ''), u.username) AS nombre,
COUNT(t.id) AS total,
SUM(t.estado IN ('finalizado','en_servicio','en_espera_lugar')) AS atendidos,
SUM(t.estado = 'ausente') AS ausentes,
SUM(t.estado = 'cancelado') AS cancelados,
ROUND(AVG(
CASE
WHEN t.fin_recepcion_at IS NOT NULL AND t.inicio_recepcion_at IS NOT NULL
THEN TIMESTAMPDIFF(SECOND, t.inicio_recepcion_at, t.fin_recepcion_at) / 60.0
END
), 1) AS tiempo_promedio_min
FROM turnero_turnos t
JOIN admin_users u ON u.id = t.atendido_recepcion_por
WHERE t.sesion_id = ?
GROUP BY u.id, u.full_name, u.username
ORDER BY atendidos DESC, total DESC"
);
$stmtRec->execute([$sesionId]);
$porRecepcionista = $stmtRec->fetchAll(PDO::FETCH_ASSOC);
// ── 4c. Desglose por bacteriólogo (quien atendió en el lugar) ──
$stmtBac = $pdo->prepare(
"SELECT u.id,
COALESCE(NULLIF(u.full_name, ''), u.username) AS nombre,
COUNT(t.id) AS total,
SUM(t.estado = 'finalizado') AS finalizados,
SUM(t.estado = 'en_servicio') AS en_servicio,
SUM(t.estado = 'ausente') AS ausentes,
ROUND(AVG(
CASE
WHEN t.fin_lugar_at IS NOT NULL AND t.inicio_lugar_at IS NOT NULL
THEN TIMESTAMPDIFF(SECOND, t.inicio_lugar_at, t.fin_lugar_at) / 60.0
END
), 1) AS tiempo_promedio_min
FROM turnero_turnos t
JOIN admin_users u ON u.id = t.atendido_lugar_por
WHERE t.sesion_id = ?
GROUP BY u.id, u.full_name, u.username
ORDER BY finalizados DESC, total DESC"
);
$stmtBac->execute([$sesionId]);
$porBacteriologo = $stmtBac->fetchAll(PDO::FETCH_ASSOC);
// ── 5. Lista de turnos del día ────────────────────────────────
$stmtTurnos = $pdo->prepare(
"SELECT t.id, t.codigo, t.numero, t.estado,
t.paciente_nombre, t.paciente_cel,
t.creado_at, t.inicio_recepcion_at, t.fin_recepcion_at,
t.inicio_lugar_at, t.fin_lugar_at,
p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color,
l.nombre AS lugar_nombre,
s.nombre_completo AS paciente_bd,
s.numero_documento AS paciente_doc,
s.tipo_documento AS paciente_tipo_doc,
rd.nombre AS recepcion_desk_nombre,
ur.full_name AS atendido_recepcion_nombre,
ul.full_name AS atendido_lugar_nombre,
ts.observaciones AS notas,
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, COALESCE(t.inicio_recepcion_at, NOW())) / 60.0, 1) AS espera_min,
ROUND(TIMESTAMPDIFF(SECOND, t.inicio_lugar_at, COALESCE(t.fin_lugar_at, NOW())) / 60.0, 1) AS servicio_min,
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, t.fin_lugar_at) / 60.0, 1) AS total_min
FROM turnero_turnos t
JOIN turnero_prioridades p ON p.id = t.prioridad_id
LEFT JOIN turnero_lugares l ON l.id = t.lugar_destino_id
LEFT JOIN turnero_lugares rd ON rd.id = t.recepcion_desk_id
LEFT JOIN admin_users ur ON ur.id = t.atendido_recepcion_por
LEFT JOIN admin_users ul ON ul.id = t.atendido_lugar_por
LEFT JOIN turnero_solicitudes ts ON ts.turno_id = t.id
LEFT JOIN lab_pacientes s ON s.id = ts.paciente_id
WHERE t.sesion_id = ?
ORDER BY t.numero ASC"
);
$stmtTurnos->execute([$sesionId]);
$turnos = $stmtTurnos->fetchAll(PDO::FETCH_ASSOC);
// Enriquecer con exámenes y comentarios — en lote.
// Antes eran 2 consultas por turno dentro del foreach (N+1): con 80 turnos en
// el día eran 160 consultas evitables. Mismo patrón que get_historial.php.
$turnoIds = array_column($turnos, 'id');
$examenesPorTurno = [];
$comentariosPorTurno = [];
if ($turnoIds) {
$ph = implode(',', array_fill(0, count($turnoIds), '?'));
$stmtEx = $pdo->prepare(
"SELECT DISTINCT ts.turno_id, et.nombre
FROM turnero_solicitudes ts
JOIN turnero_examen_items tei ON tei.solicitud_id = ts.id
JOIN exam_tipos et ON et.id = tei.exam_tipo_id
WHERE ts.turno_id IN ($ph)
ORDER BY et.nombre ASC"
);
$stmtEx->execute($turnoIds);
foreach ($stmtEx->fetchAll(PDO::FETCH_ASSOC) as $row) {
$examenesPorTurno[(int)$row['turno_id']][] = ['nombre' => $row['nombre']];
}
$stmtCom = $pdo->prepare(
"SELECT turno_id, usuario_nombre, comentario, tipo, creado_at
FROM turnero_comentarios
WHERE turno_id IN ($ph)
ORDER BY turno_id, creado_at ASC"
);
$stmtCom->execute($turnoIds);
foreach ($stmtCom->fetchAll(PDO::FETCH_ASSOC) as $row) {
$tid = (int)$row['turno_id'];
unset($row['turno_id']);
$comentariosPorTurno[$tid][] = $row;
}
}
foreach ($turnos as &$turno) {
$tid = (int)$turno['id'];
$turno['examenes'] = $examenesPorTurno[$tid] ?? [];
$turno['comentarios'] = $comentariosPorTurno[$tid] ?? [];
}
unset($turno);
// ── 6. Consentimientos del día ────────────────────────────────
$stmtcons = $pdo->prepare(
"SELECT tc.estado, COUNT(*) AS cnt
FROM turnero_consentimientos tc
JOIN turnero_turnos t ON t.id = tc.turno_id
WHERE t.sesion_id = ?
GROUP BY tc.estado"
);
$stmtcons->execute([$sesionId]);
$consentStats = [];
foreach ($stmtcons->fetchAll(PDO::FETCH_ASSOC) as $r) {
$consentStats[$r['estado']] = (int)$r['cnt'];
}
jsonOk([
'fecha' => $fecha,
'sesion' => [
'id' => $sesionId,
'inicio' => $sesion['inicio_at'] ? date('H:i', strtotime($sesion['inicio_at'])) : null,
'fin' => $sesion['fin_at'] ? date('H:i', strtotime($sesion['fin_at'])) : null,
'abierta' => $sesion['fin_at'] === null,
],
'resumen' => $resumen,
'por_prioridad' => $porPrioridad,
'por_lugar' => $porLugar,
'por_recepcionista' => $porRecepcionista,
'por_bacteriologo' => $porBacteriologo,
'consent_stats' => $consentStats,
'turnos' => $turnos,
]);