297 lines
12 KiB
PHP
297 lines
12 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: hoy; hace 12 meses si se pasa paciente_id)
|
|
* 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');
|
|
|
|
// paciente_id se lee primero: determina el rango de fechas por defecto.
|
|
$pacienteId = (int)($_GET['paciente_id'] ?? 0);
|
|
|
|
$fechaDesde = trim($_GET['fecha_desde'] ?? '');
|
|
$fechaHasta = trim($_GET['fecha_hasta'] ?? '');
|
|
|
|
// Rango por defecto cuando el cliente no manda fechas:
|
|
// - Ficha de paciente (paciente_id): ultimos 12 meses.
|
|
// Antes caia en "hoy", asi que el historial del paciente solo podia mostrar
|
|
// visitas del mismo dia y siempre respondia "sin visitas anteriores".
|
|
// - Listado general del historial: hoy (comportamiento original).
|
|
// Para ver el historial completo, el cliente pasa fecha_desde explicita.
|
|
$desdePorDefecto = $pacienteId > 0
|
|
? date('Y-m-d', strtotime('-12 months'))
|
|
: $hoy;
|
|
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaDesde)) $fechaDesde = $desdePorDefecto;
|
|
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);
|
|
$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,
|
|
s2.tipo_documento AS paciente_tipo_doc,
|
|
s2.numero_documento AS paciente_doc,
|
|
rd.nombre AS recepcion_desk_nombre,
|
|
ur.full_name AS atendido_recepcion_nombre,
|
|
ul.full_name AS atendido_lugar_nombre,
|
|
ts2.observaciones AS notas,
|
|
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,
|
|
ROUND(TIMESTAMPDIFF(SECOND, t.creado_at, t.fin_lugar_at) / 60.0, 1)
|
|
AS total_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, consentimientos, comentarios y tomas (batch) ─
|
|
$turnoIds = array_column($turnos, 'id');
|
|
$examenes = [];
|
|
$consentimientos = [];
|
|
$comentarios = [];
|
|
$relacionados = [];
|
|
|
|
if ($turnoIds) {
|
|
$ph = implode(',', array_fill(0, count($turnoIds), '?'));
|
|
|
|
// Exámenes — batch
|
|
$stmtExam = $pdo->prepare(
|
|
"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 ts.turno_id IN ($ph)
|
|
ORDER BY et.nombre ASC"
|
|
);
|
|
$stmtExam->execute($turnoIds);
|
|
foreach ($stmtExam->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$examenes[(int)$row['turno_id']][] = ['nombre' => $row['nombre'], 'codigo' => $row['codigo']];
|
|
}
|
|
|
|
// Consentimientos — batch
|
|
$stmtCons = $pdo->prepare(
|
|
"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 IN ($ph)
|
|
ORDER BY tc.estado DESC, tc.firmado_at DESC"
|
|
);
|
|
$stmtCons->execute($turnoIds);
|
|
foreach ($stmtCons->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$tid = (int)$row['turno_id'];
|
|
unset($row['turno_id']);
|
|
$consentimientos[$tid][] = $row;
|
|
}
|
|
|
|
// Comentarios — batch (últimos 3 por turno via subquery)
|
|
$stmtCom = $pdo->prepare(
|
|
"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 ASC"
|
|
);
|
|
$stmtCom->execute($turnoIds);
|
|
foreach ($stmtCom->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$tid = (int)$row['turno_id'];
|
|
if (!isset($comentarios[$tid])) $comentarios[$tid] = [];
|
|
// Se traen todos: cortar en 3 escondía notas —entre ellas el motivo de
|
|
// una ausencia— sin que nada indicara que faltaban.
|
|
if (count($comentarios[$tid]) < 20) {
|
|
unset($row['turno_id']);
|
|
$comentarios[$tid][] = $row;
|
|
}
|
|
}
|
|
|
|
// Turnos vinculados por muestras pendientes completadas en otra visita — batch (ambos sentidos)
|
|
// Sentido "seguimiento": este turno tenía una muestra pendiente que se completó en OTRO turno
|
|
$stmtRelFwd = $pdo->prepare(
|
|
"SELECT DISTINCT ts.turno_id AS turno_id, t2.id AS rel_id, t2.codigo AS rel_codigo, s2.fecha AS rel_fecha
|
|
FROM turnero_muestras tm
|
|
JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id
|
|
JOIN turnero_turnos t2 ON t2.id = tm.recibida_en_turno_id
|
|
JOIN turnero_sesiones s2 ON s2.id = t2.sesion_id
|
|
WHERE ts.turno_id IN ($ph) AND tm.recibida_en_turno_id IS NOT NULL"
|
|
);
|
|
$stmtRelFwd->execute($turnoIds);
|
|
foreach ($stmtRelFwd->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$tid = (int)$row['turno_id'];
|
|
$relacionados[$tid][] = [
|
|
'turno_id' => (int)$row['rel_id'], 'codigo' => $row['rel_codigo'],
|
|
'fecha' => $row['rel_fecha'], 'direccion' => 'seguimiento',
|
|
];
|
|
}
|
|
// Sentido "origen": este turno completó una muestra pendiente de OTRO turno anterior
|
|
$stmtRelBack = $pdo->prepare(
|
|
"SELECT DISTINCT tm.recibida_en_turno_id AS turno_id, t1.id AS rel_id, t1.codigo AS rel_codigo, s1.fecha AS rel_fecha
|
|
FROM turnero_muestras tm
|
|
JOIN turnero_solicitudes ts ON ts.id = tm.solicitud_id
|
|
JOIN turnero_turnos t1 ON t1.id = ts.turno_id
|
|
JOIN turnero_sesiones s1 ON s1.id = t1.sesion_id
|
|
WHERE tm.recibida_en_turno_id IN ($ph)"
|
|
);
|
|
$stmtRelBack->execute($turnoIds);
|
|
foreach ($stmtRelBack->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
|
$tid = (int)$row['turno_id'];
|
|
$relacionados[$tid][] = [
|
|
'turno_id' => (int)$row['rel_id'], 'codigo' => $row['rel_codigo'],
|
|
'fecha' => $row['rel_fecha'], 'direccion' => 'origen',
|
|
];
|
|
}
|
|
}
|
|
|
|
foreach ($turnos as &$turno) {
|
|
$tid = (int)$turno['id'];
|
|
$turno['examenes'] = $examenes[$tid] ?? [];
|
|
$turno['consentimientos'] = $consentimientos[$tid] ?? [];
|
|
$turno['comentarios'] = $comentarios[$tid] ?? [];
|
|
$turno['relacionados'] = $relacionados[$tid] ?? [];
|
|
}
|
|
|
|
// ── 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,
|
|
]);
|