Primer subida completa

This commit is contained in:
nickpons666
2026-01-19 15:20:36 -06:00
commit 85894619d8
146 changed files with 3620 additions and 0 deletions

384
src/Asignacion.php Executable file
View File

@@ -0,0 +1,384 @@
<?php
require_once __DIR__ . '/Database.php';
class Asignacion {
private $db;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
}
public function getAsignacionActual() {
$currentWeekStart = date('Y-m-d', strtotime('monday this week'));
$stmt = $this->db->prepare("
SELECT u.*, a.semana_inicio
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
WHERE a.semana_inicio = ?
");
$stmt->execute([$currentWeekStart]);
return $stmt->fetch();
}
public function getAsignacionPorSemana($semanaInicio) {
$stmt = $this->db->prepare("
SELECT u.*, a.semana_inicio, a.semana_fin, a.id as asignacion_id
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
WHERE a.semana_inicio = ?
ORDER BY a.orden_turno ASC
LIMIT 1
");
$stmt->execute([$semanaInicio]);
return $stmt->fetch();
}
public function getTodasAsignaciones() {
$stmt = $this->db->query("
SELECT u.nombre, a.*
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
ORDER BY a.semana_inicio DESC
LIMIT 20
");
return $stmt->fetchAll();
}
public function asignar($userId, $semanaInicio) {
$semanaFin = date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))); // Domingo a viernes
$ordenTurno = $this->getOrdenTurno($userId);
$stmt = $this->db->prepare("
INSERT INTO asignaciones_turnos (user_id, semana_inicio, semana_fin, orden_turno)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), semana_fin = VALUES(semana_fin), orden_turno = VALUES(orden_turno)
");
return $stmt->execute([$userId, $semanaInicio, $semanaFin, $ordenTurno]);
}
private function getOrdenTurno($userId) {
$stmt = $this->db->prepare("SELECT orden FROM rotacion_orden WHERE user_id = ? AND activo = 1");
$stmt->execute([$userId]);
$result = $stmt->fetch();
return $result ? $result['orden'] : 999;
}
public function getProximaPersona($userIdActual = null) {
$sql = "SELECT * FROM users WHERE rol = 'ayudante' AND activo = 1 ORDER BY id";
if ($userIdActual) {
$stmt = $this->db->query($sql);
$ayudantes = $stmt->fetchAll();
$encontrado = false;
foreach ($ayudantes as $a) {
if ($encontrado) return $a;
if ($a['id'] == $userIdActual) $encontrado = true;
}
return $ayudantes[0] ?? null;
}
$stmt = $this->db->query($sql);
return $stmt->fetch();
}
public function asignarMasivo($userIds, $semanaInicio, $rotacionAutomatica = false) {
$resultados = [];
$errores = [];
foreach ($userIds as $index => $userId) {
try {
$semanaFin = date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))); // Domingo a viernes
$ordenTurno = $this->getOrdenTurno($userId);
$stmt = $this->db->prepare("
INSERT INTO asignaciones_turnos (user_id, semana_inicio, semana_fin, orden_turno)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), semana_fin = VALUES(semana_fin), orden_turno = VALUES(orden_turno)
");
$success = $stmt->execute([$userId, $semanaInicio, $semanaFin, $ordenTurno]);
if ($success) {
$resultados[] = [
'user_id' => $userId,
'semana' => $semanaInicio,
'rotar_siguiente' => $rotacionAutomatica && $index === count($userIds) - 1
];
// Si es el último usuario y se activó rotación automática
if ($rotacionAutomatica && $index === count($userIds) - 1) {
$this->asignarSiguienteSemana($userId, $semanaInicio);
}
} else {
$errores[] = "Error al asignar usuario ID: $userId";
}
} catch (Exception $e) {
$errores[] = "Error usuario ID $userId: " . $e->getMessage();
}
}
return [
'success' => count($resultados),
'errors' => $errores,
'resultados' => $resultados
];
}
private function asignarSiguienteSemana($ultimoUserId, $semanaActual) {
$siguienteSemana = date('Y-m-d', strtotime('+1 week', strtotime($semanaActual)));
$siguientePersona = $this->getProximaPersona($ultimoUserId);
if ($siguientePersona) {
$semanaFin = date('Y-m-d', strtotime('+5 days', strtotime($siguienteSemana))); // Domingo a viernes
$ordenTurno = $this->getOrdenTurno($siguientePersona['id']);
$stmt = $this->db->prepare("
INSERT INTO asignaciones_turnos (user_id, semana_inicio, semana_fin, orden_turno)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), semana_fin = VALUES(semana_fin), orden_turno = VALUES(orden_turno)
");
return $stmt->execute([$siguientePersona['id'], $siguienteSemana, $semanaFin, $ordenTurno]);
}
return false;
}
public function asignarSemanasFuturasAutomaticas($semanasFuturas = 12) {
$resultados = [];
$errores = [];
// Obtener todos los ayudantes en orden de rotación
$ayudantesOrdenados = $this->getAyudantesPorOrden();
if (empty($ayudantesOrdenados)) {
return ['success' => 0, 'errors' => ['No hay ayudantes configurados'], 'resultados' => []];
}
// Encontrar la última semana asignada
$ultimaAsignacion = $this->getUltimaAsignacion();
$semanaActual = $ultimaAsignacion
? date('Y-m-d', strtotime('+1 week', strtotime($ultimaAsignacion['semana_inicio'])))
: date('Y-m-d', strtotime('last sunday', strtotime('today'))); // Empezar desde domingo
// Determinar el siguiente ayudante en el ciclo
$indiceActual = 0;
if ($ultimaAsignacion) {
$indiceActual = $this->findIndiceSiguiente($ultimaAsignacion['user_id'], $ayudantesOrdenados);
}
// Asignar semanas futuras
for ($i = 0; $i < $semanasFuturas; $i++) {
$semanaInicio = date('Y-m-d', strtotime("+$i weeks", strtotime($semanaActual)));
$semanaFin = date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))); // Domingo a viernes
// Seleccionar ayudante usando ciclo cíclico
$ayudanteIndex = ($indiceActual + $i) % count($ayudantesOrdenados);
$ayudante = $ayudantesOrdenados[$ayudanteIndex];
try {
$ordenTurno = $this->getOrdenTurno($ayudante['id']);
$stmt = $this->db->prepare("
INSERT INTO asignaciones_turnos (user_id, semana_inicio, semana_fin, orden_turno)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), semana_fin = VALUES(semana_fin), orden_turno = VALUES(orden_turno)
");
$success = $stmt->execute([$ayudante['id'], $semanaInicio, $semanaFin, $ordenTurno]);
if ($success) {
$resultados[] = [
'semana' => $semanaInicio,
'usuario' => $ayudante['nombre'],
'orden' => $ordenTurno
];
} else {
$errores[] = "Error al asignar semana $semanaInicio a {$ayudante['nombre']}";
}
} catch (Exception $e) {
$errores[] = "Error semana $semanaInicio: " . $e->getMessage();
}
}
return [
'success' => count($resultados),
'errors' => $errores,
'resultados' => $resultados
];
}
public function getAyudantesPorOrden() {
$stmt = $this->db->query("
SELECT u.*, ro.orden
FROM users u
LEFT JOIN rotacion_orden ro ON u.id = ro.user_id AND ro.activo = 1
WHERE u.rol = 'ayudante' AND u.activo = 1
ORDER BY COALESCE(ro.orden, 999), u.nombre
");
return $stmt->fetchAll();
}
private function getUltimaAsignacion() {
$stmt = $this->db->query("
SELECT a.*, u.nombre
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
WHERE a.semana_inicio <= CURDATE()
ORDER BY a.semana_inicio DESC
LIMIT 1
");
return $stmt->fetch();
}
private function findIndiceSiguiente($ultimoUserId, $ayudantesOrdenados) {
foreach ($ayudantesOrdenados as $index => $ayudante) {
if ($ayudante['id'] == $ultimoUserId) {
return ($index + 1) % count($ayudantesOrdenados);
}
}
return 0;
}
public function recalcularAsignaciones($semanasFuturas = 20) {
$resultados = [];
$errores = [];
// Obtener todos los ayudantes en orden de rotación
$ayudantesOrdenados = $this->getAyudantesPorOrden();
if (empty($ayudantesOrdenados)) {
return ['success' => 0, 'errors' => ['No hay ayudantes configurados'], 'resultados' => []];
}
// Encontrar la última asignación existente (histórica)
$stmt = $this->db->query("
SELECT a.*, u.nombre
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
ORDER BY a.semana_inicio DESC
LIMIT 1
");
$ultimaAsignacion = $stmt->fetch();
// Determinar desde dónde empezar a recalcular
if ($ultimaAsignacion) {
$semanaInicio = date('Y-m-d', strtotime('+1 week', strtotime($ultimaAsignacion['semana_inicio'])));
// Encontrar posición del último usuario en el nuevo orden
$indiceInicial = 0;
foreach ($ayudantesOrdenados as $index => $ayudante) {
if ($ayudante['id'] == $ultimaAsignacion['user_id']) {
$indiceInicial = ($index + 1) % count($ayudantesOrdenados);
break;
}
}
} else {
// No hay asignaciones, empezar desde el próximo domingo
$hoy = new DateTime();
$diaSemana = (int)$hoy->format('w');
$domingo = clone $hoy;
$domingo->modify('-' . $diaSemana . ' days');
$semanaInicio = $domingo->format('Y-m-d');
$indiceInicial = 0;
}
// Eliminar asignaciones futuras
$stmt = $this->db->prepare("DELETE FROM asignaciones_turnos WHERE semana_inicio >= ?");
$stmt->execute([$semanaInicio]);
// Generar nuevas asignaciones
for ($i = 0; $i < $semanasFuturas; $i++) {
$siguienteDomingo = new DateTime($semanaInicio);
$siguienteDomingo->modify("+{$i} weeks");
$fechaInicio = $siguienteDomingo->format('Y-m-d');
$fechaFin = $siguienteDomingo->modify('+5 days')->format('Y-m-d');
$ayudanteIndex = ($indiceInicial + $i) % count($ayudantesOrdenados);
$ayudante = $ayudantesOrdenados[$ayudanteIndex];
try {
$stmt = $this->db->prepare("
INSERT INTO asignaciones_turnos (user_id, semana_inicio, semana_fin, orden_turno)
VALUES (?, ?, ?, ?)
");
$success = $stmt->execute([
$ayudante['id'],
$fechaInicio,
$fechaFin,
$ayudante['orden']
]);
if ($success) {
$resultados[] = [
'semana' => $fechaInicio,
'usuario' => $ayudante['nombre'],
'orden' => $ayudante['orden']
];
} else {
$errores[] = "Error al asignar semana $fechaInicio a {$ayudante['nombre']}";
}
} catch (Exception $e) {
$errores[] = "Error semana $fechaInicio: " . $e->getMessage();
}
}
return [
'success' => count($resultados),
'errors' => $errores,
'resultados' => $resultados
];
}
public function inicializarOrdenRotacion() {
$ayudantes = $this->getAyudantesPorOrden();
$errores = [];
$actualizados = 0;
foreach ($ayudantes as $index => $ayudante) {
if ($ayudante['orden'] === null) {
try {
$stmt = $this->db->prepare("
INSERT INTO rotacion_orden (user_id, orden, activo)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE orden = VALUES(orden), activo = 1
");
$stmt->execute([$ayudante['id'], $index + 1]);
$actualizados++;
} catch (Exception $e) {
$errores[] = "Error con {$ayudante['nombre']}: " . $e->getMessage();
}
}
}
return [
'actualizados' => $actualizados,
'errores' => $errores
];
}
public function getAsignacionesPorRango($semanaInicio, $semanaFin) {
$stmt = $this->db->prepare("
SELECT u.*, a.semana_inicio, a.semana_fin, a.orden_turno
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
WHERE a.semana_inicio >= ? AND a.semana_inicio <= ?
ORDER BY a.semana_inicio, u.nombre
");
$stmt->execute([$semanaInicio, $semanaFin]);
return $stmt->fetchAll();
}
public function getTodasAsignacionesPorSemana($semanaInicio) {
$stmt = $this->db->prepare("
SELECT u.*, a.semana_inicio, a.orden_turno
FROM asignaciones_turnos a
JOIN users u ON a.user_id = u.id
WHERE a.semana_inicio = ?
ORDER BY a.orden_turno
");
$stmt->execute([$semanaInicio]);
return $stmt->fetchAll();
}
}

78
src/Auth.php Executable file
View File

@@ -0,0 +1,78 @@
<?php
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/User.php';
$sessionPath = dirname(__DIR__) . '/sessions';
if (!is_dir($sessionPath)) {
mkdir($sessionPath, 0733, true);
}
session_save_path($sessionPath);
session_name('contenedor_session');
class Auth {
private $db;
private $userModel;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
$this->userModel = new User();
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
public function login($login, $password) {
$user = $this->userModel->findByLogin($login);
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['nombre'];
$_SESSION['user_rol'] = $user['rol'];
$_SESSION['logged_in'] = true;
session_write_close();
return true;
}
return false;
}
public function logout() {
session_destroy();
$_SESSION = [];
return true;
}
public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
public function isAdmin() {
return isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'admin';
}
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
}
return [
'id' => $_SESSION['user_id'],
'nombre' => $_SESSION['user_name'],
'rol' => $_SESSION['user_rol']
];
}
public function requireAuth() {
if (!$this->isLoggedIn()) {
header('Location: /login.php');
exit;
}
}
public function requireAdmin() {
$this->requireAuth();
if (!$this->isAdmin()) {
header('Location: /ayudante.php');
exit;
}
}
}

50
src/Database.php Executable file
View File

@@ -0,0 +1,50 @@
<?php
require_once __DIR__ . '/../config/config.php';
class Database {
private static $instance = null;
private $connection;
private function __construct() {
$config = require __DIR__ . '/../config/config.php';
$hostPort = $config['db']['host'];
$dbname = $config['db']['database'];
$username = $config['db']['username'];
$password = $config['db']['password'];
if (strpos($hostPort, ':') !== false) {
list($host, $port) = explode(':', $hostPort);
} else {
$host = $hostPort;
$port = $config['db']['port'] ?? '3306';
}
try {
$this->connection = new PDO(
"mysql:host=$host;port=$port;dbname=$dbname;charset=utf8mb4",
$username,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Error de conexión a la base de datos: " . $e->getMessage());
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->connection;
}
}

46
src/DiasHorarios.php Executable file
View File

@@ -0,0 +1,46 @@
<?php
require_once __DIR__ . '/Database.php';
class DiasHorarios {
private $db;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
}
public function getAll() {
$stmt = $this->db->query("SELECT * FROM dias_horarios ORDER BY FIELD(dia_semana, 'domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado')");
return $stmt->fetchAll();
}
public function getActivos() {
$stmt = $this->db->query("SELECT * FROM dias_horarios WHERE activo = 1 ORDER BY FIELD(dia_semana, 'domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado')");
return $stmt->fetchAll();
}
public function getByDia($dia) {
$stmt = $this->db->prepare("SELECT * FROM dias_horarios WHERE dia_semana = ?");
$stmt->execute([$dia]);
return $stmt->fetch();
}
public function update($dia, $data) {
$stmt = $this->db->prepare("
UPDATE dias_horarios
SET hora_apertura = ?, hora_cierre = ?, activo = ?
WHERE dia_semana = ?
");
return $stmt->execute([
$data['hora_apertura'],
$data['hora_cierre'],
$data['activo'] ?? 1,
$dia
]);
}
public function getDiasConActividad() {
$stmt = $this->db->query("SELECT dia_semana FROM dias_horarios WHERE activo = 1");
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
}

72
src/RotacionTurnos.php Executable file
View File

@@ -0,0 +1,72 @@
<?php
require_once __DIR__ . '/../src/Database.php';
require_once __DIR__ . '/../src/User.php';
require_once __DIR__ . '/../src/Asignacion.php';
class RotacionTurnos {
private $db;
private $userModel;
private $asignacionModel;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
$this->userModel = new User();
$this->asignacionModel = new Asignacion();
}
public function rotarSemanaActual() {
$currentWeekStart = date('Y-m-d', strtotime('monday this week'));
return $this->rotarSemana($currentWeekStart);
}
public function rotarSemana($semanaInicio) {
$asignacionActual = $this->asignacionModel->getAsignacionPorSemana($semanaInicio);
$userIdActual = $asignacionActual ? $asignacionActual['user_id'] : null;
$proximaPersona = $this->asignacionModel->getProximaPersona($userIdActual);
if (!$proximaPersona) {
return ['success' => false, 'message' => 'No hay personas disponibles para asignar'];
}
$this->asignacionModel->asignar($proximaPersona['id'], $semanaInicio);
return [
'success' => true,
'message' => "Turno asignado a: {$proximaPersona['nombre']}",
'persona' => $proximaPersona
];
}
public function verificarYRotar() {
$currentWeekStart = date('Y-m-d', strtotime('monday this week'));
$asignacionActual = $this->asignacionModel->getAsignacionPorSemana($currentWeekStart);
if (!$asignacionActual) {
return $this->rotarSemana($currentWeekStart);
}
return ['success' => true, 'message' => 'Ya existe asignación para esta semana', 'already_assigned' => true];
}
}
if (php_sapi_name() === 'cli' || basename($_SERVER['SCRIPT_FILENAME']) === 'rotar.php') {
$rotacion = new RotacionTurnos();
$resultado = $rotacion->verificarYRotar();
echo "Resultado: " . $resultado['message'] . "\n";
if (isset($resultado['already_assigned'])) {
echo "No se realizó rotación (ya estaba asignada)\n";
exit(0);
}
if ($resultado['success']) {
echo "Rotación completada exitosamente\n";
exit(0);
} else {
echo "Error: " . $resultado['message'] . "\n";
exit(1);
}
}

128
src/User.php Executable file
View File

@@ -0,0 +1,128 @@
<?php
require_once __DIR__ . '/Database.php';
class User {
private $db;
public function __construct() {
$this->db = Database::getInstance()->getConnection();
}
public function getAll($includeInactive = false) {
$sql = "SELECT * FROM users";
if (!$includeInactive) {
$sql .= " WHERE activo = 1";
}
$sql .= " ORDER BY nombre";
$stmt = $this->db->query($sql);
return $stmt->fetchAll();
}
public function getById($id) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
public function getByEmail($email) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
return $stmt->fetch();
}
public function findByLogin($login) {
$login = trim($login);
$stmt = $this->db->prepare("SELECT * FROM users WHERE (email = ? OR username = ?) AND activo = 1");
$stmt->execute([$login, $login]);
return $stmt->fetch();
}
public function getByUsername($username) {
$stmt = $this->db->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
return $stmt->fetch();
}
public function usernameExists($username, $excludeId = null) {
$sql = "SELECT COUNT(*) as total FROM users WHERE username = ?";
$params = [$username];
if ($excludeId) {
$sql .= " AND id != ?";
$params[] = $excludeId;
}
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
return $stmt->fetch()['total'] > 0;
}
public function create($data) {
$username = !empty($data['username']) ? $data['username'] : strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $data['nombre']));
$stmt = $this->db->prepare("
INSERT INTO users (username, nombre, email, password, rol)
VALUES (?, ?, ?, ?, ?)
");
$password = password_hash($data['password'], PASSWORD_DEFAULT);
$stmt->execute([
$username,
$data['nombre'],
$data['email'],
$password,
$data['rol'] ?? 'ayudante'
]);
$userId = $this->db->lastInsertId();
// Si es un ayudante, agregar automáticamente a rotacion_orden
if (isset($data['rol']) && $data['rol'] === 'ayudante') {
$this->agregarARotacion($userId);
}
return $userId;
}
private function agregarARotacion($userId) {
// Obtener el siguiente orden disponible
$stmt = $this->db->query("SELECT MAX(orden) as max_orden FROM rotacion_orden WHERE activo = 1");
$result = $stmt->fetch();
$nuevoOrden = ($result['max_orden'] ?? 0) + 1;
// Insertar en rotacion_orden
$stmt = $this->db->prepare("
INSERT INTO rotacion_orden (user_id, orden, activo)
VALUES (?, ?, 1)
");
$stmt->execute([$userId, $nuevoOrden]);
}
public function update($id, $data) {
$sql = "UPDATE users SET username = ?, nombre = ?, email = ?, rol = ?";
$params = [$data['username'] ?? '', $data['nombre'], $data['email'], $data['rol']];
if (!empty($data['password'])) {
$sql .= ", password = ?";
$params[] = password_hash($data['password'], PASSWORD_DEFAULT);
}
$sql .= " WHERE id = ?";
$params[] = $id;
$stmt = $this->db->prepare($sql);
return $stmt->execute($params);
}
public function deactivate($id) {
$stmt = $this->db->prepare("UPDATE users SET activo = 0 WHERE id = ?");
return $stmt->execute([$id]);
}
public function activate($id) {
$stmt = $this->db->prepare("UPDATE users SET activo = 1 WHERE id = ?");
return $stmt->execute([$id]);
}
public function getAyudantesActivos() {
$stmt = $this->db->query("SELECT * FROM users WHERE rol = 'ayudante' AND activo = 1 ORDER BY nombre");
return $stmt->fetchAll();
}
}

4
src/layout/footer.php Executable file
View File

@@ -0,0 +1,4 @@
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

51
src/layout/header.php Executable file
View File

@@ -0,0 +1,51 @@
<?php
$currentPage = $currentPage ?? '';
$auth = require_once __DIR__ . '/Auth.php';
$user = $auth->getCurrentUser() ?? [];
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $pageTitle ?? 'Panel' ?> - Contenedor Ibiza</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/admin/index.php">Contenedor Ibiza</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'dashboard' ? 'active' : '' ?>" href="/admin/index.php">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'usuarios' ? 'active' : '' ?>" href="/admin/usuarios.php">Usuarios</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'horarios' ? 'active' : '' ?>" href="/admin/horarios.php">Horarios</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'asignaciones' ? 'active' : '' ?>" href="/admin/asignaciones.php">Asignaciones</a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
<?= htmlspecialchars($user['nombre'] ?? 'Usuario') ?>
</a>
<ul class="dropdown-menu">
<li><span class="dropdown-item-text"><strong>Rol:</strong> <?= htmlspecialchars(ucfirst($user['rol'] ?? '')) ?></span></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="/logout.php">Cerrar Sesión</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">