Marcar ausente solo pedía confirmación: el turno quedaba registrado sin ninguna explicación, y hoy son 6 en un día. Ahora se pide el motivo y se guarda como comentario de recepción, no como columna nueva: así aparece en todas las pantallas que ya muestran comentarios, sin duplicar el dato ni tocar el esquema. Dashboard e historial mostraban las notas sin indicar de dónde venían, así que no se distinguía una observación de toma de muestras de una de recepción. Se agrega la insignia de origen con los mismos colores que ya usa la bandeja, y el título pasa a "Notas y observaciones" con su cantidad. El historial además traía solo las 3 últimas por turno y en orden inverso. Con recepción empezando a dejar notas eso habría escondido justamente el motivo de una ausencia sin que nada indicara que faltaban; ahora trae hasta 20, en orden cronológico. De las 114 notas existentes, todas son de toma de muestras: recepción nunca dejó ninguna porque no tenía dónde hacerlo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
284 lines
11 KiB
PHP
284 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 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,
|
|
]);
|