perf(historial): reemplazar N+1 queries por 3 batch queries

Las 3 subqueries por turno (examenes, consentimientos, comentarios)
× 50 turnos = 150 round-trips al servidor remoto (~83ms c/u) sumaban
12.5s de espera, causando timeouts o página sin carga.

Ahora se agrupan en 3 queries con IN(?) sobre todos los IDs del lote.
Tiempo total: ~357ms (35x más rápido).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-28 08:54:04 -05:00
co-authored by Claude Sonnet 4.6
parent 70fda3dacc
commit dffb242522
+48 -23
View File
@@ -136,42 +136,67 @@ $stmtTurnos = $pdo->prepare($sqlTurnos);
$stmtTurnos->execute($bindsPage);
$turnos = $stmtTurnos->fetchAll(PDO::FETCH_ASSOC);
// ── Enriquecer con exámenes y consentimientos ─────────────────
foreach ($turnos as &$turno) {
// Exámenes
// ── Enriquecer con exámenes, consentimientos y comentarios (batch)
$turnoIds = array_column($turnos, 'id');
$examenes = [];
$consentimientos = [];
$comentarios = [];
if ($turnoIds) {
$ph = implode(',', array_fill(0, count($turnoIds), '?'));
// Exámenes — batch
$stmtExam = $pdo->prepare(
"SELECT DISTINCT et.nombre, et.codigo
FROM turnero_examen_items tei
"SELECT ts.turno_id, et.nombre, et.codigo
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 tei.solicitud_id = (
SELECT id FROM turnero_solicitudes WHERE turno_id = ? LIMIT 1
)
WHERE ts.turno_id IN ($ph)
ORDER BY et.nombre ASC"
);
$stmtExam->execute([(int)$turno['id']]);
$turno['examenes'] = $stmtExam->fetchAll(PDO::FETCH_ASSOC);
$stmtExam->execute($turnoIds);
foreach ($stmtExam->fetchAll(PDO::FETCH_ASSOC) as $row) {
$examenes[(int)$row['turno_id']][] = ['nombre' => $row['nombre'], 'codigo' => $row['codigo']];
}
// Consentimientos
// Consentimientos — batch
$stmtCons = $pdo->prepare(
"SELECT tc.id, f.nombre, tc.estado, tc.firmado_at
"SELECT tc.turno_id, tc.id, f.nombre, tc.estado, tc.firmado_at
FROM turnero_consentimientos tc
LEFT JOIN lab_formularios f ON f.id = tc.formulario_id
WHERE tc.turno_id = ?
WHERE tc.turno_id IN ($ph)
ORDER BY tc.estado DESC, tc.firmado_at DESC"
);
$stmtCons->execute([(int)$turno['id']]);
$turno['consentimientos'] = $stmtCons->fetchAll(PDO::FETCH_ASSOC);
$stmtCons->execute($turnoIds);
foreach ($stmtCons->fetchAll(PDO::FETCH_ASSOC) as $row) {
$tid = (int)$row['turno_id'];
unset($row['turno_id']);
$consentimientos[$tid][] = $row;
}
// Comentarios
// Comentarios — batch (últimos 3 por turno via subquery)
$stmtCom = $pdo->prepare(
"SELECT usuario_nombre, comentario, tipo, creado_at
FROM turnero_comentarios
WHERE turno_id = ?
ORDER BY creado_at DESC
LIMIT 3"
"SELECT tc.turno_id, tc.usuario_nombre, tc.comentario, tc.tipo, tc.creado_at
FROM turnero_comentarios tc
WHERE tc.turno_id IN ($ph)
ORDER BY tc.turno_id, tc.creado_at DESC"
);
$stmtCom->execute([(int)$turno['id']]);
$turno['comentarios'] = $stmtCom->fetchAll(PDO::FETCH_ASSOC);
$stmtCom->execute($turnoIds);
foreach ($stmtCom->fetchAll(PDO::FETCH_ASSOC) as $row) {
$tid = (int)$row['turno_id'];
if (!isset($comentarios[$tid])) $comentarios[$tid] = [];
if (count($comentarios[$tid]) < 3) {
unset($row['turno_id']);
$comentarios[$tid][] = $row;
}
}
}
foreach ($turnos as &$turno) {
$tid = (int)$turno['id'];
$turno['examenes'] = $examenes[$tid] ?? [];
$turno['consentimientos'] = $consentimientos[$tid] ?? [];
$turno['comentarios'] = $comentarios[$tid] ?? [];
}
// ── Resumen por estado (del rango filtrado) ───────────────────