Mejoras de seguridad y nueva tabla de turnos para ayudantes

- Agregado sistema de protección CSRF con tokens
- Creada clase Session para gestión centralizada de sesiones
- Mejorado manejo de errores en Database (sin die())
- Refactorizado Auth para usar nueva clase Session
- Agregada validación CSRF a formularios de login y admin
- Agregada validación de roles en modelo User
- Mejorada vista de ayudante con tabla de horarios por semana
- Agregada tabla de Turnos de Ayudantes con fechas en columnas
This commit is contained in:
nickpons666
2026-01-20 15:24:07 -06:00
parent dc8e83db6c
commit 05631e4a63
10 changed files with 467 additions and 254 deletions

View File

@@ -7,6 +7,7 @@ require_once BASE_PATH . '/src/Auth.php';
require_once BASE_PATH . '/src/User.php'; require_once BASE_PATH . '/src/User.php';
require_once BASE_PATH . '/src/DiasHorarios.php'; require_once BASE_PATH . '/src/DiasHorarios.php';
require_once BASE_PATH . '/src/Asignacion.php'; require_once BASE_PATH . '/src/Asignacion.php';
require_once BASE_PATH . '/src/CSRF.php';
$auth = new Auth(); $auth = new Auth();
$auth->requireAdmin(); $auth->requireAdmin();
@@ -19,6 +20,10 @@ $message = '';
$messageType = ''; $messageType = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!CSRF::isValidRequest()) {
$message = 'Error de validación del formulario';
$messageType = 'danger';
} else {
$action = $_POST['action'] ?? ''; $action = $_POST['action'] ?? '';
if ($action === 'asignar') { if ($action === 'asignar') {
@@ -68,6 +73,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
} }
} }
}
$ayudantes = $userModel->getAyudantesActivos(); $ayudantes = $userModel->getAyudantesActivos();
$horarios = $horariosModel->getActivos(); $horarios = $horariosModel->getActivos();
@@ -167,6 +173,7 @@ $pageTitle = 'Asignación de Turnos';
</div> </div>
<form method="POST" class="d-flex gap-2"> <form method="POST" class="d-flex gap-2">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="rotar"> <input type="hidden" name="action" value="rotar">
<input type="hidden" name="semana" value="<?= $currentWeekStart ?>"> <input type="hidden" name="semana" value="<?= $currentWeekStart ?>">
<button type="submit" class="btn btn-outline-primary"> <button type="submit" class="btn btn-outline-primary">
@@ -178,6 +185,7 @@ $pageTitle = 'Asignación de Turnos';
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="asignar"> <input type="hidden" name="action" value="asignar">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="semana" value="<?= $currentWeekStart ?>"> <input type="hidden" name="semana" value="<?= $currentWeekStart ?>">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Asignar a:</label> <label class="form-label">Asignar a:</label>
@@ -260,6 +268,7 @@ $pageTitle = 'Asignación de Turnos';
</div> </div>
<form method="POST" class="d-flex gap-2"> <form method="POST" class="d-flex gap-2">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="asignar"> <input type="hidden" name="action" value="asignar">
<input type="hidden" name="semana" value="<?= $semanaVer ?>"> <input type="hidden" name="semana" value="<?= $semanaVer ?>">
<select class="form-select" name="user_id" style="max-width: 250px;"> <select class="form-select" name="user_id" style="max-width: 250px;">
@@ -302,6 +311,7 @@ No hay asignación para la semana <?= $posicionSinAsignar ?> de 4 (<?= date('d/m
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST" id="asignacionMasivaForm"> <form method="POST" id="asignacionMasivaForm">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="asignar_masivo"> <input type="hidden" name="action" value="asignar_masivo">
<div class="row mb-3"> <div class="row mb-3">
@@ -401,6 +411,7 @@ No hay asignación para la semana <?= $posicionSinAsignar ?> de 4 (<?= date('d/m
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<form method="POST" class="h-100 d-flex flex-column justify-content-center"> <form method="POST" class="h-100 d-flex flex-column justify-content-center">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="rotacion_automatica"> <input type="hidden" name="action" value="rotacion_automatica">
<button type="submit" class="btn btn-warning w-100"> <button type="submit" class="btn btn-warning w-100">
<i class="fas fa-sync"></i> Generar Rotación Automática <i class="fas fa-sync"></i> Generar Rotación Automática
@@ -429,6 +440,7 @@ No hay asignación para la semana <?= $posicionSinAsignar ?> de 4 (<?= date('d/m
</div> </div>
<div class="card-body"> <div class="card-body">
<form method="POST" id="reordenarForm"> <form method="POST" id="reordenarForm">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="reordenar"> <input type="hidden" name="action" value="reordenar">
<p class="text-muted"> <p class="text-muted">

View File

@@ -5,6 +5,7 @@ if (!defined('BASE_PATH')) {
require_once BASE_PATH . '/config/config.php'; require_once BASE_PATH . '/config/config.php';
require_once BASE_PATH . '/src/Auth.php'; require_once BASE_PATH . '/src/Auth.php';
require_once BASE_PATH . '/src/DiasHorarios.php'; require_once BASE_PATH . '/src/DiasHorarios.php';
require_once BASE_PATH . '/src/CSRF.php';
$auth = new Auth(); $auth = new Auth();
$auth->requireAdmin(); $auth->requireAdmin();
@@ -24,6 +25,10 @@ $diasNombres = [
]; ];
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!CSRF::isValidRequest()) {
$message = 'Error de validación del formulario';
$messageType = 'danger';
} else {
$dia = $_POST['dia'] ?? ''; $dia = $_POST['dia'] ?? '';
$hora_apertura = $_POST["hora_apertura_$dia"] ?? ''; $hora_apertura = $_POST["hora_apertura_$dia"] ?? '';
$hora_cierre = $_POST["hora_cierre_$dia"] ?? ''; $hora_cierre = $_POST["hora_cierre_$dia"] ?? '';
@@ -38,6 +43,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$messageType = 'success'; $messageType = 'success';
} }
} }
}
$horarios = $horariosModel->getAll(); $horarios = $horariosModel->getAll();
$currentPage = 'horarios'; $currentPage = 'horarios';
@@ -64,6 +70,7 @@ $pageTitle = 'Configuración de Horarios';
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-body"> <div class="card-body">
<form method="POST" id="horariosForm"> <form method="POST" id="horariosForm">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="dia" id="selectedDia" value=""> <input type="hidden" name="dia" id="selectedDia" value="">
<div class="table-responsive"> <div class="table-responsive">

View File

@@ -5,6 +5,8 @@ if (!defined('BASE_PATH')) {
require_once BASE_PATH . '/config/config.php'; require_once BASE_PATH . '/config/config.php';
require_once BASE_PATH . '/src/Auth.php'; require_once BASE_PATH . '/src/Auth.php';
require_once BASE_PATH . '/src/User.php'; require_once BASE_PATH . '/src/User.php';
require_once BASE_PATH . '/src/CSRF.php';
require_once BASE_PATH . '/src/Session.php';
$auth = new Auth(); $auth = new Auth();
$auth->requireAdmin(); $auth->requireAdmin();
@@ -14,6 +16,10 @@ $message = '';
$messageType = ''; $messageType = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!CSRF::isValidRequest()) {
$message = 'Error de validación del formulario';
$messageType = 'danger';
} else {
$action = $_POST['action'] ?? ''; $action = $_POST['action'] ?? '';
if ($action === 'create') { if ($action === 'create') {
@@ -70,6 +76,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
} }
} }
}
$users = $userModel->getAll(true); $users = $userModel->getAll(true);
$currentPage = 'usuarios'; $currentPage = 'usuarios';
@@ -133,8 +140,9 @@ $pageTitle = 'Gestión de Usuarios';
onclick="editUser(<?= $u['id'] ?>, '<?= htmlspecialchars($u['nombre']) ?>', '<?= htmlspecialchars($u['email']) ?>', '<?= htmlspecialchars($u['username'] ?? '') ?>', '<?= $u['rol'] ?>')"> onclick="editUser(<?= $u['id'] ?>, '<?= htmlspecialchars($u['nombre']) ?>', '<?= htmlspecialchars($u['email']) ?>', '<?= htmlspecialchars($u['username'] ?? '') ?>', '<?= $u['rol'] ?>')">
Editar Editar
</button> </button>
<?php if ($u['id'] != $_SESSION['user_id']): ?> <?php if ($u['id'] != Session::get('user_id')): ?>
<form method="POST" class="d-inline"> <form method="POST" class="d-inline">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="toggle"> <input type="hidden" name="action" value="toggle">
<input type="hidden" name="id" value="<?= $u['id'] ?>"> <input type="hidden" name="id" value="<?= $u['id'] ?>">
<button type="submit" class="btn btn-sm btn-<?= $u['activo'] ? 'outline-warning' : 'outline-success' ?>"> <button type="submit" class="btn btn-sm btn-<?= $u['activo'] ? 'outline-warning' : 'outline-success' ?>">
@@ -160,6 +168,7 @@ $pageTitle = 'Gestión de Usuarios';
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div> </div>
<form method="POST" id="userForm"> <form method="POST" id="userForm">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="create" id="formAction"> <input type="hidden" name="action" value="create" id="formAction">
<input type="hidden" name="id" value="" id="userId"> <input type="hidden" name="id" value="" id="userId">
<div class="modal-body"> <div class="modal-body">

View File

@@ -3,6 +3,7 @@ require_once __DIR__ . '/../src/Auth.php';
require_once __DIR__ . '/../src/User.php'; require_once __DIR__ . '/../src/User.php';
require_once __DIR__ . '/../src/DiasHorarios.php'; require_once __DIR__ . '/../src/DiasHorarios.php';
require_once __DIR__ . '/../src/Asignacion.php'; require_once __DIR__ . '/../src/Asignacion.php';
require_once __DIR__ . '/../src/Database.php';
$auth = new Auth(); $auth = new Auth();
$auth->requireAuth(); $auth->requireAuth();
@@ -15,18 +16,17 @@ if ($auth->isAdmin()) {
$user = $auth->getCurrentUser(); $user = $auth->getCurrentUser();
$horariosModel = new DiasHorarios(); $horariosModel = new DiasHorarios();
$asignacionModel = new Asignacion(); $asignacionModel = new Asignacion();
$db = Database::getInstance()->getConnection();
$horarios = $horariosModel->getActivos(); $horarios = $horariosModel->getActivos();
$asignacionActual = $asignacionModel->getAsignacionActual(); $asignacionActual = $asignacionModel->getAsignacionActual();
// Obtener todas las asignaciones de las próximas semanas
$semanasFuturas = []; $semanasFuturas = [];
// Encontrar el domingo de esta semana
$hoy = new DateTime(); $hoy = new DateTime();
$diaSemana = (int)$hoy->format('w'); // 0 = domingo, 6 = sábado $diaSemana = (int)$hoy->format('w');
$domingoEstaSemana = clone $hoy; $domingoEstaSemana = clone $hoy;
$domingoEstaSemana->modify('-' . $diaSemana . ' days'); // Restar días para llegar al domingo $domingoEstaSemana->modify('-' . $diaSemana . ' days');
for ($i = 0; $i <= 4; $i++) { for ($i = 0; $i <= 4; $i++) {
$semanaDomingo = clone $domingoEstaSemana; $semanaDomingo = clone $domingoEstaSemana;
@@ -37,7 +37,7 @@ for ($i = 0; $i <= 4; $i++) {
$semanasFuturas[] = [ $semanasFuturas[] = [
'inicio' => $semanaInicio, 'inicio' => $semanaInicio,
'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))), // +5 días = domingo a viernes 'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))),
'asignaciones' => $asignacionesSemana, 'asignaciones' => $asignacionesSemana,
'asignacion' => !empty($asignacionesSemana) ? $asignacionesSemana[0] : null 'asignacion' => !empty($asignacionesSemana) ? $asignacionesSemana[0] : null
]; ];
@@ -45,7 +45,6 @@ for ($i = 0; $i <= 4; $i++) {
$miTurno = $asignacionActual && $asignacionActual['id'] == $user['id']; $miTurno = $asignacionActual && $asignacionActual['id'] == $user['id'];
// También verificar si el usuario tiene turno en las próximas semanas
$misAsignacionesFuturas = []; $misAsignacionesFuturas = [];
foreach ($semanasFuturas as $semana) { foreach ($semanasFuturas as $semana) {
foreach ($semana['asignaciones'] as $asignacion) { foreach ($semana['asignaciones'] as $asignacion) {
@@ -57,6 +56,12 @@ foreach ($semanasFuturas as $semana) {
} }
} }
} }
$userModel = new User();
$ayudantes = $userModel->getAyudantesActivos();
$domingo = new DateTime();
$domingo->modify('-' . (int)$domingo->format('w') . ' days');
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
@@ -81,13 +86,10 @@ foreach ($semanasFuturas as $semana) {
<h2 class="mb-4">Mis Turnos</h2> <h2 class="mb-4">Mis Turnos</h2>
<?php <?php
// Verificar si tiene turno esta semana (hoy está entre domingo y viernes de la semana actual)
$hoy = new DateTime(); $hoy = new DateTime();
$diaSemana = (int)$hoy->format('w'); // 0 = domingo, 6 = sábado $diaSemana = (int)$hoy->format('w');
$domingoActual = clone $hoy; $domingoActual = clone $hoy;
$domingoActual->modify('-' . $diaSemana . ' days'); // Restar días para llegar al domingo $domingoActual->modify('-' . $diaSemana . ' days');
$viernesActual = clone $domingoActual;
$viernesActual->modify('+5 days');
$asignacionEstaSemana = $asignacionModel->getAsignacionPorSemana($domingoActual->format('Y-m-d')); $asignacionEstaSemana = $asignacionModel->getAsignacionPorSemana($domingoActual->format('Y-m-d'));
$tengoTurnoEstaSemana = $asignacionEstaSemana && $asignacionEstaSemana['id'] == $user['id']; $tengoTurnoEstaSemana = $asignacionEstaSemana && $asignacionEstaSemana['id'] == $user['id'];
@@ -95,7 +97,7 @@ foreach ($semanasFuturas as $semana) {
if ($tengoTurnoEstaSemana): if ($tengoTurnoEstaSemana):
?> ?>
<div class="alert alert-success mb-4"> <div class="alert alert-success mb-4">
<strong>¡Tienes turno esta semana!</strong><br> <strong>Tienes turno esta semana!</strong><br>
Del <?= date('d/m/y', strtotime($asignacionEstaSemana['semana_inicio'])) ?> Del <?= date('d/m/y', strtotime($asignacionEstaSemana['semana_inicio'])) ?>
al <?= date('d/m/y', strtotime($asignacionEstaSemana['semana_fin'])) ?> al <?= date('d/m/y', strtotime($asignacionEstaSemana['semana_fin'])) ?>
</div> </div>
@@ -103,125 +105,161 @@ foreach ($semanasFuturas as $semana) {
<div class="alert alert-secondary mb-4"> <div class="alert alert-secondary mb-4">
<strong>Turno esta semana:</strong> <?= htmlspecialchars($asignacionEstaSemana['nombre']) ?><br> <strong>Turno esta semana:</strong> <?= htmlspecialchars($asignacionEstaSemana['nombre']) ?><br>
<?php if (!empty($misAsignacionesFuturas)): ?> <?php if (!empty($misAsignacionesFuturas)): ?>
Tu próximo turno: <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['inicio'])) ?> Tu proximo turno: <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['inicio'])) ?>
al <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['fin'])) ?> al <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['fin'])) ?>
<?php else: ?> <?php else: ?>
Tu próximo turno será en las próximas semanas. Tu proximo turno sera en las proximas semanas.
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php elseif (!empty($misAsignacionesFuturas)): ?> <?php elseif (!empty($misAsignacionesFuturas)): ?>
<div class="alert alert-info mb-4"> <div class="alert alert-info mb-4">
<strong>Próximo turno:</strong><br> <strong>Proximo turno:</strong><br>
Del <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['inicio'])) ?> Del <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['inicio'])) ?>
al <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['fin'])) ?> al <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['fin'])) ?>
</div> </div>
<?php else: ?> <?php else: ?>
<div class="alert alert-warning mb-4"> <div class="alert alert-warning mb-4">
No hay turnos asignados para las próximas semanas. No hay turnos asignados para las proximas semanas.
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-primary text-white"> <div class="card-header bg-warning text-dark">
<h5 class="mb-0">Horarios de Apertura del Contenedor</h5> <h5 class="mb-0">Horarios por Semana</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<?php
$diasNombres = [
'domingo' => 'Domingo',
'lunes' => 'Lunes',
'martes' => 'Martes',
'miercoles' => 'Miercoles',
'jueves' => 'Jueves',
'viernes' => 'Viernes',
'sabado' => 'Sabado'
];
$diasOrden = ['domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado'];
?>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover mb-0"> <table class="table table-bordered table-sm mb-0" style="min-width: 600px;">
<thead> <thead class="table-light">
<tr> <tr>
<th>Día</th> <th class="text-center" style="min-width: 120px;">Semana</th>
<th>Hora Apertura</th> <?php foreach ($diasOrden as $dia): ?>
<th>Hora Cierre</th> <th class="text-center"><?= $diasNombres[$dia] ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($horarios as $h): ?>
<tr class="<?= $miTurno ? 'table-primary' : '' ?>">
<td><strong><?= ucfirst($h['dia_semana']) ?></strong></td>
<td><?= date('H:i', strtotime($h['hora_apertura'])) ?></td>
<td><?= date('H:i', strtotime($h['hora_cierre'])) ?></td>
</tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Tabla de Asignaciones de Turnos -->
<div class="card mt-4 shadow-sm">
<div class="card-header bg-success text-white">
<h5 class="mb-0">Calendario de Turnos</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Semana</th>
<th>Período</th>
<th>Asignado a</th>
<th>Estado</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($semanasFuturas as $index => $semana): ?> <?php foreach ($semanasFuturas as $index => $semana): ?>
<tr class="<?= !empty($semana['asignaciones']) && in_array($user['id'], array_column($semana['asignaciones'], 'id')) ? 'table-success' : '' ?>"> <?php
<td> $esMiTurno = !empty($semana['asignaciones']) && in_array($user['id'], array_column($semana['asignaciones'], 'id'));
<strong>Semana <?= date('d/m/y', strtotime($semana['inicio'])) ?></strong> $rowClass = $esMiTurno ? 'table-success' : '';
?>
<tr class="<?= $rowClass ?>">
<td class="text-center align-middle">
<strong><?= date('d/m', strtotime($semana['inicio'])) ?></strong>
<?php if ($index === 0): ?> <?php if ($index === 0): ?>
<span class="badge bg-primary ms-1">Actual</span> <span class="badge bg-primary ms-1">Actual</span>
<?php endif; ?> <?php endif; ?>
</td> <?php if ($esMiTurno): ?>
<td> <span class="badge bg-success ms-1">Tu turno</span>
<?= date('d/m/y', strtotime($semana['inicio'])) ?> (Dom) -
<?= date('d/m/y', strtotime($semana['fin'])) ?> (Vie)
</td>
<td>
<?php if (!empty($semana['asignaciones'])): ?>
<?php foreach ($semana['asignaciones'] as $asignacion): ?>
<div class="mb-1">
<?= htmlspecialchars($asignacion['nombre']) ?>
<?php if ($asignacion['id'] == $user['id']): ?>
<span class="badge bg-success ms-1">Tú</span>
<?php endif; ?> <?php endif; ?>
</div> </td>
<?php foreach ($diasOrden as $dia): ?>
<?php
$horarioDia = null;
foreach ($horarios as $h) {
if ($h['dia_semana'] === $dia) {
$horarioDia = $h;
break;
}
}
$esActivo = $horarioDia && $horarioDia['activo'];
$claseDia = '';
if ($esMiTurno && $esActivo) {
$claseDia = 'bg-success text-white';
} elseif (!$esActivo) {
$claseDia = 'text-muted';
}
?>
<td class="text-center <?= $claseDia ?>" style="min-width: 100px;">
<?php if ($esActivo): ?>
<small><?= date('H:i', strtotime($horarioDia['hora_apertura'])) ?></small><br>
<small><?= date('H:i', strtotime($horarioDia['hora_cierre'])) ?></small>
<?php else: ?>
<small class="text-muted">Cerrado</small>
<?php endif; ?>
</td>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?>
<span class="text-muted">Sin asignar</span>
<?php endif; ?>
</td>
<td>
<?php if (!empty($semana['asignaciones']) && in_array($user['id'], array_column($semana['asignaciones'], 'id'))): ?>
<span class="badge bg-success">Tu turno</span>
<?php elseif (!empty($semana['asignaciones'])): ?>
<span class="badge bg-secondary">
<?= count($semana['asignaciones']) ?> asignado(s)
</span>
<?php else: ?>
<span class="badge bg-warning">Pendiente</span>
<?php endif; ?>
</td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="mt-2">
<small class="text-muted">
<span class="badge bg-success text-white">Verde</span> = Tu turno activo |
<span class="badge bg-secondary text-white">Gris</span> = Dia cerrado |
Horario: Apertura - Cierre
</small>
</div>
</div> </div>
</div> </div>
<div class="card mt-4 shadow-sm"> <div class="card mt-4 shadow-sm">
<div class="card-header bg-info text-white"> <div class="card-header bg-info text-white">
<h5 class="mb-0">Información</h5> <h5 class="mb-0">Turnos de Ayudantes</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<ul class="mb-0"> <div class="table-responsive">
<li>Los turnos se asignan de forma rotativa semanalmente.</li> <table class="table table-hover mb-0">
<li>Cada semana inicia en lunes y termina en domingo.</li> <thead>
<li>Recuerda estar atento a tu turno para abrir y cerrar el contenedor.</li> <tr>
<li>Las filas en verde indican tus turnos asignados.</li> <th>Ayudante</th>
</ul> <th class="text-center">Fecha 1</th>
<th class="text-center">Fecha 2</th>
<th class="text-center">Fecha 3</th>
<th class="text-center">Fecha 4</th>
</tr>
</thead>
<tbody>
<?php foreach ($ayudantes as $ayudante): ?>
<?php
$stmt = $db->prepare("
SELECT semana_inicio, semana_fin
FROM asignaciones_turnos
WHERE user_id = ? AND semana_inicio >= CURDATE()
ORDER BY semana_inicio
LIMIT 4
");
$stmt->execute([$ayudante['id']]);
$turnos = $stmt->fetchAll();
?>
<tr class="<?= $ayudante['id'] == $user['id'] ? 'table-success' : '' ?>">
<td>
<?= htmlspecialchars($ayudante['nombre']) ?>
<?php if ($ayudante['id'] == $user['id']): ?>
<span class="badge bg-success ms-1">Tu</span>
<?php endif; ?>
</td>
<?php for ($i = 0; $i < 4; $i++): ?>
<td class="text-center">
<?php if (isset($turnos[$i])): ?>
<span class="badge bg-primary">
<?= date('d/m/Y', strtotime($turnos[$i]['semana_inicio'])) ?> -
<?= date('d/m/Y', strtotime($turnos[$i]['semana_fin'])) ?>
</span>
<?php else: ?>
<span class="text-muted">-</span>
<?php endif; ?>
</td>
<?php endfor; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@ ini_set('display_errors', 1);
error_reporting(E_ALL); error_reporting(E_ALL);
require_once __DIR__ . '/../src/Auth.php'; require_once __DIR__ . '/../src/Auth.php';
require_once __DIR__ . '/../src/CSRF.php';
$auth = new Auth(); $auth = new Auth();
@@ -19,6 +20,9 @@ $error = '';
$loginInput = ''; $loginInput = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!CSRF::isValidRequest()) {
$error = 'Error de validación del formulario';
} else {
$loginInput = trim($_POST['login'] ?? ''); $loginInput = trim($_POST['login'] ?? '');
$password = $_POST['password'] ?? ''; $password = $_POST['password'] ?? '';
@@ -26,7 +30,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$error = 'Por favor ingresa usuario/email y contraseña'; $error = 'Por favor ingresa usuario/email y contraseña';
} else { } else {
if ($auth->login($loginInput, $password)) { if ($auth->login($loginInput, $password)) {
session_write_close();
if ($auth->isAdmin()) { if ($auth->isAdmin()) {
header('Location: /admin/index.php'); header('Location: /admin/index.php');
} else { } else {
@@ -38,6 +41,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
} }
} }
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
@@ -61,6 +65,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<?php endif; ?> <?php endif; ?>
<form method="POST"> <form method="POST">
<?= CSRF::getTokenField() ?>
<div class="mb-3"> <div class="mb-3">
<label for="login" class="form-label">Usuario o Email</label> <label for="login" class="form-label">Usuario o Email</label>
<input type="text" class="form-control" id="login" name="login" <input type="text" class="form-control" id="login" name="login"

View File

@@ -2,52 +2,49 @@
require_once __DIR__ . '/Database.php'; require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/User.php'; require_once __DIR__ . '/User.php';
require_once __DIR__ . '/Session.php';
$sessionPath = dirname(__DIR__) . '/sessions';
if (!is_dir($sessionPath)) {
mkdir($sessionPath, 0733, true);
}
session_save_path($sessionPath);
session_name('contenedor_session');
class Auth { class Auth {
private $db;
private $userModel; private $userModel;
public function __construct() { public function __construct() {
$this->db = Database::getInstance()->getConnection();
$this->userModel = new User(); $this->userModel = new User();
if (session_status() === PHP_SESSION_NONE) { Session::init();
session_start();
}
} }
public function login($login, $password) { public function login($login, $password) {
$user = $this->userModel->findByLogin($login); $user = $this->userModel->findByLogin($login);
if ($user && password_verify($password, $user['password'])) { if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id']; Session::set('user_id', $user['id']);
$_SESSION['user_name'] = $user['nombre']; Session::set('user_name', $user['nombre']);
$_SESSION['user_rol'] = $user['rol']; Session::set('user_rol', $user['rol']);
$_SESSION['logged_in'] = true; Session::set('logged_in', true);
session_write_close(); Session::set('login_time', time());
return true; return true;
} }
return false; return false;
} }
public function logout() { public function logout() {
session_destroy(); Session::destroy();
$_SESSION = [];
return true; return true;
} }
public function isLoggedIn() { public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true; return Session::get('logged_in') === true;
} }
public function isAdmin() { public function isAdmin() {
return isset($_SESSION['user_rol']) && $_SESSION['user_rol'] === 'admin'; return Session::get('user_rol') === 'admin';
}
public function isAyudante() {
return Session::get('user_rol') === 'ayudante';
}
public function hasRole($role) {
return Session::get('user_rol') === $role;
} }
public function getCurrentUser() { public function getCurrentUser() {
@@ -55,23 +52,32 @@ class Auth {
return null; return null;
} }
return [ return [
'id' => $_SESSION['user_id'], 'id' => Session::get('user_id'),
'nombre' => $_SESSION['user_name'], 'nombre' => Session::get('user_name'),
'rol' => $_SESSION['user_rol'] 'rol' => Session::get('user_rol')
]; ];
} }
public function requireAuth() { public function requireAuth($redirectUrl = '/login.php') {
if (!$this->isLoggedIn()) { if (!$this->isLoggedIn()) {
header('Location: /login.php'); header('Location: ' . $redirectUrl);
exit; exit;
} }
} }
public function requireAdmin() { public function requireAdmin($redirectUrl = '/ayudante.php') {
$this->requireAuth(); $this->requireAuth($redirectUrl);
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
header('Location: /ayudante.php'); header('Location: ' . $redirectUrl);
exit;
}
}
public function requireRole($roles, $redirectUrl = '/') {
$this->requireAuth($redirectUrl);
$userRole = Session::get('user_rol');
if (!in_array($userRole, (array)$roles)) {
header('Location: ' . $redirectUrl);
exit; exit;
} }
} }

65
src/CSRF.php Normal file
View File

@@ -0,0 +1,65 @@
<?php
class CSRF {
private static $tokenName = 'csrf_token';
private static $tokenLifetime = 3600;
public static function generateToken() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
$token = bin2hex(random_bytes(32));
$_SESSION[self::$tokenName] = [
'token' => $token,
'expires' => time() + self::$tokenLifetime
];
return $token;
}
public static function getToken() {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (isset($_SESSION[self::$tokenName])) {
$data = $_SESSION[self::$tokenName];
if ($data['expires'] > time()) {
return $data['token'];
}
unset($_SESSION[self::$tokenName]);
}
return self::generateToken();
}
public static function validateToken($token) {
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION[self::$tokenName]) || empty($token)) {
return false;
}
$data = $_SESSION[self::$tokenName];
unset($_SESSION[self::$tokenName]);
return hash_equals($data['token'], $token);
}
public static function getTokenField() {
return '<input type="hidden" name="csrf_token" value="' . self::getToken() . '">';
}
public static function isValidRequest() {
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET' || $method === 'HEAD') {
return true;
}
$token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
return self::validateToken($token);
}
}

View File

@@ -33,7 +33,8 @@ class Database {
] ]
); );
} catch (PDOException $e) { } catch (PDOException $e) {
die("Error de conexión a la base de datos: " . $e->getMessage()); error_log("Error de conexión a la base de datos: " . $e->getMessage());
throw new Exception("Error de conexión a la base de datos");
} }
} }

54
src/Session.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
class Session {
private static $sessionPath;
public static function init() {
$sessionPath = dirname(__DIR__) . '/sessions';
if (!is_dir($sessionPath)) {
mkdir($sessionPath, 0733, true);
}
session_save_path($sessionPath);
session_name('contenedor_session');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
public static function set($key, $value) {
self::init();
$_SESSION[$key] = $value;
}
public static function get($key, $default = null) {
self::init();
return $_SESSION[$key] ?? $default;
}
public static function has($key) {
self::init();
return isset($_SESSION[$key]);
}
public static function remove($key) {
self::init();
unset($_SESSION[$key]);
}
public static function destroy() {
self::init();
session_destroy();
$_SESSION = [];
}
public static function regenerate($deleteOldSession = false) {
self::init();
session_regenerate_id($deleteOldSession);
}
public static function getId() {
self::init();
return session_id();
}
}

View File

@@ -125,4 +125,20 @@ class User {
$stmt = $this->db->query("SELECT * FROM users WHERE rol = 'ayudante' AND activo = 1 ORDER BY nombre"); $stmt = $this->db->query("SELECT * FROM users WHERE rol = 'ayudante' AND activo = 1 ORDER BY nombre");
return $stmt->fetchAll(); return $stmt->fetchAll();
} }
public function isValidRole($role) {
return in_array($role, ['admin', 'ayudante']);
}
public function hasRole($userId, $role) {
$stmt = $this->db->prepare("SELECT rol FROM users WHERE id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch();
return $user && $user['rol'] === $role;
}
public function getAdmins() {
$stmt = $this->db->query("SELECT * FROM users WHERE rol = 'admin' AND activo = 1 ORDER BY nombre");
return $stmt->fetchAll();
}
} }