Files
whatsapp/modules/turnero/api/get_historial.php
T
Lizandro GuarnizoandClaude Sonnet 4.6 f363631b32 Turnero: mejoras UI, historial detallado, kiosko scanner, firma por turno
- Kiosko: detector scanner (gap < 80ms), iconos 90px, fa-person-pregnant, ticket Arial
- Recepción: vincular_paciente.php para persistir paciente_id antes de firmar consentimiento
- Historial y dashboard: recepción desk, empleado recepción/muestras, exámenes y comentarios
- display_global: turno más pequeño, lugar-nombre más grande
- lab_pacientes: modal compacta con form-sm
- Configuración: icono embarazada fa-person-pregnant en prioridades
- ver_formulario_enviado: firma por campo (fix doble firma)
- docker-compose.local.yml: override BD producción para dev local

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 22:07:09 -05:00

215 lines
8.1 KiB
PHP

<?php
/**
* modules/turnero/api/get_historial.php
* GET Historial paginado de turnos con filtros multi-día.
*
* Parámetros (todos opcionales):
* fecha_desde YYYY-MM-DD (default: hace 7 días)
* fecha_hasta YYYY-MM-DD (default: hoy)
* estado string ("finalizado","ausente","cancelado","en_servicio",... o vacío = todos)
* lugar_id int (0 = todos)
* prioridad_id int (0 = todos)
* q string búsqueda libre por nombre/código
* page int (default 1)
* per_page int (default 50, max 200)
*/
require_once __DIR__ . '/_helpers.php';
requireTurnero();
requireMethod('GET');
$pdo = db();
// ── Parámetros ────────────────────────────────────────────────
$hoy = date('Y-m-d');
$hace7 = date('Y-m-d', strtotime('-6 days'));
$fechaDesde = trim($_GET['fecha_desde'] ?? '');
$fechaHasta = trim($_GET['fecha_hasta'] ?? '');
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaDesde)) $fechaDesde = $hace7;
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaHasta)) $fechaHasta = $hoy;
if ($fechaDesde > $fechaHasta) $fechaDesde = $fechaHasta;
$estadoFiltro = trim($_GET['estado'] ?? '');
$lugarId = (int)($_GET['lugar_id'] ?? 0);
$prioridadId = (int)($_GET['prioridad_id'] ?? 0);
$pacienteId = (int)($_GET['paciente_id'] ?? 0);
$q = trim($_GET['q'] ?? '');
$perPage = min(200, max(10, (int)($_GET['per_page'] ?? 50)));
$page = max(1, (int)($_GET['page'] ?? 1));
$offset = ($page - 1) * $perPage;
// ── Base WHERE ───────────────────────────────────────────────
// Unimos turnos con sesiones para filtrar por fecha de sesión
$where = ['DATE(s.fecha) BETWEEN ? AND ?'];
$binds = [$fechaDesde, $fechaHasta];
$estadosValidos = ['espera','en_recepcion','en_espera_lugar','en_servicio','finalizado','ausente','cancelado'];
if ($estadoFiltro && in_array($estadoFiltro, $estadosValidos, true)) {
$where[] = 't.estado = ?';
$binds[] = $estadoFiltro;
}
if ($lugarId > 0) {
$where[] = 't.lugar_destino_id = ?';
$binds[] = $lugarId;
}
if ($prioridadId > 0) {
$where[] = 't.prioridad_id = ?';
$binds[] = $prioridadId;
}
if ($pacienteId > 0) {
$where[] = 'ts2.paciente_id = ?';
$binds[] = $pacienteId;
}
if ($q !== '') {
$where[] = '(t.codigo LIKE ? OR t.paciente_nombre LIKE ? OR s2.nombre_completo LIKE ?)';
$like = '%' . $q . '%';
$binds[] = $like;
$binds[] = $like;
$binds[] = $like;
}
$whereStr = 'WHERE ' . implode(' AND ', $where);
// ── Conteo total ──────────────────────────────────────────────
$sqlCount = "
SELECT COUNT(*) AS total
FROM turnero_turnos t
JOIN turnero_sesiones s ON s.id = t.sesion_id
LEFT JOIN turnero_solicitudes ts2 ON ts2.turno_id = t.id
LEFT JOIN lab_pacientes s2 ON s2.id = ts2.paciente_id
$whereStr";
$stmtCount = $pdo->prepare($sqlCount);
$stmtCount->execute($binds);
$totalRows = (int)$stmtCount->fetchColumn();
$totalPages = max(1, (int)ceil($totalRows / $perPage));
if ($page > $totalPages) $page = $totalPages;
$offset = ($page - 1) * $perPage;
// ── Lista paginada ────────────────────────────────────────────
$sqlTurnos = "
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,
s.fecha AS fecha_sesion,
p.codigo AS prioridad_codigo,
p.nombre AS prioridad_nombre,
p.color AS prioridad_color,
l.nombre AS lugar_nombre,
s2.nombre_completo AS paciente_bd,
rd.nombre AS recepcion_desk_nombre,
ur.full_name AS atendido_recepcion_nombre,
ul.full_name AS atendido_lugar_nombre,
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at,
COALESCE(t.inicio_recepcion_at, t.fin_recepcion_at)) / 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
FROM turnero_turnos t
JOIN turnero_sesiones s ON s.id = t.sesion_id
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 ts2 ON ts2.turno_id = t.id
LEFT JOIN lab_pacientes s2 ON s2.id = ts2.paciente_id
$whereStr
ORDER BY t.creado_at DESC
LIMIT ? OFFSET ?";
$bindsPage = array_merge($binds, [$perPage, $offset]);
$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
$stmtExam = $pdo->prepare(
"SELECT DISTINCT et.nombre, et.codigo
FROM turnero_examen_items tei
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
)
ORDER BY et.nombre ASC"
);
$stmtExam->execute([(int)$turno['id']]);
$turno['examenes'] = $stmtExam->fetchAll(PDO::FETCH_ASSOC);
// Consentimientos
$stmtCons = $pdo->prepare(
"SELECT 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 = ?
ORDER BY tc.estado DESC, tc.firmado_at DESC"
);
$stmtCons->execute([(int)$turno['id']]);
$turno['consentimientos'] = $stmtCons->fetchAll(PDO::FETCH_ASSOC);
// Comentarios
$stmtCom = $pdo->prepare(
"SELECT usuario_nombre, comentario, tipo, creado_at
FROM turnero_comentarios
WHERE turno_id = ?
ORDER BY creado_at DESC
LIMIT 3"
);
$stmtCom->execute([(int)$turno['id']]);
$turno['comentarios'] = $stmtCom->fetchAll(PDO::FETCH_ASSOC);
}
// ── Resumen por estado (del rango filtrado) ───────────────────
$sqlRes = "
SELECT t.estado, COUNT(*) AS cnt
FROM turnero_turnos t
JOIN turnero_sesiones s ON s.id = t.sesion_id
LEFT JOIN turnero_solicitudes ts2 ON ts2.turno_id = t.id
LEFT JOIN lab_pacientes s2 ON s2.id = ts2.paciente_id
$whereStr
GROUP BY t.estado";
$stmtRes = $pdo->prepare($sqlRes);
$stmtRes->execute($binds);
$resumenEstados = [];
foreach ($stmtRes->fetchAll(PDO::FETCH_ASSOC) as $r) {
$resumenEstados[$r['estado']] = (int)$r['cnt'];
}
// ── Catálogos para los selectores (si pide con ?meta=1) ──────
$meta = [];
if (!empty($_GET['meta'])) {
$meta['lugares'] = $pdo->query(
"SELECT id, nombre FROM turnero_lugares WHERE activo=1 ORDER BY sort_order ASC, nombre ASC"
)->fetchAll(PDO::FETCH_ASSOC);
$meta['prioridades'] = $pdo->query(
"SELECT id, nombre, codigo, color FROM turnero_prioridades ORDER BY orden_peso ASC"
)->fetchAll(PDO::FETCH_ASSOC);
}
jsonOk([
'fecha_desde' => $fechaDesde,
'fecha_hasta' => $fechaHasta,
'total' => $totalRows,
'page' => $page,
'per_page' => $perPage,
'total_pages' => $totalPages,
'resumen_estados'=> $resumenEstados,
'turnos' => $turnos,
'meta' => $meta,
]);