From 3a19f08bf8c86a25157e2b6eb6322622f4ee4ad4 Mon Sep 17 00:00:00 2001
From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com>
Date: Tue, 21 Apr 2026 21:13:35 -0500
Subject: [PATCH] up
---
modules/turnero/api/export_historial.php | 152 +++++++
modules/turnero/api/get_historial.php | 165 +++++++
modules/turnero/module.php | 1 +
modules/turnero/views/historial.php | 552 +++++++++++++++++++++++
run_migration_005.php | 46 ++
5 files changed, 916 insertions(+)
create mode 100644 modules/turnero/api/export_historial.php
create mode 100644 modules/turnero/api/get_historial.php
create mode 100644 modules/turnero/views/historial.php
create mode 100644 run_migration_005.php
diff --git a/modules/turnero/api/export_historial.php b/modules/turnero/api/export_historial.php
new file mode 100644
index 0000000..4031c84
--- /dev/null
+++ b/modules/turnero/api/export_historial.php
@@ -0,0 +1,152 @@
+ $fechaHasta) $fechaDesde = $fechaHasta;
+
+$estadoFiltro = trim($_GET['estado'] ?? '');
+$lugarId = (int)($_GET['lugar_id'] ?? 0);
+$prioridadId = (int)($_GET['prioridad_id'] ?? 0);
+$q = trim($_GET['q'] ?? '');
+
+// ── WHERE ─────────────────────────────────────────────────────
+$where = ['DATE(ses.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 ($q !== '') {
+ $where[] = '(t.codigo LIKE ? OR t.paciente_nombre LIKE ? OR pac.nombre_completo LIKE ?)';
+ $like = '%' . $q . '%';
+ $binds[] = $like;
+ $binds[] = $like;
+ $binds[] = $like;
+}
+
+$whereStr = 'WHERE ' . implode(' AND ', $where);
+
+// ── Consulta ──────────────────────────────────────────────────
+$sql = "
+ SELECT
+ ses.fecha AS fecha_sesion,
+ t.codigo,
+ t.numero,
+ p.codigo AS prioridad,
+ p.nombre AS prioridad_nombre,
+ COALESCE(pac.nombre_completo, t.paciente_nombre) AS paciente,
+ pac.numero_documento,
+ pac.tipo_documento,
+ t.paciente_cel AS celular,
+ l.nombre AS lugar,
+ t.estado,
+ t.creado_at,
+ t.llamado_recepcion_at,
+ t.inicio_recepcion_at,
+ t.fin_recepcion_at,
+ t.llamado_lugar_at,
+ t.inicio_lugar_at,
+ t.fin_lugar_at,
+ ROUND(TIMESTAMPDIFF(SECOND, t.creado_at,
+ COALESCE(t.inicio_recepcion_at, t.fin_recepcion_at)) / 60.0, 1) AS espera_recepcion_min,
+ ROUND(TIMESTAMPDIFF(SECOND, t.inicio_lugar_at,
+ COALESCE(t.fin_lugar_at, NOW())) / 60.0, 1) AS servicio_lugar_min,
+ sol.total_cobrado,
+ sol.metodo_pago
+ FROM turnero_turnos t
+ JOIN turnero_sesiones ses ON ses.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_solicitudes sol ON sol.turno_id = t.id
+ LEFT JOIN lab_pacientes pac ON pac.id = sol.paciente_id
+ $whereStr
+ ORDER BY t.creado_at ASC";
+
+$stmt = $pdo->prepare($sql);
+$stmt->execute($binds);
+$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+// ── Nombre de archivo ─────────────────────────────────────────
+$nombreArchivo = "historial_turnos_{$fechaDesde}_a_{$fechaHasta}.csv";
+
+// ── Output ────────────────────────────────────────────────────
+// Limpiar cualquier output previo antes de los headers
+ob_clean();
+
+header('Content-Type: text/csv; charset=utf-8');
+header('Content-Disposition: attachment; filename="' . $nombreArchivo . '"');
+header('Cache-Control: no-cache, no-store, must-revalidate');
+header('Pragma: no-cache');
+header('Expires: 0');
+
+$out = fopen('php://output', 'w');
+echo "\xEF\xBB\xBF"; // BOM UTF-8 para Excel
+
+// Encabezados
+$headers = [
+ 'Fecha', 'Código', 'Número', 'Prioridad', 'Prioridad nombre',
+ 'Paciente', 'Documento', 'Tipo doc.', 'Celular',
+ 'Lugar', 'Estado',
+ 'Creado', 'Llamado recepción', 'Inicio recepción', 'Fin recepción',
+ 'Llamado lugar', 'Inicio lugar', 'Fin lugar',
+ 'Espera recepción (min)', 'Servicio lugar (min)',
+ 'Total cobrado', 'Método pago',
+];
+fputcsv($out, $headers, ',', '"');
+
+$fechaCols = [
+ 'creado_at','llamado_recepcion_at','inicio_recepcion_at','fin_recepcion_at',
+ 'llamado_lugar_at','inicio_lugar_at','fin_lugar_at',
+];
+$fields = [
+ 'fecha_sesion', 'codigo', 'numero', 'prioridad', 'prioridad_nombre',
+ 'paciente', 'numero_documento', 'tipo_documento', 'celular',
+ 'lugar', 'estado',
+ 'creado_at', 'llamado_recepcion_at', 'inicio_recepcion_at', 'fin_recepcion_at',
+ 'llamado_lugar_at', 'inicio_lugar_at', 'fin_lugar_at',
+ 'espera_recepcion_min', 'servicio_lugar_min',
+ 'total_cobrado', 'metodo_pago',
+];
+
+foreach ($rows as $row) {
+ $line = [];
+ foreach ($fields as $f) {
+ $v = $row[$f] ?? '';
+ if ($v && in_array($f, $fechaCols, true)) {
+ $v = date('d/m/Y H:i:s', strtotime($v));
+ }
+ $line[] = $v;
+ }
+ fputcsv($out, $line, ',', '"');
+}
+
+fclose($out);
+exit;
diff --git a/modules/turnero/api/get_historial.php b/modules/turnero/api/get_historial.php
new file mode 100644
index 0000000..b33e391
--- /dev/null
+++ b/modules/turnero/api/get_historial.php
@@ -0,0 +1,165 @@
+ $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 ($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,
+ 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_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);
+
+// ── 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,
+]);
diff --git a/modules/turnero/module.php b/modules/turnero/module.php
index 1026300..a6b9994 100644
--- a/modules/turnero/module.php
+++ b/modules/turnero/module.php
@@ -7,6 +7,7 @@
// ── Links estáticos base ──────────────────────────────────────
$_trLinks = [
['name' => 'Dashboard', 'icon' => 'fas fa-tachometer-alt', 'route' => '/erp.php?m=turnero&v=dashboard'],
+ ['name' => 'Historial', 'icon' => 'fas fa-history', 'route' => '/erp.php?m=turnero&v=historial'],
['name' => 'Configuración', 'icon' => 'fas fa-sliders-h', 'route' => '/erp.php?m=turnero&v=configuracion'],
['name' => 'Kiosko', 'icon' => 'fas fa-desktop', 'route' => '/erp.php?m=turnero&v=kiosko'],
['name' => 'Pantalla TV Global', 'icon' => 'fas fa-th-large', 'route' => '/erp.php?m=turnero&v=display_global'],
diff --git a/modules/turnero/views/historial.php b/modules/turnero/views/historial.php
new file mode 100644
index 0000000..8dd70ab
--- /dev/null
+++ b/modules/turnero/views/historial.php
@@ -0,0 +1,552 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Fecha |
+ Código |
+ Prioridad |
+ Paciente |
+ Teléfono |
+ Lugar |
+ Estado |
+ Espera |
+ Servicio |
+ Entrada |
+ |
+
+
+
+
+ | Cargando… |
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/run_migration_005.php b/run_migration_005.php
new file mode 100644
index 0000000..5f88178
--- /dev/null
+++ b/run_migration_005.php
@@ -0,0 +1,46 @@
+getConnection();
+$sqlFile = __DIR__ . '/migrations/005_registro_exams.sql';
+$sql = file_get_contents($sqlFile);
+
+// Separar sentencias (ignorar comentarios)
+$statements = array_filter(array_map('trim', explode(';', $sql)));
+
+$results = [];
+foreach ($statements as $stmt) {
+ if (empty($stmt) || preg_match('/^\s*--/', $stmt)) continue;
+ try {
+ $pdo->exec($stmt);
+ $results[] = ['ok' => true, 'sql' => substr(preg_replace('/\s+/', ' ', $stmt), 0, 120)];
+ } catch (\PDOException $e) {
+ $results[] = ['ok' => false, 'sql' => substr(preg_replace('/\s+/', ' ', $stmt), 0, 120), 'error' => $e->getMessage()];
+ }
+}
+?>
+Migración 005
+
+
+Migración 005 — Registro de Exámenes
+
+| # | Estado | SQL | Error |
+
+ $r): ?>
+
+ | = $i+1 ?> |
+ = $r['ok'] ? '✓ OK' : '✗ Error' ?> |
+ = htmlspecialchars($r['sql']) ?>… |
+ = htmlspecialchars($r['error'] ?? '') ?> |
+
+
+
+
+⚠️ Elimina este archivo del servidor después de ejecutarlo.
+Ir a Registro Exámenes
+