commit 85894619d8a679f9f2d7db11a9fed35d1f039b87 Author: nickpons666 Date: Mon Jan 19 15:20:36 2026 -0600 Primer subida completa diff --git a/.env b/.env new file mode 100755 index 0000000..8fcc155 --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +DB_HOST=10.10.4.17:3391 +DB_NAME=contenedor_ibiza +DB_USER=nickpons666 +DB_PASS=MiPo6425@@ + +SITE_URL=https://contenedor-ibiza.ddns.net + +TELEGRAM_BOT_TOKEN=8589698394:AAFSphFBEy1DQmOIUDyEKCMAwksTaYlatYE + +SESSION_NAME=contenedor_session diff --git a/.env.example b/.env.example new file mode 100755 index 0000000..be98d62 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +DB_HOST=localhost +DB_NAME=contenedor_ibiza +DB_USER=root +DB_PASS= + +SITE_URL=http://localhost:8080 + +TELEGRAM_BOT_TOKEN=tu_token_aqui + +SESSION_NAME=contenedor_session diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..794a40b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Logs +logs/ +*.log diff --git a/README.md b/README.md new file mode 100755 index 0000000..7d93165 --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +# Sistema de Administración de Apertura y Cierre de Contenedor + +## Descripción + +Sistema web en PHP + MySQL para administrar la apertura y cierre de un contenedor de basura, asignando turnos semanales a personas con horarios configurables. + +## Requisitos + +- PHP 7.4 o superior +- MySQL 5.7 o superior +- Extensión PHP PDO MySQL +- Composer (opcional) +- Servidor web (Apache/Nginx/PHP built-in) + +## Instalación + +### 1. Clonar o copiar los archivos + +Copiar todos los archivos del proyecto al directorio del servidor web. + +### 2. Configurar variables de entorno + +Copiar el archivo de ejemplo: + +```bash +cp .env.example .env +``` + +Editar el archivo `.env` con la configuración correcta: + +```env +DB_HOST=localhost +DB_NAME=contenedor_ibiza +DB_USER=root +DB_PASS=tu_password + +SITE_URL=http://localhost:8080 + +TELEGRAM_BOT_TOKEN=tu_token_de_telegram +``` + +### 3. Crear la base de datos + +Ejecutar el script SQL: + +```bash +mysql -u root -p < sql/schema.sql +``` + +O importar el archivo `sql/schema.sql` desde phpMyAdmin o cualquier cliente MySQL. + +### 4. Configurar el servidor web + +#### Con PHP built-in (desarrollo): + +```bash +php -S localhost:8080 -t public +``` + +#### Con Apache: + +Configurar el document root hacia la carpeta `public/`. + +#### Con Nginx: + +```nginx +server { + listen 80; + server_name localhost; + root /path/to/project/public; + index index.php; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + include fastcgi_params; + fastcgi_pass unix:/var/run/php/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } +} +``` + +### 5. Credenciales de acceso + +- **Email:** admin@ibiza.com +- **Contraseña:** admin123 + +## Estructura del Proyecto + +``` +contenedor/ +├── .env.example # Variables de entorno +├── config/ +│ └── config.php # Configuración general +├── public/ # Archivos públicos +│ ├── index.php # Redirección según rol +│ ├── login.php # Página de login +│ ├── logout.php # Cierre de sesión +│ ├── ayudante.php # Panel del ayudante +│ ├── admin/ # Panel de administración +│ │ ├── index.php # Dashboard +│ │ ├── usuarios.php # CRUD de usuarios +│ │ ├── horarios.php # Configuración de horarios +│ │ ├── asignaciones.php # Asignación de turnos con reordenamiento +│ │ └── webhook.php # Configuración del webhook del bot +│ └── partials/ # Compartials/ +│ └── navbar.php # Barra de navegación +├── src/ # Código fuente +│ ├── Database.php # Conexión a BD +│ ├── Auth.php # Autenticación +│ ├── User.php # Modelo de usuarios +│ ├── DiasHorarios.php # Modelo de horarios +│ └── Asignacion.php # Modelo de asignaciones y rotación automática +├── bot/ # Bot de Telegram +│ ├── TelegramBot.php # Clase del bot con teclado de menú +│ ├── webhook.php # Webhook del bot +│ └── setup_webhook.php # Script CLI para configurar webhook +├── sql/ +│ └── schema.sql # Esquema de base de datos +├── scripts/ # Scripts CLI +│ └── rotar_automatico.php # Script de rotación automática +└── assets/ # Recursos estáticos + ├── css/ + └── js/ +``` + +## Configuración del Bot de Telegram + +### 1. Crear un bot + +1. Talking to @BotFather on Telegram +2. Send `/newbot` to create a new bot +3. Follow the instructions to set a name and username +4. Copy the API token + +### 2. Configurar el webhook + +El webhook puede configurarse de tres formas: + +**Opción A: Desde el panel de administración** +1. Ir a Administración > Webhook +2. Ingresar la URL del bot +3. Click en "Configurar Webhook" + +**Opción B: Con script CLI** +```bash +php bot/setup_webhook.php +``` + +**Opción C: Manualmente** +``` +https://api.telegram.org/bot/setWebhook?url=https://tu-dominio.com/bot/webhook.php +``` + +### 3. Usar el bot + +El bot tiene un teclado de menú con las siguientes opciones: + +- **📅 Ver Turnos** - Muestra todos los turnos de la semana actual +- **🔍 ¿Qué Semana Es?** - Indica la semana actual del ciclo de rotación +- **📋 Ayuda** - Muestra los comandos disponibles + +**Comandos disponibles:** +- `/start` - Iniciar el bot +- `/menu` - Mostrar el teclado de menú +- `/turnos` - Ver turnos de la semana actual +- `/semana` - Ver qué semana es del ciclo de rotación + +## Configuración de Rotación Automática + +Para configurar la rotación automática de turnos cada semana, agregar al crontab: + +```bash +# Rotar turnos cada domingo a las 00:00 +0 0 * * 0 /usr/bin/php /path/to/project/scripts/rotar_automatico.php >> /var/log/rotacion.log 2>&1 +``` + +O ejecutar manualmente: + +```bash +php scripts/rotar_automatico.php +``` + +## Funcionalidades + +### Administrador +- Crear, editar y desactivar usuarios +- Asignar turnos por semana con selector agrupado por mes +- Visualizar asignaciones con contador "Semana X de 4" +- **Reordenar rotación** - Arrastrar y soltar para cambiar el orden de la rotación +- Recálculo automático de asignaciones futuras al reordenar +- Configurar días activos de apertura +- Configurar horarios de apertura y cierre por día +- Visualizar todas las asignaciones +- Rotar turnos manualmente +- Configurar webhook del bot de Telegram + +### Ayudante +- Visualizar únicamente los días y horarios que le corresponden +- Ver información de su turno actual +- **Calendario de Turnos** - Tabla con todos los turnos de las próximas 5 semanas + +### Bot de Telegram +- **Teclado de menú** con botones interactivos +- `/start` - Iniciar conversación con el bot +- `/menu` - Mostrar teclado de opciones +- `/turnos` - Ver todos los turnos de la semana actual +- `/semana` - Ver qué semana es del ciclo de rotación (1-4) +- Configuración de webhook desde el panel de administración + +## Personalización + +### Agregar más personas + +1. Ir al panel de administración > Usuarios +2. Click en "Nuevo Usuario" +3. Completar los datos + +### Modificar horarios + +1. Ir al panel de administración > Horarios +2. Modificar las horas de apertura y cierre +3. Activar/desactivar días según necesidad + +### Cambiar configuración de turnos + +Los turnos pueden asignarse manualmente desde el panel de administración > Asignaciones. + +## Seguridad + +- Las contraseñas se almacenan usando `password_hash()` de PHP +- Las sesiones son únicas por usuario +- Los accesos están protegidos por autenticación +- Las consultas usan prepared statements para prevenir SQL injection + +## Solución de Problemas + +### Error de conexión a la base de datos +- Verificar las credenciales en el archivo `.env` +- Asegurarse de que MySQL esté ejecutándose + +### El bot de Telegram no responde +- Verificar que el token sea correcto en `.env` +- Confirmar que el webhook esté configurado correctamente +- Usar el panel de administración > Webhook para verificar +- Revisar los logs del servidor + +### Error "Todos los campos son obligatorios" al guardar horarios +- Verificar que las variables de horario coincidan con los nombres del formulario + +### Error en rotación de turnos +- Verificar que exista al menos un usuario activo +- Verificar que la tabla `asignaciones_turnos` tenga registros previos +- El ciclo de rotación inicia el domingo 28 de diciembre de 2024 diff --git a/bot/TelegramBot.php b/bot/TelegramBot.php new file mode 100755 index 0000000..05faf90 --- /dev/null +++ b/bot/TelegramBot.php @@ -0,0 +1,206 @@ +token = $config['telegram_bot_token']; + $this->apiUrl = "https://api.telegram.org/bot{$this->token}"; + } + + private function request($method, $data = []) { + $url = "{$this->apiUrl}/{$method}"; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + return json_decode($response, true); + } + + public function sendMessage($chatId, $text, $parseMode = 'HTML', $replyMarkup = null) { + $data = [ + 'chat_id' => $chatId, + 'text' => $text, + 'parse_mode' => $parseMode + ]; + if ($replyMarkup) { + $data['reply_markup'] = $replyMarkup; + } + return $this->request('sendMessage', $data); + } + + public function getMe() { + return $this->request('getMe'); + } + + public function getUpdates($offset = 0) { + return $this->request('getUpdates', ['offset' => $offset, 'timeout' => 60]); + } + + public function sendKeyboard($chatId, $text) { + $keyboard = [ + 'inline_keyboard' => [ + [ + ['text' => 'Ver Turnos', 'callback_data' => 'ver_turnos'], + ['text' => 'Semana Actual', 'callback_data' => 'semana_actual'] + ], + [ + ['text' => '🔍 Buscar por Nombre', 'callback_data' => 'buscar_nombre'] + ] + ] + ]; + + return $this->sendMessage($chatId, $text, 'HTML', json_encode($keyboard)); + } + + public function answerCallback($callbackId, $text, $showAlert = false) { + return $this->request('answerCallbackQuery', [ + 'callback_query_id' => $callbackId, + 'text' => $text, + 'show_alert' => $showAlert + ]); + } + + public function deleteMessage($chatId, $messageId) { + return $this->request('deleteMessage', [ + 'chat_id' => $chatId, + 'message_id' => $messageId + ]); + } + + public function editMessage($chatId, $messageId, $text, $keyboard = null) { + $data = [ + 'chat_id' => $chatId, + 'message_id' => $messageId, + 'text' => $text, + 'parse_mode' => 'HTML' + ]; + if ($keyboard) { + $data['reply_markup'] = $keyboard; + } + return $this->request('editMessageText', $data); + } + + public function getTablaTurnos($semanas = 4) { + $asignacion = new Asignacion(); + $ayudantes = $asignacion->getAyudantesPorOrden(); + + if (empty($ayudantes)) { + return "No hay ayudantes configurados."; + } + + $hoy = new DateTime(); + $diaSemana = (int)$hoy->format('w'); + $domingo = clone $hoy; + $domingo->modify('-' . $diaSemana . ' days'); + + $tabla = "TABLA DE TURNOS\n\n"; + $tabla .= ""; + $tabla .= str_pad("Semana", 10) . " | " . str_pad("Ayudante", 12) . " | Periodo\n"; + $tabla .= str_repeat("-", 45) . "\n"; + + for ($i = 0; $i < $semanas; $i++) { + $domingoSemana = clone $domingo; + $domingoSemana->modify("+{$i} weeks"); + $viernesSemana = clone $domingoSemana; + $viernesSemana->modify('+5 days'); + + $posicion = ($i % count($ayudantes)); + $ayudante = $ayudantes[$posicion]; + + $semanaNum = $i + 1; + $periodo = $domingoSemana->format('d/m') . '-' . $viernesSemana->format('d/m'); + + $tabla .= str_pad("Sem $semanaNum", 10) . " | " . + str_pad(substr($ayudante['nombre'], 0, 10), 12) . " | $periodo\n"; + } + + $tabla .= ""; + $tabla .= "\n\nCiclo: " . implode(' -> ', array_column($ayudantes, 'nombre')); + + return $tabla; + } + + public function getSemanaActual() { + $asignacion = new Asignacion(); + + $hoy = new DateTime(); + $diaSemana = (int)$hoy->format('w'); + $domingo = clone $hoy; + $domingo->modify('-' . $diaSemana . ' days'); + + $asignacionActual = $asignacion->getAsignacionPorSemana($domingo->format('Y-m-d')); + + if ($asignacionActual) { + return "SEMANA ACTUAL\n\n" . + "Asignado: {$asignacionActual['nombre']}\n" . + "Periodo: " . date('d/m/Y', strtotime($asignacionActual['semana_inicio'])) . + " - " . date('d/m/Y', strtotime($asignacionActual['semana_fin'])) . "\n" . + "Dias: Domingo a Viernes"; + } else { + return "No hay asignacion para esta semana."; + } + } + + public function getTurnosAyudante($nombre) { + $config = require __DIR__ . '/../config/config.php'; + + try { + $pdo = new PDO( + "mysql:host={$config['db']['host']};port={$config['db']['port']};dbname={$config['db']['database']};charset=utf8mb4", + $config['db']['username'], + $config['db']['password'], + [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] + ); + } catch (Exception $e) { + return "Error de conexion."; + } + + $stmt = $pdo->prepare("SELECT * FROM users WHERE nombre LIKE ? AND rol = 'ayudante' AND activo = 1 LIMIT 1"); + $stmt->execute(["%$nombre%"]); + $user = $stmt->fetch(); + + if (!$user) { + $ayudantes = (new Asignacion())->getAyudantesPorOrden(); + $nombres = implode(', ', array_map(fn($a) => $a['nombre'], $ayudantes)); + return "No encontre '$nombre'.\n\nAyudantes: $nombres"; + } + + $stmt = $pdo->prepare(" + SELECT semana_inicio, semana_fin + FROM asignaciones_turnos + WHERE user_id = ? AND semana_inicio >= CURDATE() + ORDER BY semana_inicio + LIMIT 4 + "); + $stmt->execute([$user['id']]); + $turnos = $stmt->fetchAll(); + + if (empty($turnos)) { + return "{$user['nombre']} no tiene turnos proximos."; + } + + $result = "TURNOS DE {$user['nombre']}\n\n"; + + foreach ($turnos as $turno) { + $result .= date('d/m/Y', strtotime($turno['semana_inicio'])) . + " - " . date('d/m/Y', strtotime($turno['semana_fin'])) . "\n"; + } + + return $result; + } + + public function getListaAyudantesParaBusqueda() { + $ayudantes = (new Asignacion())->getAyudantesPorOrden(); + return array_map(fn($a) => $a['nombre'], $ayudantes); + } +} diff --git a/bot/setup_webhook.php b/bot/setup_webhook.php new file mode 100755 index 0000000..1e55e9f --- /dev/null +++ b/bot/setup_webhook.php @@ -0,0 +1,49 @@ +#!/usr/bin/env php + + * + * Ejemplo: php setup_webhook.php https://contenedor-test.local:82/bot/webhook.php + * + * Requiere: TELEGRAM_BOT_TOKEN configurado en .env + */ + +require_once __DIR__ . '/../config/config.php'; + +$config = require __DIR__ . '/../config/config.php'; +$token = $config['telegram_bot_token']; + +if (empty($token)) { + echo "ERROR: TELEGRAM_BOT_TOKEN no configurado en .env\n"; + exit(1); +} + +$urlWebhook = $argv[1] ?? "https://contenedor-test.local:82/bot/webhook.php"; + +echo "Configurando webhook...\n"; +echo "URL: {$urlWebhook}\n\n"; + +$url = "https://api.telegram.org/bot{$token}/setWebhook"; +$ch = curl_init($url); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ + 'url' => $urlWebhook, + 'allowed_updates' => ['message', 'callback_query'] +])); +curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +$response = curl_exec($ch); +curl_close($ch); + +$result = json_decode($response, true); + +if ($result && $result['ok']) { + echo "Webhook configurado correctamente!\n\n"; + echo "Ahora ve a Telegram y envia /start al bot\n"; +} else { + echo "Error al configurar webhook:\n"; + print_r($result); + exit(1); +} diff --git a/bot/webhook.php b/bot/webhook.php new file mode 100755 index 0000000..f3ae82f --- /dev/null +++ b/bot/webhook.php @@ -0,0 +1,127 @@ +config = require __DIR__ . '/../config/config.php'; + $this->bot = new TelegramBot(); + } + + public function handleUpdate($update) { + try { + // Manejar callback de botones inline + if (isset($update['callback_query'])) { + $this->handleCallback($update['callback_query']); + return; + } + + // Manejar mensajes normales + if (!isset($update['message'])) { + return; + } + + $message = $update['message']; + $chatId = $message['chat']['id']; + $text = trim($message['text'] ?? ''); + + if (empty($text)) { + return; + } + + $textLower = mb_strtolower($text, 'UTF-8'); + + // Comandos + if ($textLower === '/start' || $textLower === '/menu' || $textLower === 'menu') { + $this->sendMenu($chatId); + } elseif ($textLower === '/turnos' || $textLower === 'turnos') { + $this->bot->sendMessage($chatId, $this->bot->getTablaTurnos(8)); + } elseif ($textLower === '/semana' || $textLower === 'semana' || $textLower === 'hoy') { + $this->bot->sendMessage($chatId, $this->bot->getSemanaActual()); + } elseif ($textLower === '/ayudantes' || $textLower === 'ayudantes') { + $ayudantes = $this->bot->getListaAyudantesParaBusqueda(); + $this->bot->sendMessage($chatId, "AYUDANTES DISPONIBLES:\n\n" . implode("\n", $ayudantes)); + } else { + // Buscar por nombre + $this->bot->sendMessage($chatId, $this->bot->getTurnosAyudante($text)); + } + } catch (Exception $e) { + error_log("Error en handleUpdate: " . $e->getMessage()); + if (isset($update['message']['chat']['id'])) { + $this->bot->sendMessage($update['message']['chat']['id'], "Error: " . $e->getMessage()); + } + } + } + + private function handleCallback($callback) { + try { + $callbackId = $callback['id']; + $data = $callback['data']; + $message = $callback['message']; + $chatId = $message['chat']['id']; + $messageId = $message['message_id']; + + switch ($data) { + case 'ver_turnos': + $this->bot->answerCallback($callbackId, 'Cargando turnos...'); + $this->bot->editMessage($chatId, $messageId, $this->bot->getTablaTurnos(8)); + break; + + case 'semana_actual': + $this->bot->answerCallback($callbackId, 'Cargando semana actual...'); + $this->bot->editMessage($chatId, $messageId, $this->bot->getSemanaActual()); + break; + + case 'buscar_nombre': + $this->bot->answerCallback($callbackId, ''); + $this->bot->deleteMessage($chatId, $messageId); + $this->bot->sendMessage($chatId, "🔍 Buscar por Nombre\n\nEscribe el nombre del ayudante que buscas:"); + break; + + case 'mi_turno': + $this->bot->answerCallback($callbackId, 'Enviando tu turno...'); + $this->bot->editMessage($chatId, $messageId, "Por favor ingresa tu nombre para ver tu turno:"); + break; + + default: + $this->bot->answerCallback($callbackId, 'Opcion no reconocida'); + } + } catch (Exception $e) { + error_log("Error en handleCallback: " . $e->getMessage()); + } + } + + private function sendMenu($chatId) { + $mensaje = "BOT DE TURNOS - CONTENEDOR IBIZA\n\n"; + $mensaje .= "Selecciona una opcion del menu:\n\n"; + $mensaje .= "Ver Turnos - Tabla completa de asignaciones\n"; + $mensaje .= "Semana Actual - Quien tiene turno esta semana\n"; + $mensaje .= "Buscar por Nombre - Consultar un ayudante especifico\n"; + $mensaje .= "Mi Turno - Ver tu proximo turno"; + + $this->bot->sendKeyboard($chatId, $mensaje); + } +} + +// Recibir actualización +$update = json_decode(file_get_contents('php://input'), true); + +// Log para debugging +error_log("Webhook recibido: " . json_encode($update)); + +if ($update) { + $bot = new TurnoBot(); + $bot->handleUpdate($update); +} else { + http_response_code(200); + echo "Webhook activo. Usa /start para ver el menu."; +} diff --git a/config/config.php b/config/config.php new file mode 100755 index 0000000..0a7a6d0 --- /dev/null +++ b/config/config.php @@ -0,0 +1,35 @@ + [ + 'host' => getenv('DB_HOST') ?: 'localhost', + 'port' => getenv('DB_PORT') ?: '3306', + 'database' => getenv('DB_NAME') ?: 'contenedor_ibiza', + 'username' => getenv('DB_USER') ?: 'root', + 'password' => getenv('DB_PASS') ?: '', + ], + 'site_url' => getenv('SITE_URL') ?: 'http://localhost:8080', + 'telegram_bot_token' => getenv('TELEGRAM_BOT_TOKEN') ?: '', + 'session_name' => getenv('SESSION_NAME') ?: 'contenedor_session', +]; diff --git a/config/error_logging.php b/config/error_logging.php new file mode 100755 index 0000000..ccc2e99 --- /dev/null +++ b/config/error_logging.php @@ -0,0 +1,58 @@ + 'ERROR', + E_WARNING => 'WARNING', + E_PARSE => 'PARSE', + E_NOTICE => 'NOTICE', + E_CORE_ERROR => 'CORE_ERROR', + E_CORE_WARNING => 'CORE_WARNING', + E_COMPILE_ERROR => 'COMPILE_ERROR', + E_COMPILE_WARNING => 'COMPILE_WARNING', + E_USER_ERROR => 'USER_ERROR', + E_USER_WARNING => 'USER_WARNING', + E_USER_NOTICE => 'USER_NOTICE', + E_STRICT => 'STRICT', + E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR', + E_DEPRECATED => 'DEPRECATED', + E_USER_DEPRECATED => 'USER_DEPRECATED', + default => 'UNKNOWN' + }; + + $message = "[$timestamp] PHP $errorType: $errstr in $errfile on line $errline"; + error_log($message . PHP_EOL, 3, $logFile); + + return false; +}); + +set_exception_handler(function($exception) use ($logFile) { + $timestamp = date('Y-m-d H:i:s'); + $message = "[$timestamp] UNCAUGHT EXCEPTION: " . get_class($exception) . ": " . + $exception->getMessage() . " in " . $exception->getFile() . + " on line " . $exception->getLine() . "\n" . $exception->getTraceAsString(); + error_log($message . PHP_EOL, 3, $logFile); +}); + +register_shutdown_function(function() use ($logFile) { + $error = error_get_last(); + if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { + $timestamp = date('Y-m-d H:i:s'); + $message = "[$timestamp] FATAL: " . $error['message'] . " in " . + $error['file'] . " on line " . $error['line']; + error_log($message . PHP_EOL, 3, $logFile); + } +}); diff --git a/crontab_config.txt b/crontab_config.txt new file mode 100755 index 0000000..ca6df37 --- /dev/null +++ b/crontab_config.txt @@ -0,0 +1,45 @@ +# ============================================ +# CONFIGURACIÓN DE CRONTAB PARA ROTACIÓN AUTOMÁTICA +# Sistema de Contenedor Ibiza +# ============================================ + +# Opción 1: Ejecutar todos los domingos a las 00:00 (medianoche) +# ---------------------------------------------------------------- +0 0 * * 0 cd /var/www/html/contenedor && php scripts/rotar_automatico.php >> /var/log/rotacion.log 2>&1 + +# ============================================ +# EXPLICACIÓN: +# ============================================ +# 0 0 * * 0 = Segundos Minutos Horas DíaDelMes Mes DíaDeSemana +# │ │ │ │ │ │ +# │ │ │ │ │ └─ Domingo (0-7, 0 y 7 son domingo) +# │ │ │ │ └────── Mes (1-12) +# │ │ │ └─────────────── Día del mes (1-31) +# │ │ └─────────────────── Hora (0-23) +# │ └──────────────────────── Minuto (0-59) +# └────────────────────────── Segundo (0-59, siempre 0) + +# cd /var/www/html/contenedor = Ir al directorio del proyecto +# php scripts/rotar_automatico.php = Ejecutar el script de rotación +# >> /var/log/rotacion.log 2>&1 = Guardar salida en log + +# ============================================ +# PASOS PARA INSTALAR: +# ============================================ +# 1. Abrir editor de crontab: +# crontab -e +# +# 2. Copiar y pegar la línea de arriba (sin el # del inicio) +# +# 3. Guardar y salir +# +# 4. Verificar que esté instalado: +# crontab -l +# +# ============================================ + +# ============================================ +# NOTA: Si quieres probar primero, puedes +# ejecutar manualmente con: +# php /var/www/html/contenedor/scripts/rotar_automatico.php +# ============================================ diff --git a/public/admin/asignaciones.php b/public/admin/asignaciones.php new file mode 100755 index 0000000..34240b2 --- /dev/null +++ b/public/admin/asignaciones.php @@ -0,0 +1,559 @@ +requireAdmin(); + +$userModel = new User(); +$horariosModel = new DiasHorarios(); +$asignacionModel = new Asignacion(); + +$message = ''; +$messageType = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + + if ($action === 'asignar') { + $userId = $_POST['user_id'] ?? 0; + $semana = $_POST['semana'] ?? ''; + + if ($userId && $semana) { + $asignacionModel->asignar($userId, $semana); + $message = 'Turno asignado correctamente'; + $messageType = 'success'; + } + } elseif ($action === 'rotar') { + $semana = $_POST['semana'] ?? ''; + $asignacionActual = $asignacionModel->getAsignacionPorSemana($semana); + + if ($asignacionActual) { + $proximaPersona = $asignacionModel->getProximaPersona($asignacionActual['user_id']); + if ($proximaPersona) { + $asignacionModel->asignar($proximaPersona['id'], $semana); + $message = 'Turno rotado a: ' . htmlspecialchars($proximaPersona['nombre']); + $messageType = 'success'; + } + } + } elseif ($action === 'asignar_masivo') { + $userIds = $_POST['user_ids'] ?? []; + $semanaInicio = $_POST['semana_inicio'] ?? ''; + $rotacionAutomatica = isset($_POST['rotacion_automatica']) ? true : false; + + if (!empty($userIds) && $semanaInicio) { + $resultado = $asignacionModel->asignarMasivo($userIds, $semanaInicio, $rotacionAutomatica); + + if ($resultado['success'] > 0) { + $message = "Se asignaron {$resultado['success']} turnos correctamente"; + if ($rotacionAutomatica) { + $message .= " con rotación automática para la siguiente semana"; + } + $messageType = 'success'; + } + + if (!empty($resultado['errors'])) { + $message .= "
Errores: " . implode('
', $resultado['errors']); + $messageType = 'warning'; + } + } else { + $message = 'Debes seleccionar al menos un ayudante y una semana'; + $messageType = 'danger'; + } + } +} + +$ayudantes = $userModel->getAyudantesActivos(); +$horarios = $horariosModel->getActivos(); + +// Encontrar el domingo actual +$hoy = new DateTime(); +$diaSemana = (int)$hoy->format('w'); // 0 = domingo, 6 = sábado +$domingoActual = clone $hoy; +$domingoActual->modify('-' . $diaSemana . ' days'); // Restar días para llegar al domingo + +$currentWeekStart = $domingoActual->format('Y-m-d'); +$asignacionActual = $asignacionModel->getAsignacionPorSemana($currentWeekStart); + +// Calcular posición en el ciclo (semana X de 4) +function calcularPosicionCiclo($semanaInicio) { + // Empezamos desde el inicio del ciclo: 28 Dic 2025 + $fechaInicioCiclo = new DateTime('2025-12-28'); + $semanaActual = new DateTime($semanaInicio); + + $diasDiferencia = $fechaInicioCiclo->diff($semanaActual)->days; + $semanasDesdeInicio = floor($diasDiferencia / 7); + + // Posición en ciclo de 4 semanas (1-4) + $posicion = ($semanasDesdeInicio % 4) + 1; + + return $posicion; +} + +$posicionCicloActual = calcularPosicionCiclo($currentWeekStart); + +// Generar semanas agrupadas por mes +$mesesEspanol = [ + 'January' => 'Enero', 'February' => 'Febrero', 'March' => 'Marzo', + 'April' => 'Abril', 'May' => 'Mayo', 'June' => 'Junio', + 'July' => 'Julio', 'August' => 'Agosto', 'September' => 'Septiembre', + 'October' => 'Octubre', 'November' => 'Noviembre', 'December' => 'Diciembre' +]; + +$semanasAgrupadas = []; +for ($i = -4; $i <= 12; $i++) { + $semanaDomingo = clone $domingoActual; + $semanaDomingo->modify("+{$i} weeks"); + + $key = $semanaDomingo->format('Y-m'); + $mesIngles = $semanaDomingo->format('F'); + $mesEspanol = $mesesEspanol[$mesIngles] ?? $mesIngles; + $anio = $semanaDomingo->format('Y'); + + if (!isset($semanasAgrupadas[$key])) { + $semanasAgrupadas[$key] = [ + 'nombre' => "$mesEspanol $anio", + 'semanas' => [] + ]; + } + $semanasAgrupadas[$key]['semanas'][] = [ + 'fecha' => $semanaDomingo->format('Y-m-d'), + 'posicion' => calcularPosicionCiclo($semanaDomingo->format('Y-m-d')) + ]; +} + +$currentPage = 'asignaciones'; +$pageTitle = 'Asignación de Turnos'; +?> + + + + + + Asignaciones - Contenedor Ibiza + + + + + + +
+

Asignación de Turnos

+ + +
+ + +
+
+
+
+
Asignación Actual (Semana de 4)
+
+
+

+ Fecha: (Dom) - (Vie) +

+ + +
+ Asignado a: +
+ +
+ + + +
+ +
No hay asignación para esta semana
+ +
+ + +
+ + +
+ +
+ +
+
+
+ +
+
+
+
Horarios Activos
+
+
+
+ + + + + + + + + + + + + + + +
DíaHora
-
+
+
+
+
+
+ +
+
+
Historial de Asignaciones
+
+
+
+ +
+ + +
+
+ + getAsignacionPorSemana($semanaVer); + ?> + + +
+ +Semana de 4 (): + +
+ +
+ + + + +
+ +
+ +No hay asignación para la semana de 4 () +
+ +
+ + +
+ + +
+
+ +
+
+ + +
+
+
Asignación Masiva
+
+
+
+ + +
+
+ + + Debe ser un domingo +
+
+ +
+ + +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + +
+ + NombreEmailUsername
+ +
+
+
+ +
+ + + +
+
+
+
+ + +
+
+
Rotación Automática
+
+
+
+
+
Orden de Rotación Actual:
+
+ getAyudantesPorOrden(); + foreach ($ayudantesOrdenados as $index => $ayudante): + ?> + + . + + +
+ + + asignarSemanasFuturasAutomaticas(12); + ?> +
+ Resultado: Se asignaron semanas futuras + +
Errores: + +
+ +
+
+
+ + + + Asigna automáticamente los próximos 12 semanas siguiendo el orden de rotación + +
+
+
+ +
+ ℹ️ ¿Cómo funciona?
+ • El sistema mantiene un orden cíclico de ayudantes
+ • Cada semana (Dom→Vie) asigna automáticamente al siguiente en la lista
+ • Al agregar nuevos ayudantes, se integran automáticamente en el ciclo
+ • Usa el botón para generar las próximas 12 semanas +
+
+
+ + +
+
+
Reordenar Rotación
+
+
+
+ + +

+ Arrastra los elementos para cambiar el orden de rotación. + Los cambios afectarán las asignaciones futuras. +

+ +
    + getAyudantesPorOrden(); + foreach ($ayudantesOrdenados as $index => $ayudante): + ?> +
  • + + + +
  • + +
+ +
+ + +
+
+ + + $userId) { + $stmt = $db->prepare(" + UPDATE rotacion_orden + SET orden = ? + WHERE user_id = ? AND activo = 1 + "); + $stmt->execute([$index + 1, $userId]); + } + + // Recalcular asignaciones futuras + $resultado = $asignacionModel->recalcularAsignaciones(20); + + if ($resultado['success'] > 0) { + echo '
'; + echo 'Orden actualizado correctamente. '; + echo "Se recalcularon {$resultado['success']} semanas futuras."; + echo '
'; + } + } + ?> + +
+
+
+ + + + + + diff --git a/public/admin/horarios.php b/public/admin/horarios.php new file mode 100755 index 0000000..1d72954 --- /dev/null +++ b/public/admin/horarios.php @@ -0,0 +1,139 @@ +requireAdmin(); + +$horariosModel = new DiasHorarios(); +$message = ''; +$messageType = ''; + +$diasNombres = [ + 'domingo' => 'Domingo', + 'lunes' => 'Lunes', + 'martes' => 'Martes', + 'miercoles' => 'Miércoles', + 'jueves' => 'Jueves', + 'viernes' => 'Viernes', + 'sabado' => 'Sábado' +]; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $dia = $_POST['dia'] ?? ''; + $hora_apertura = $_POST["hora_apertura_$dia"] ?? ''; + $hora_cierre = $_POST["hora_cierre_$dia"] ?? ''; + $activo = isset($_POST["activo_$dia"]) ? 1 : 0; + + if (empty($dia) || empty($hora_apertura) || empty($hora_cierre)) { + $message = 'Todos los campos son obligatorios'; + $messageType = 'danger'; + } else { + $horariosModel->update($dia, compact('hora_apertura', 'hora_cierre', 'activo')); + $message = 'Horario actualizado correctamente'; + $messageType = 'success'; + } +} + +$horarios = $horariosModel->getAll(); +$currentPage = 'horarios'; +$pageTitle = 'Configuración de Horarios'; +?> + + + + + + Horarios - Contenedor Ibiza + + + + + +
+

Configuración de Horarios

+ + +
+ + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
DíaHora AperturaHora CierreActivoAcciones
+ + + + +
+ > +
+
+ +
+
+
+
+
+ +
+
+
Información
+
+
+
    +
  • Los días marcados como "Activo" aparecerán en los turnos.
  • +
  • Los horarios pueden modificarse en cualquier momento.
  • +
  • La hora de cierre debe ser posterior a la hora de apertura.
  • +
+
+
+
+ + + + + diff --git a/public/admin/index.php b/public/admin/index.php new file mode 100755 index 0000000..aacc60c --- /dev/null +++ b/public/admin/index.php @@ -0,0 +1,120 @@ +requireAdmin(); + +$userModel = new User(); +$horariosModel = new DiasHorarios(); +$asignacionModel = new Asignacion(); + +$totalUsuarios = count($userModel->getAll()); +$totalAyudantes = count($userModel->getAyudantesActivos()); +$totalHorarios = count($horariosModel->getAll()); +$asignacionActual = $asignacionModel->getAsignacionActual(); + +$currentPage = 'dashboard'; +$pageTitle = 'Dashboard'; +?> + + + + + + Dashboard - Contenedor Ibiza + + + + + +
+

Panel de Administración

+ +
+
+
+
+
+

Total Usuarios

+
+
+
+
+
+
+
+

Ayudantes Activos

+
+
+
+
+
+
+
+

Días Configurados

+
+
+
+
+
+
+
+ +
+

Turno Actual

+
+
+
+
+ +
+
+
+
+
Acciones Rápidas
+
+ +
+
+
+
+
+
Información del Sistema
+
+
+
    +
  • + Semana actual: + +
  • +
  • + Inicio semana: + +
  • +
  • + Rol actual: + +
  • +
+
+
+
+
+
+ + + + diff --git a/public/admin/logs.php b/public/admin/logs.php new file mode 100755 index 0000000..b3d4ba5 --- /dev/null +++ b/public/admin/logs.php @@ -0,0 +1,112 @@ +requireAdmin(); + +$logFile = BASE_PATH . '/public/logs/error.log'; + +$logs = []; +if (file_exists($logFile)) { + $logs = array_reverse(file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); + $logs = array_slice($logs, 0, 100); +} + +$currentPage = 'logs'; +$pageTitle = 'Logs del Sistema'; +?> + + + + + + Logs - Contenedor Ibiza + + + + + + +
+
+

Logs del Sistema

+
+ Vaciar Logs + Ver Archivo Completo +
+
+ + + +
+
+
+ +
No hay logs registrados.
+ + + +
+ + +
+
+
+ +
+
+
Información
+
+
+
    +
  • Los errores se guardan automáticamente en: logs/error.log
  • +
  • Se registran errores de PHP, excepciones y errores fatales.
  • +
  • La configuración está en: config/error_logging.php
  • +
+
+
+
+ + + + diff --git a/public/admin/usuarios.php b/public/admin/usuarios.php new file mode 100755 index 0000000..4163b3e --- /dev/null +++ b/public/admin/usuarios.php @@ -0,0 +1,223 @@ +requireAdmin(); + +$userModel = new User(); +$message = ''; +$messageType = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + + if ($action === 'create') { + $nombre = trim($_POST['nombre'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $username = trim($_POST['username'] ?? ''); + $password = $_POST['password'] ?? ''; + $rol = $_POST['rol'] ?? 'ayudante'; + + if (empty($nombre) || empty($email) || empty($password)) { + $message = 'Todos los campos son obligatorios'; + $messageType = 'danger'; + } elseif ($userModel->getByEmail($email)) { + $message = 'El email ya está registrado'; + $messageType = 'danger'; + } elseif ($username && $userModel->usernameExists($username)) { + $message = 'El username ya está en uso'; + $messageType = 'danger'; + } else { + $userModel->create(compact('nombre', 'email', 'username', 'password', 'rol')); + $message = 'Usuario creado exitosamente'; + $messageType = 'success'; + } + } elseif ($action === 'update') { + $id = $_POST['id'] ?? 0; + $nombre = trim($_POST['nombre'] ?? ''); + $email = trim($_POST['email'] ?? ''); + $username = trim($_POST['username'] ?? ''); + $password = $_POST['password'] ?? ''; + $rol = $_POST['rol'] ?? 'ayudante'; + + if (empty($nombre) || empty($email)) { + $message = 'Nombre y email son obligatorios'; + $messageType = 'danger'; + } elseif ($userModel->usernameExists($username, $id)) { + $message = 'El username ya está en uso'; + $messageType = 'danger'; + } else { + $userModel->update($id, compact('nombre', 'email', 'username', 'password', 'rol')); + $message = 'Usuario actualizado exitosamente'; + $messageType = 'success'; + } + } elseif ($action === 'toggle') { + $id = $_POST['id'] ?? 0; + $user = $userModel->getById($id); + if ($user) { + if ($user['activo']) { + $userModel->deactivate($id); + } else { + $userModel->activate($id); + } + $message = 'Estado actualizado'; + $messageType = 'success'; + } + } +} + +$users = $userModel->getAll(true); +$currentPage = 'usuarios'; +$pageTitle = 'Gestión de Usuarios'; +?> + + + + + + Usuarios - Contenedor Ibiza + + + + + +
+
+

Gestión de Usuarios

+ +
+ + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
UsernameNombreEmailRolEstadoAcciones
+ + + + + + + + + + +
+ + + +
+ +
+
+
+
+
+ + + + + + + diff --git a/public/admin/webhook.php b/public/admin/webhook.php new file mode 100755 index 0000000..66b38da --- /dev/null +++ b/public/admin/webhook.php @@ -0,0 +1,327 @@ +requireAdmin(); + +$bot = new TelegramBot(); +$message = ''; +$messageType = ''; +$webhookInfo = null; +$botInfo = null; + +// Obtener información del bot +$botMe = $bot->getMe(); +if ($botMe && isset($botMe['ok']) && $botMe['ok']) { + $botInfo = $botMe['result']; +} + +// Verificar estado del webhook +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + + if ($action === 'verificar') { + $url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/getWebhookInfo"; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + $result = json_decode($response, true); + + if ($result && isset($result['ok'])) { + $webhookInfo = $result; + $message = 'Información del webhook obtenida'; + $messageType = 'success'; + } else { + $message = 'Error al obtener información del webhook'; + $messageType = 'danger'; + } + } elseif ($action === 'borrar') { + $url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/deleteWebhook"; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([])); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + $result = json_decode($response, true); + + if ($result && isset($result['ok']) && $result['ok']) { + $message = 'Webhook eliminado correctamente'; + $messageType = 'success'; + $webhookInfo = null; + } else { + $message = 'Error al eliminar webhook: ' . ($result['description'] ?? 'Desconocido'); + $messageType = 'danger'; + } + } elseif ($action === 'configurar') { + $webhookUrl = trim($_POST['webhook_url'] ?? ''); + + if (empty($webhookUrl)) { + $message = 'Debes ingresar la URL del webhook'; + $messageType = 'danger'; + } elseif (!filter_var($webhookUrl, FILTER_VALIDATE_URL)) { + $message = 'La URL ingresada no es válida'; + $messageType = 'danger'; + } else { + $url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/setWebhook"; + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ + 'url' => $webhookUrl, + 'allowed_updates' => ['message', 'callback_query'] + ])); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $response = curl_exec($ch); + curl_close($ch); + $result = json_decode($response, true); + + if ($result && isset($result['ok']) && $result['ok']) { + $message = "Webhook configurado correctamente en:\n" . htmlspecialchars($webhookUrl); + $messageType = 'success'; + } else { + $message = 'Error al configurar webhook: ' . ($result['description'] ?? 'Desconocido'); + $messageType = 'danger'; + } + } + } +} + +// Obtener estado actual del webhook +$url = "https://api.telegram.org/bot{$config['telegram_bot_token']}/getWebhookInfo"; +$ch = curl_init($url); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +$response = curl_exec($ch); +curl_close($ch); +$webhookInfo = json_decode($response, true); + +$currentPage = 'webhook'; +$pageTitle = 'Administración del Bot de Telegram'; +?> + + + + + + <?= $pageTitle ?> - Contenedor Ibiza + + + + + +
+

🤖 Administración del Bot de Telegram

+ + +
+ + + +
+
+
Información del Bot
+
+
+ +
+
+

Nombre:

+

Username: @

+

ID:

+

Estado: + Conectado +

+
+
+ Token configurado correctamente +
+
+ +
+ Error: No se pudo conectar con el bot. + Verifica que el TELEGRAM_BOT_TOKEN esté configurado correctamente en .env +
+ +
+
+ + +
+
+
Estado del Webhook
+
+
+ + +
+ ✅ Webhook activo +

+ URL: +

+
+
    +
  • + Última actualización: + +
  • +
  • + IP permitida: + +
  • +
  • + Errores acumulados: + +
  • +
  • + Actualizaciones pendientes: + +
  • +
+ +
+ ⚠️ Webhook no configurado +

No hay webhook configurado para este bot.

+
+ + +
+ Error: No se pudo obtener información del webhook +
+ + + +
+
+ + +
+ + +
+ + +
+ +
+
+
+ + +
+
+
⚙️ Configurar Webhook
+
+
+ + +
+ + +
+ + + +
+ URL sugerida basada en SITE_URL: +
+ +
+ +
+ 📝 Instrucciones: +
    +
  1. Asegúrate de que la URL sea accesible públicamente (no localhost)
  2. +
  3. El dominio debe tener certificado SSL (HTTPS)
  4. +
  5. Ejemplo de URL: https://contenedor-test.local:82/bot/webhook.php
  6. +
+
+ + +
+
+
+ + +
+
+
📋 Comandos Disponibles
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComandoDescripción
/start o /menuMuestra el menú interactivo con botones
/turnosMuestra la tabla completa de asignaciones
/semana o hoyMuestra quién tiene turno esta semana
/ayudantesLista de todos los ayudantes
[Nombre]Busca los turnos de un ayudante específico
+
+
+
+ + +
+
+
🔗 URLs de Referencia
+
+
+
+
+ Webhook:
+ /bot/webhook.php +
+
+ Test del bot:
+ /bot/test.php +
+
+
+
+
+ + + + diff --git a/public/ayudante.php b/public/ayudante.php new file mode 100755 index 0000000..03b352e --- /dev/null +++ b/public/ayudante.php @@ -0,0 +1,231 @@ +requireAuth(); + +if ($auth->isAdmin()) { + header('Location: /admin/index.php'); + exit; +} + +$user = $auth->getCurrentUser(); +$horariosModel = new DiasHorarios(); +$asignacionModel = new Asignacion(); + +$horarios = $horariosModel->getActivos(); +$asignacionActual = $asignacionModel->getAsignacionActual(); + +// Obtener todas las asignaciones de las próximas semanas +$semanasFuturas = []; + +// Encontrar el domingo de esta semana +$hoy = new DateTime(); +$diaSemana = (int)$hoy->format('w'); // 0 = domingo, 6 = sábado +$domingoEstaSemana = clone $hoy; +$domingoEstaSemana->modify('-' . $diaSemana . ' days'); // Restar días para llegar al domingo + +for ($i = 0; $i <= 4; $i++) { + $semanaDomingo = clone $domingoEstaSemana; + $semanaDomingo->modify("+{$i} weeks"); + + $semanaInicio = $semanaDomingo->format('Y-m-d'); + $asignacionesSemana = $asignacionModel->getTodasAsignacionesPorSemana($semanaInicio); + + $semanasFuturas[] = [ + 'inicio' => $semanaInicio, + 'fin' => date('Y-m-d', strtotime('+5 days', strtotime($semanaInicio))), // +5 días = domingo a viernes + 'asignaciones' => $asignacionesSemana, + 'asignacion' => !empty($asignacionesSemana) ? $asignacionesSemana[0] : null + ]; +} + +$miTurno = $asignacionActual && $asignacionActual['id'] == $user['id']; + +// También verificar si el usuario tiene turno en las próximas semanas +$misAsignacionesFuturas = []; +foreach ($semanasFuturas as $semana) { + foreach ($semana['asignaciones'] as $asignacion) { + if ($asignacion['id'] == $user['id']) { + $misAsignacionesFuturas[] = [ + 'semana' => $semana, + 'asignacion' => $asignacion + ]; + } + } +} +?> + + + + + + Mis Turnos - Contenedor Ibiza + + + + + +
+

Mis Turnos

+ + format('w'); // 0 = domingo, 6 = sábado + $domingoActual = clone $hoy; + $domingoActual->modify('-' . $diaSemana . ' days'); // Restar días para llegar al domingo + $viernesActual = clone $domingoActual; + $viernesActual->modify('+5 days'); + + $asignacionEstaSemana = $asignacionModel->getAsignacionPorSemana($domingoActual->format('Y-m-d')); + $tengoTurnoEstaSemana = $asignacionEstaSemana && $asignacionEstaSemana['id'] == $user['id']; + + if ($tengoTurnoEstaSemana): +?> +
+ ¡Tienes turno esta semana!
+ Del + al +
+ +
+ Turno esta semana:
+ + Tu próximo turno: + al + + Tu próximo turno será en las próximas semanas. + +
+ +
+ Próximo turno:
+ Del + al +
+ +
+ No hay turnos asignados para las próximas semanas. +
+ + +
+
+
Horarios de Apertura del Contenedor
+
+
+
+ + + + + + + + + + + + + + + + + +
DíaHora AperturaHora Cierre
+
+
+
+ + +
+
+
Calendario de Turnos
+
+
+
+ + + + + + + + + + + $semana): ?> + + + + + + + + +
SemanaPeríodoAsignado aEstado
+ Semana + + Actual + + + (Dom) - + (Vie) + + + +
+ + + + +
+ + + Sin asignar + +
+ + Tu turno + + + asignado(s) + + + Pendiente + +
+
+
+
+ +
+
+
Información
+
+
+
    +
  • Los turnos se asignan de forma rotativa semanalmente.
  • +
  • Cada semana inicia en lunes y termina en domingo.
  • +
  • Recuerda estar atento a tu turno para abrir y cerrar el contenedor.
  • +
  • Las filas en verde indican tus turnos asignados.
  • +
+
+
+
+ + + + diff --git a/public/index.php b/public/index.php new file mode 100755 index 0000000..8c41579 --- /dev/null +++ b/public/index.php @@ -0,0 +1,16 @@ +isLoggedIn()) { + header('Location: /login.php'); + exit; +} + +if ($auth->isAdmin()) { + header('Location: /admin/index.php'); +} else { + header('Location: /ayudante.php'); +} +exit; diff --git a/public/login.php b/public/login.php new file mode 100755 index 0000000..a2da44e --- /dev/null +++ b/public/login.php @@ -0,0 +1,82 @@ +isLoggedIn()) { + if ($auth->isAdmin()) { + header('Location: /admin/index.php'); + } else { + header('Location: /ayudante.php'); + } + exit; +} + +$error = ''; +$loginInput = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $loginInput = trim($_POST['login'] ?? ''); + $password = $_POST['password'] ?? ''; + + if (empty($loginInput) || empty($password)) { + $error = 'Por favor ingresa usuario/email y contraseña'; + } else { + if ($auth->login($loginInput, $password)) { + session_write_close(); + if ($auth->isAdmin()) { + header('Location: /admin/index.php'); + } else { + header('Location: /ayudante.php'); + } + exit; + } else { + $error = 'Usuario/email o contraseña incorrectos'; + } + } +} +?> + + + + + + Login - Contenedor Ibiza + + + +
+
+
+
+
+

Contenedor Ibiza

+
Iniciar Sesión
+ + +
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+
+ + + diff --git a/public/logout.php b/public/logout.php new file mode 100755 index 0000000..cebd468 --- /dev/null +++ b/public/logout.php @@ -0,0 +1,8 @@ +logout(); + +header('Location: /login.php'); +exit; diff --git a/public/logs.php b/public/logs.php new file mode 100755 index 0000000..fb320ca --- /dev/null +++ b/public/logs.php @@ -0,0 +1,3 @@ +getCurrentUser(); +$currentPage = $currentPage ?? ''; +?> + diff --git a/scripts/rotar.php b/scripts/rotar.php new file mode 100755 index 0000000..204c333 --- /dev/null +++ b/scripts/rotar.php @@ -0,0 +1,24 @@ +#!/usr/bin/env php +verificarYRotar(); + +echo "=== Rotación de Turnos ===\n"; +echo "Fecha: " . date('Y-m-d H:i:s') . "\n"; +echo "Resultado: " . $resultado['message'] . "\n"; + +if (isset($resultado['already_assigned'])) { + echo "Estado: Ya estaba asignada\n"; + exit(0); +} + +if ($resultado['success']) { + echo "Estado: Éxito\n"; + exit(0); +} else { + echo "Estado: Error\n"; + exit(1); +} diff --git a/scripts/rotar_automatico.php b/scripts/rotar_automatico.php new file mode 100755 index 0000000..e374e28 --- /dev/null +++ b/scripts/rotar_automatico.php @@ -0,0 +1,32 @@ +inicializarOrdenRotacion(); +echo " - Usuarios actualizados: {$resultado['actualizados']}\n"; +if (!empty($resultado['errores'])) { + echo " - Errores: " . implode(', ', $resultado['errores']) . "\n"; +} + +// 2. Asignar semanas futuras automáticamente +echo "\n2. Asignando semanas futuras...\n"; +$resultado = $asignacion->asignarSemanasFuturasAutomaticas(12); +echo " - Semanas asignadas: {$resultado['success']}\n"; +if (!empty($resultado['errores'])) { + echo " - Errores: " . implode(', ', $resultado['errores']) . "\n"; +} + +// 3. Mostrar orden de rotación actual +echo "\n3. Orden de rotación actual:\n"; +$ayudantes = $asignacion->getAyudantesPorOrden(); +foreach ($ayudantes as $index => $ayudante) { + echo " " . ($index + 1) . ". {$ayudante['nombre']} (Orden: {$ayudante['orden']})\n"; +} + +echo "\n=== Proceso completado ===\n"; \ No newline at end of file diff --git a/sessions/sess_00bl6h856q88l76ba1voh6fjar b/sessions/sess_00bl6h856q88l76ba1voh6fjar new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_1iq2ih0bf7i2n4dh1dotp5jbsl b/sessions/sess_1iq2ih0bf7i2n4dh1dotp5jbsl new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_1qorjhdal1pmvrcoo0bnfcfgk8 b/sessions/sess_1qorjhdal1pmvrcoo0bnfcfgk8 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_1th6bp3uek4rm6u7ua9ui2pb47 b/sessions/sess_1th6bp3uek4rm6u7ua9ui2pb47 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_21b815bksed3hj8urv2ru75b92 b/sessions/sess_21b815bksed3hj8urv2ru75b92 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_2bbciohucqm3vkt1i50932hcis b/sessions/sess_2bbciohucqm3vkt1i50932hcis new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_2n5h5gch3nj38il7o244eorbom b/sessions/sess_2n5h5gch3nj38il7o244eorbom new file mode 100755 index 0000000..280d0f9 --- /dev/null +++ b/sessions/sess_2n5h5gch3nj38il7o244eorbom @@ -0,0 +1 @@ +user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1; \ No newline at end of file diff --git a/sessions/sess_35u20e7gas4k0v2e4kf1n52194 b/sessions/sess_35u20e7gas4k0v2e4kf1n52194 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_43dk4j9tbdh3lc5pl1li935o7o b/sessions/sess_43dk4j9tbdh3lc5pl1li935o7o new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_4b8ji012s30kabi62jskd7vh1b b/sessions/sess_4b8ji012s30kabi62jskd7vh1b new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_4ofnavtltl5kam8gscb5m4ob09 b/sessions/sess_4ofnavtltl5kam8gscb5m4ob09 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_4uo75t4lsoauaet8u2b9lcdreg b/sessions/sess_4uo75t4lsoauaet8u2b9lcdreg new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_52n4t1en33kk4mmhm8d8hi425d b/sessions/sess_52n4t1en33kk4mmhm8d8hi425d new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_55hetos1v8erfdojstagrok3cg b/sessions/sess_55hetos1v8erfdojstagrok3cg new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_55igfik2tqlp9fv73n32eoevuk b/sessions/sess_55igfik2tqlp9fv73n32eoevuk new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_569qght58r0d0jjcoo9nb6rat2 b/sessions/sess_569qght58r0d0jjcoo9nb6rat2 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_6n6c0fhi7qe1esqokoo997cdul b/sessions/sess_6n6c0fhi7qe1esqokoo997cdul new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_6q7blq7h172qk2spnlsk1qcfdi b/sessions/sess_6q7blq7h172qk2spnlsk1qcfdi new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_6qmvlpm64niup8urblt4qulu6g b/sessions/sess_6qmvlpm64niup8urblt4qulu6g new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_70jnhjdapqi3so3a7tlivk12ig b/sessions/sess_70jnhjdapqi3so3a7tlivk12ig new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_75pk2rjt9qlfraehnlfifut2c1 b/sessions/sess_75pk2rjt9qlfraehnlfifut2c1 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_7ik362psdh2m9s6ocpq0717aka b/sessions/sess_7ik362psdh2m9s6ocpq0717aka new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_7k50rk5l31lir0sfkm37ja5e5a b/sessions/sess_7k50rk5l31lir0sfkm37ja5e5a new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_7k8b343ivebeqtnkva3iqb5p3o b/sessions/sess_7k8b343ivebeqtnkva3iqb5p3o new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_7m8uq06462bsl8f91b69vls8kc b/sessions/sess_7m8uq06462bsl8f91b69vls8kc new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_7r7o6e9pa84074u0ljacmnrkor b/sessions/sess_7r7o6e9pa84074u0ljacmnrkor new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_8rpncmmfl40cu757v33kvet87o b/sessions/sess_8rpncmmfl40cu757v33kvet87o new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_8v3ajfdid4fhhak4ivrgaepf1g b/sessions/sess_8v3ajfdid4fhhak4ivrgaepf1g new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_95saljdgo1bdbpu51aod75eivv b/sessions/sess_95saljdgo1bdbpu51aod75eivv new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_9a79chd621vqopm2qkt61ot3kt b/sessions/sess_9a79chd621vqopm2qkt61ot3kt new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_9eflrgl7j1hkre50l8o9un31l0 b/sessions/sess_9eflrgl7j1hkre50l8o9un31l0 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_9h5m8merop5algdv2s495t1j4k b/sessions/sess_9h5m8merop5algdv2s495t1j4k new file mode 100755 index 0000000..280d0f9 --- /dev/null +++ b/sessions/sess_9h5m8merop5algdv2s495t1j4k @@ -0,0 +1 @@ +user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1; \ No newline at end of file diff --git a/sessions/sess_9nhl79kj4di41dkb2f4hk24dke b/sessions/sess_9nhl79kj4di41dkb2f4hk24dke new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_9ojk9le1om4qgm16k51bp5rjq2 b/sessions/sess_9ojk9le1om4qgm16k51bp5rjq2 new file mode 100755 index 0000000..280d0f9 --- /dev/null +++ b/sessions/sess_9ojk9le1om4qgm16k51bp5rjq2 @@ -0,0 +1 @@ +user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1; \ No newline at end of file diff --git a/sessions/sess_9ot0ujn4rfd1kobtke6tn4o6oq b/sessions/sess_9ot0ujn4rfd1kobtke6tn4o6oq new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_9raj925f1d6kkcqnpkuk6mv0rv b/sessions/sess_9raj925f1d6kkcqnpkuk6mv0rv new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_9sailnre4vnuq0bguqlq6jf4a8 b/sessions/sess_9sailnre4vnuq0bguqlq6jf4a8 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_aclc0fjdoms8tr9mciqjv5r94l b/sessions/sess_aclc0fjdoms8tr9mciqjv5r94l new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_af76mblkugo7gtm61i9oqi6mlm b/sessions/sess_af76mblkugo7gtm61i9oqi6mlm new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_al5o42ung8vr5kaigoaul94fjv b/sessions/sess_al5o42ung8vr5kaigoaul94fjv new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_aqu1vk69dr4svpff9r6n45ngn8 b/sessions/sess_aqu1vk69dr4svpff9r6n45ngn8 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_b13295vdb1mft1u3jl14981jct b/sessions/sess_b13295vdb1mft1u3jl14981jct new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_b6ar2325dsbpc152hic8bo2hbt b/sessions/sess_b6ar2325dsbpc152hic8bo2hbt new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_bjojn9rneht9ics9b9f5jf97go b/sessions/sess_bjojn9rneht9ics9b9f5jf97go new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_bvo8li65tst17fqfigu96crbda b/sessions/sess_bvo8li65tst17fqfigu96crbda new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_cgkh5mvaislhl4mshdc995h32u b/sessions/sess_cgkh5mvaislhl4mshdc995h32u new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_cimh1u3spbbspdtgs711ubihg4 b/sessions/sess_cimh1u3spbbspdtgs711ubihg4 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_cmbf53uqvcrg9fv6elrhnnvnm1 b/sessions/sess_cmbf53uqvcrg9fv6elrhnnvnm1 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_cs1qu9cmtus6qjr0lihvpbp545 b/sessions/sess_cs1qu9cmtus6qjr0lihvpbp545 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_cuie31c5163ttvmoj2g6l03bda b/sessions/sess_cuie31c5163ttvmoj2g6l03bda new file mode 100755 index 0000000..280d0f9 --- /dev/null +++ b/sessions/sess_cuie31c5163ttvmoj2g6l03bda @@ -0,0 +1 @@ +user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1; \ No newline at end of file diff --git a/sessions/sess_d12vv5n3onbq6erl7koqofcfh3 b/sessions/sess_d12vv5n3onbq6erl7koqofcfh3 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_d3i9h8i8kdqrgm4d44o2f2t71h b/sessions/sess_d3i9h8i8kdqrgm4d44o2f2t71h new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_d9bhpnd968qtjp2bun2fcbekfg b/sessions/sess_d9bhpnd968qtjp2bun2fcbekfg new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_dcdm3nv9jeuq4p201afi8kseua b/sessions/sess_dcdm3nv9jeuq4p201afi8kseua new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_dcem3cgs9ll4ap0cviuj8f9q6p b/sessions/sess_dcem3cgs9ll4ap0cviuj8f9q6p new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_dddjo346pr4dgmijkpk81mai2l b/sessions/sess_dddjo346pr4dgmijkpk81mai2l new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_dr2r20k9h23hrsq5i1f26eijmb b/sessions/sess_dr2r20k9h23hrsq5i1f26eijmb new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_dsnfbkkoom2dt08gk3oibg874u b/sessions/sess_dsnfbkkoom2dt08gk3oibg874u new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_e4uv39asdligponb8p9fgvm7g3 b/sessions/sess_e4uv39asdligponb8p9fgvm7g3 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_e9ciopb4n3mtfg840arlprm4qg b/sessions/sess_e9ciopb4n3mtfg840arlprm4qg new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_ei6j6ed6khvrt46hro47fopkjd b/sessions/sess_ei6j6ed6khvrt46hro47fopkjd new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_fbd7nirtqh8bb7muo9456u3t90 b/sessions/sess_fbd7nirtqh8bb7muo9456u3t90 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_fk1hpvrhh7d44vjt1jgim51ud2 b/sessions/sess_fk1hpvrhh7d44vjt1jgim51ud2 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_fnmbtv5bqi28pbpcrdma395gqg b/sessions/sess_fnmbtv5bqi28pbpcrdma395gqg new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_g0gmbnb8jmk8bpj413tnnmhloo b/sessions/sess_g0gmbnb8jmk8bpj413tnnmhloo new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_gdmkamsrs5tqlns339sc6f0rrr b/sessions/sess_gdmkamsrs5tqlns339sc6f0rrr new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_gq1q96v1umot7ubntbspfee6em b/sessions/sess_gq1q96v1umot7ubntbspfee6em new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_gt9h4bk40n1mgrqkglaeot9fl0 b/sessions/sess_gt9h4bk40n1mgrqkglaeot9fl0 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_hq897eqaih8190m9c06bl279vu b/sessions/sess_hq897eqaih8190m9c06bl279vu new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_htp0ti3lumr8t177mertkkhmfa b/sessions/sess_htp0ti3lumr8t177mertkkhmfa new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_i9nbkn73ncsheeferis3si64hn b/sessions/sess_i9nbkn73ncsheeferis3si64hn new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_ip7rvqa024ehfd6ks56tld2fmp b/sessions/sess_ip7rvqa024ehfd6ks56tld2fmp new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_ir1ok0d5p7ndlotvflu2uddhh5 b/sessions/sess_ir1ok0d5p7ndlotvflu2uddhh5 new file mode 100755 index 0000000..280d0f9 --- /dev/null +++ b/sessions/sess_ir1ok0d5p7ndlotvflu2uddhh5 @@ -0,0 +1 @@ +user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1; \ No newline at end of file diff --git a/sessions/sess_j17rcie1m94g9bvhamnbndt0si b/sessions/sess_j17rcie1m94g9bvhamnbndt0si new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_j4mm6t8o3c31vaoo8ia1mhpje6 b/sessions/sess_j4mm6t8o3c31vaoo8ia1mhpje6 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_jq1kt76ehvqs9u6bipc87uf9lq b/sessions/sess_jq1kt76ehvqs9u6bipc87uf9lq new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_k56gj6e3uoi2k72u9064ckkvt8 b/sessions/sess_k56gj6e3uoi2k72u9064ckkvt8 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_kl1afnb38df14avdumihfisa9c b/sessions/sess_kl1afnb38df14avdumihfisa9c new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_koh5hdre53skrs4ej51dbpk65q b/sessions/sess_koh5hdre53skrs4ej51dbpk65q new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_kpr5vo14r7pnditdiumj8fc9hi b/sessions/sess_kpr5vo14r7pnditdiumj8fc9hi new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_l8mnetbq84a4bamp3shdr2rl1d b/sessions/sess_l8mnetbq84a4bamp3shdr2rl1d new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_l9goi2btaotmia9qb2d1bil159 b/sessions/sess_l9goi2btaotmia9qb2d1bil159 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_lc45hgi6pkf352jjd71reosi46 b/sessions/sess_lc45hgi6pkf352jjd71reosi46 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_lhifv4lpf278rambqmauqmpmko b/sessions/sess_lhifv4lpf278rambqmauqmpmko new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_lqttvdtio8usemr5vt7spduqis b/sessions/sess_lqttvdtio8usemr5vt7spduqis new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_lr9b373lqbeaar5ehgqv1lv09s b/sessions/sess_lr9b373lqbeaar5ehgqv1lv09s new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_mft681i3l33oupcl3f1aff83lu b/sessions/sess_mft681i3l33oupcl3f1aff83lu new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_mlnr3rj1798l93t5rdbmipcc2t b/sessions/sess_mlnr3rj1798l93t5rdbmipcc2t new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_n4fasngv0ngr05639b64kp2i49 b/sessions/sess_n4fasngv0ngr05639b64kp2i49 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_nosglauve0sh0g5jbl8bfl375f b/sessions/sess_nosglauve0sh0g5jbl8bfl375f new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_np53skmol4ep6k61mdvul0j647 b/sessions/sess_np53skmol4ep6k61mdvul0j647 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_o45uee2pqla3uuf8mr9e43iahe b/sessions/sess_o45uee2pqla3uuf8mr9e43iahe new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_ohd0dqq68cj64018vmrkekf976 b/sessions/sess_ohd0dqq68cj64018vmrkekf976 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_osbqb0rfdesm6hetf01fm3tv1e b/sessions/sess_osbqb0rfdesm6hetf01fm3tv1e new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_p63p3thvgfgk1q4u9optqhnif1 b/sessions/sess_p63p3thvgfgk1q4u9optqhnif1 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_pav9lmop45d8qid3moqbus4j3i b/sessions/sess_pav9lmop45d8qid3moqbus4j3i new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_pbc8srhv1ptbl5au2hbhg67vh2 b/sessions/sess_pbc8srhv1ptbl5au2hbhg67vh2 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_pefl859vl1258pdapc923oo93a b/sessions/sess_pefl859vl1258pdapc923oo93a new file mode 100755 index 0000000..9887eae --- /dev/null +++ b/sessions/sess_pefl859vl1258pdapc923oo93a @@ -0,0 +1 @@ +user_id|i:4;user_name|s:9:"Esperanza";user_rol|s:8:"ayudante";logged_in|b:1; \ No newline at end of file diff --git a/sessions/sess_pjtev1v8ucc1l7dop0om4tp16t b/sessions/sess_pjtev1v8ucc1l7dop0om4tp16t new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_pt71qmg0svmon5iaat0uso37n9 b/sessions/sess_pt71qmg0svmon5iaat0uso37n9 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_rehf9jbefo0oppr5aeght18b4p b/sessions/sess_rehf9jbefo0oppr5aeght18b4p new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_rg6o20i71kdn0ea6bqs05vts6k b/sessions/sess_rg6o20i71kdn0ea6bqs05vts6k new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_rso95qa5buchs3qijnb23e9sgm b/sessions/sess_rso95qa5buchs3qijnb23e9sgm new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_s8p1c7u4vtuv46gctpr07uemjc b/sessions/sess_s8p1c7u4vtuv46gctpr07uemjc new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_sc7o0gobeqk9am9v4bshfosok2 b/sessions/sess_sc7o0gobeqk9am9v4bshfosok2 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_svpout5pp2d4io2pkiclgpe3lf b/sessions/sess_svpout5pp2d4io2pkiclgpe3lf new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_t5dtg5vlq2ggr38kjb5tgmh7s3 b/sessions/sess_t5dtg5vlq2ggr38kjb5tgmh7s3 new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_t8vgr1uko63tmph2chtqv4mgus b/sessions/sess_t8vgr1uko63tmph2chtqv4mgus new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_to9dj5d7kaeccumijljjue8uph b/sessions/sess_to9dj5d7kaeccumijljjue8uph new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_u3pnjbpo9aft8pagga2ckjtk0c b/sessions/sess_u3pnjbpo9aft8pagga2ckjtk0c new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_vobka04m303u3k2ce7ga5qduig b/sessions/sess_vobka04m303u3k2ce7ga5qduig new file mode 100755 index 0000000..e69de29 diff --git a/sessions/sess_vuudoasoefqjm2dacpfnehvdh5 b/sessions/sess_vuudoasoefqjm2dacpfnehvdh5 new file mode 100755 index 0000000..e69de29 diff --git a/sql/migracion_username.sql b/sql/migracion_username.sql new file mode 100755 index 0000000..2c8af81 --- /dev/null +++ b/sql/migracion_username.sql @@ -0,0 +1,10 @@ +-- Migración: Agregar campo username a tabla users +-- Ejecutar solo si la tabla ya existe sin el campo + +ALTER TABLE users ADD COLUMN username VARCHAR(50) UNIQUE AFTER id; + +-- Actualizar usuarios existentes con usernames +UPDATE users SET username = LOWER(REPLACE(nombre, ' ', '')) WHERE username IS NULL; + +-- Crear índice único +CREATE UNIQUE INDEX idx_username ON users(username); diff --git a/sql/schema.sql b/sql/schema.sql new file mode 100755 index 0000000..8137760 --- /dev/null +++ b/sql/schema.sql @@ -0,0 +1,68 @@ +-- Base de datos: Contenedor Condominio Ibiza +-- Script de creación de tablas e inserción de datos iniciales + +CREATE DATABASE IF NOT EXISTS contenedor_ibiza DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE contenedor_ibiza; + +-- Tabla de usuarios +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE, + nombre VARCHAR(100) NOT NULL, + email VARCHAR(150) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + rol ENUM('admin', 'ayudante') NOT NULL DEFAULT 'ayudante', + activo TINYINT(1) DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Tabla de configuración de días y horarios +CREATE TABLE IF NOT EXISTS dias_horarios ( + id INT AUTO_INCREMENT PRIMARY KEY, + dia_semana ENUM('domingo', 'lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado') NOT NULL UNIQUE, + hora_apertura TIME NOT NULL, + hora_cierre TIME NOT NULL, + activo TINYINT(1) DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Tabla de asignaciones de turnos por semana +CREATE TABLE IF NOT EXISTS asignaciones_turnos ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + semana_inicio DATE NOT NULL, + semana_fin DATE NOT NULL, + orden_turno INT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE KEY unique_semana (semana_inicio) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Tabla para controlar el orden de rotación de personas +CREATE TABLE IF NOT EXISTS rotacion_orden ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + orden INT NOT NULL, + activo TINYINT(1) DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Insertar usuario administrador inicial (password: admin123) +INSERT INTO users (username, nombre, email, password, rol) VALUES +('admin', 'Administrador', 'admin@ibiza.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin'); + +-- Insertar usuarios de ejemplo (ayudantes) +INSERT INTO users (username, nombre, email, password, rol) VALUES +('miguel', 'Miguel', 'miguel@ibiza.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'ayudante'), +('ana', 'Ana', 'ana@ibiza.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'ayudante'), +('carlos', 'Carlos', 'carlos@ibiza.com', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'ayudante'); + +-- Insertar orden de rotación +INSERT INTO rotacion_orden (user_id, orden) VALUES +(2, 1), +(3, 2), +(4, 3); diff --git a/src/Asignacion.php b/src/Asignacion.php new file mode 100755 index 0000000..4242390 --- /dev/null +++ b/src/Asignacion.php @@ -0,0 +1,384 @@ +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(); + } +} diff --git a/src/Auth.php b/src/Auth.php new file mode 100755 index 0000000..5a4b3ad --- /dev/null +++ b/src/Auth.php @@ -0,0 +1,78 @@ +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; + } + } +} diff --git a/src/Database.php b/src/Database.php new file mode 100755 index 0000000..0dfa889 --- /dev/null +++ b/src/Database.php @@ -0,0 +1,50 @@ +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; + } +} diff --git a/src/DiasHorarios.php b/src/DiasHorarios.php new file mode 100755 index 0000000..d160974 --- /dev/null +++ b/src/DiasHorarios.php @@ -0,0 +1,46 @@ +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); + } +} diff --git a/src/RotacionTurnos.php b/src/RotacionTurnos.php new file mode 100755 index 0000000..8c7639d --- /dev/null +++ b/src/RotacionTurnos.php @@ -0,0 +1,72 @@ +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); + } +} diff --git a/src/User.php b/src/User.php new file mode 100755 index 0000000..f768b5f --- /dev/null +++ b/src/User.php @@ -0,0 +1,128 @@ +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(); + } +} diff --git a/src/layout/footer.php b/src/layout/footer.php new file mode 100755 index 0000000..a7f1b2e --- /dev/null +++ b/src/layout/footer.php @@ -0,0 +1,4 @@ + + + + diff --git a/src/layout/header.php b/src/layout/header.php new file mode 100755 index 0000000..2eafeb7 --- /dev/null +++ b/src/layout/header.php @@ -0,0 +1,51 @@ +getCurrentUser() ?? []; +?> + + + + + + <?= $pageTitle ?? 'Panel' ?> - Contenedor Ibiza + + + + +