Files
whatsapp/modules/turnero/api/get_historial.php
T
Lizandro GuarnizoandClaude Sonnet 4.6 538ee8ad25 Vincular turnos por muestra pendiente y precargar Datos Toma de Muestras
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>
2026-08-03 13:10:59 -05:00

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,
]);