El turno original no se modifica (queda finalizado igual que antes) — solo se agrega trazabilidad: turnero_muestras.recibida_en_turno_id registra en qué turno se completó una muestra que había quedado pendiente en otra visita. Historial, bandeja y lugar.php ahora muestran un chip/botón "Turno vinculado" en ambos sentidos (origen ⇄ seguimiento) con acceso directo. Además, al abrir un formulario "Datos Toma de Muestras" pendiente para una visita de seguimiento, aparece un botón para cargar las respuestas del último formulario firmado de ese mismo paciente (sin firma ni identidad del profesional anterior) — evita reescribir la historia clínica; la firma y fecha siguen siendo las de hoy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
282 lines
11 KiB
PHP
282 lines
11 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');
|
|
|
|
$fechaDesde = trim($_GET['fecha_desde'] ?? '');
|
|
$fechaHasta = trim($_GET['fecha_hasta'] ?? '');
|
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $fechaDesde)) $fechaDesde = $hoy;
|
|
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,
|
|
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 DESC"
|
|
);
|
|
$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;
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
]);
|