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