Primer subida completa
This commit is contained in:
10
.env
Executable file
10
.env
Executable file
@@ -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
|
||||||
10
.env.example
Executable file
10
.env.example
Executable file
@@ -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
|
||||||
3
.gitignore
vendored
Executable file
3
.gitignore
vendored
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
256
README.md
Executable file
256
README.md
Executable file
@@ -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<TOKEN>/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
|
||||||
206
bot/TelegramBot.php
Executable file
206
bot/TelegramBot.php
Executable file
@@ -0,0 +1,206 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
require_once __DIR__ . '/../src/Asignacion.php';
|
||||||
|
require_once __DIR__ . '/../src/User.php';
|
||||||
|
|
||||||
|
class TelegramBot {
|
||||||
|
private $token;
|
||||||
|
private $apiUrl;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$config = require __DIR__ . '/../config/config.php';
|
||||||
|
$this->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 = "<b>TABLA DE TURNOS</b>\n\n";
|
||||||
|
$tabla .= "<code>";
|
||||||
|
$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 .= "</code>";
|
||||||
|
$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 "<b>SEMANA ACTUAL</b>\n\n" .
|
||||||
|
"Asignado: <b>{$asignacionActual['nombre']}</b>\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 = "<b>TURNOS DE {$user['nombre']}</b>\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);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
bot/setup_webhook.php
Executable file
49
bot/setup_webhook.php
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Script para configurar el webhook de Telegram
|
||||||
|
*
|
||||||
|
* Uso: php setup_webhook.php <url_webhook>
|
||||||
|
*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
127
bot/webhook.php
Executable file
127
bot/webhook.php
Executable file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Configurar logging de errores
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
ini_set('error_log', __DIR__ . '/../logs/bot_error.log');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
require_once __DIR__ . '/TelegramBot.php';
|
||||||
|
|
||||||
|
class TurnoBot {
|
||||||
|
private $bot;
|
||||||
|
private $config;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->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, "<b>AYUDANTES DISPONIBLES:</b>\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, "🔍 <b>Buscar por Nombre</b>\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 = "<b>BOT DE TURNOS - CONTENEDOR IBIZA</b>\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.";
|
||||||
|
}
|
||||||
35
config/config.php
Executable file
35
config/config.php
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/error_logging.php';
|
||||||
|
|
||||||
|
$envFile = dirname(__DIR__) . '/.env';
|
||||||
|
if (file_exists($envFile)) {
|
||||||
|
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
if (strpos(trim($line), '#') === 0) continue;
|
||||||
|
if (strpos($line, '=') !== false) {
|
||||||
|
list($key, $value) = explode('=', $line, 2);
|
||||||
|
$key = trim($key);
|
||||||
|
$value = trim($value);
|
||||||
|
$_ENV[$key] = $value;
|
||||||
|
putenv("$key=$value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'db' => [
|
||||||
|
'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',
|
||||||
|
];
|
||||||
58
config/error_logging.php
Executable file
58
config/error_logging.php
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 0);
|
||||||
|
ini_set('log_errors', 1);
|
||||||
|
|
||||||
|
$logFile = __DIR__ . '/../logs/error.log';
|
||||||
|
|
||||||
|
if (!file_exists(dirname($logFile))) {
|
||||||
|
mkdir(dirname($logFile), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ini_set('error_log', $logFile);
|
||||||
|
|
||||||
|
set_error_handler(function($errno, $errstr, $errfile, $errline) use ($logFile) {
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$errorType = match($errno) {
|
||||||
|
E_ERROR => '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);
|
||||||
|
}
|
||||||
|
});
|
||||||
45
crontab_config.txt
Executable file
45
crontab_config.txt
Executable file
@@ -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
|
||||||
|
# ============================================
|
||||||
559
public/admin/asignaciones.php
Executable file
559
public/admin/asignaciones.php
Executable file
@@ -0,0 +1,559 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__, 2));
|
||||||
|
}
|
||||||
|
require_once BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/Auth.php';
|
||||||
|
require_once BASE_PATH . '/src/User.php';
|
||||||
|
require_once BASE_PATH . '/src/DiasHorarios.php';
|
||||||
|
require_once BASE_PATH . '/src/Asignacion.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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 .= "<br>Errores: " . implode('<br>', $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';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Asignaciones - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include BASE_PATH . '/public/partials/navbar.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2 class="mb-4">Asignación de Turnos</h2>
|
||||||
|
|
||||||
|
<?php if ($message): ?>
|
||||||
|
<div class="alert alert-<?= $messageType ?>"><?= htmlspecialchars($message) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0">Asignación Actual (Semana <?= $posicionCicloActual ?> de 4)</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="mb-3">
|
||||||
|
<strong>Fecha:</strong> <?= date('d/m/y', strtotime($currentWeekStart)) ?> (Dom) - <?= date('d/m/y', strtotime('+5 days', strtotime($currentWeekStart))) ?> (Vie)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<?php if ($asignacionActual): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<strong>Asignado a:</strong> <?= htmlspecialchars($asignacionActual['nombre']) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="d-flex gap-2">
|
||||||
|
<input type="hidden" name="action" value="rotar">
|
||||||
|
<input type="hidden" name="semana" value="<?= $currentWeekStart ?>">
|
||||||
|
<button type="submit" class="btn btn-outline-primary">
|
||||||
|
↻ Rotar al siguiente
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-warning">No hay asignación para esta semana</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="asignar">
|
||||||
|
<input type="hidden" name="semana" value="<?= $currentWeekStart ?>">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Asignar a:</label>
|
||||||
|
<select class="form-select" name="user_id" required>
|
||||||
|
<option value="">Seleccionar persona...</option>
|
||||||
|
<?php foreach ($ayudantes as $a): ?>
|
||||||
|
<option value="<?= $a['id'] ?>"><?= htmlspecialchars($a['nombre']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Asignar</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5 class="mb-0">Horarios Activos</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Día</th>
|
||||||
|
<th>Hora</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($horarios as $h): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= ucfirst($h['dia_semana']) ?></td>
|
||||||
|
<td><?= date('H:i', strtotime($h['hora_apertura'])) ?> - <?= date('H:i', strtotime($h['hora_cierre'])) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h5 class="mb-0">Historial de Asignaciones</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="GET" class="mb-3">
|
||||||
|
<label class="form-label">Seleccionar semana:</label>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<select class="form-select" name="semana" style="max-width: 320px;">
|
||||||
|
<?php foreach ($semanasAgrupadas as $grupo): ?>
|
||||||
|
<optgroup label="<?= $grupo['nombre'] ?>">
|
||||||
|
<?php foreach ($grupo['semanas'] as $s): ?>
|
||||||
|
<option value="<?= $s['fecha'] ?>" <?= $s['fecha'] === ($_GET['semana'] ?? $currentWeekStart) ? 'selected' : '' ?>>
|
||||||
|
Semana <?= $s['posicion'] ?> de 4 - <?= date('d/m', strtotime($s['fecha'])) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</optgroup>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-outline-primary">Ver</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$semanaVer = $_GET['semana'] ?? $currentWeekStart;
|
||||||
|
$asignacionVer = $asignacionModel->getAsignacionPorSemana($semanaVer);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($asignacionVer): ?>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<?php $posicionSemanaVer = calcularPosicionCiclo($semanaVer); ?>
|
||||||
|
<strong>Semana <?= $posicionSemanaVer ?> de 4 (<?= date('d/m/y', strtotime($semanaVer)) ?>):</strong>
|
||||||
|
<?= htmlspecialchars($asignacionVer['nombre']) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST" class="d-flex gap-2">
|
||||||
|
<input type="hidden" name="action" value="asignar">
|
||||||
|
<input type="hidden" name="semana" value="<?= $semanaVer ?>">
|
||||||
|
<select class="form-select" name="user_id" style="max-width: 250px;">
|
||||||
|
<option value="">Cambiar persona...</option>
|
||||||
|
<?php foreach ($ayudantes as $a): ?>
|
||||||
|
<option value="<?= $a['id'] ?>" <?= isset($asignacionVer['user_id']) && $a['id'] == $asignacionVer['user_id'] ? 'selected' : '' ?>>
|
||||||
|
<?= htmlspecialchars($a['nombre']) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-outline-secondary">Actualizar</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-secondary">
|
||||||
|
<?php $posicionSinAsignar = calcularPosicionCiclo($semanaVer); ?>
|
||||||
|
No hay asignación para la semana <?= $posicionSinAsignar ?> de 4 (<?= date('d/m/y', strtotime($semanaVer)) ?>)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="asignar">
|
||||||
|
<input type="hidden" name="semana" value="<?= $semanaVer ?>">
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<select class="form-select" name="user_id" style="max-width: 300px;" required>
|
||||||
|
<option value="">Seleccionar persona...</option>
|
||||||
|
<?php foreach ($ayudantes as $a): ?>
|
||||||
|
<option value="<?= $a['id'] ?>"><?= htmlspecialchars($a['nombre']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-primary">Asignar</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección de Asignación Masiva -->
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h5 class="mb-0">Asignación Masiva</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" id="asignacionMasivaForm">
|
||||||
|
<input type="hidden" name="action" value="asignar_masivo">
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Semana de inicio:</label>
|
||||||
|
<input type="date" class="form-control" name="semana_inicio" required>
|
||||||
|
<small class="text-muted">Debe ser un domingo</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Opciones:</label>
|
||||||
|
<div class="form-check mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="rotacion_automatica" id="rotacion_automatica">
|
||||||
|
<label class="form-check-label" for="rotacion_automatica">
|
||||||
|
Activar rotación automática para la siguiente semana
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Seleccionar ayudantes:</label>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="50">
|
||||||
|
<input type="checkbox" class="form-check-input" id="selectAll">
|
||||||
|
</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Username</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($ayudantes as $a): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" class="form-check-input user-checkbox"
|
||||||
|
name="user_ids[]" value="<?= $a['id'] ?>">
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($a['nombre']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($a['email']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($a['username']) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="fas fa-users"></i> Asignar a seleccionados
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="selectAllUsers()">
|
||||||
|
<i class="fas fa-check-square"></i> Seleccionar todos
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="deselectAllUsers()">
|
||||||
|
<i class="fas fa-square"></i> Deseleccionar todos
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección de Rotación Automática -->
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h5 class="mb-0">Rotación Automática</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h6>Orden de Rotación Actual:</h6>
|
||||||
|
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||||
|
<?php
|
||||||
|
$ayudantesOrdenados = $asignacionModel->getAyudantesPorOrden();
|
||||||
|
foreach ($ayudantesOrdenados as $index => $ayudante):
|
||||||
|
?>
|
||||||
|
<span class="badge bg-primary fs-6">
|
||||||
|
<?= ($index + 1) ?>. <?= htmlspecialchars($ayudante['nombre']) ?>
|
||||||
|
</span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'rotacion_automatica'): ?>
|
||||||
|
<?php
|
||||||
|
$resultado = $asignacionModel->asignarSemanasFuturasAutomaticas(12);
|
||||||
|
?>
|
||||||
|
<div class="alert alert-<?= !empty($resultado['errors']) ? 'warning' : 'success' ?>">
|
||||||
|
<strong>Resultado:</strong> Se asignaron <?= $resultado['success'] ?> semanas futuras
|
||||||
|
<?php if (!empty($resultado['errores'])): ?>
|
||||||
|
<br><small>Errores: <?= implode(', ', $resultado['errores']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<form method="POST" class="h-100 d-flex flex-column justify-content-center">
|
||||||
|
<input type="hidden" name="action" value="rotacion_automatica">
|
||||||
|
<button type="submit" class="btn btn-warning w-100">
|
||||||
|
<i class="fas fa-sync"></i> Generar Rotación Automática
|
||||||
|
</button>
|
||||||
|
<small class="text-muted mt-2">
|
||||||
|
Asigna automáticamente los próximos 12 semanas siguiendo el orden de rotación
|
||||||
|
</small>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info mt-3 mb-0">
|
||||||
|
<strong>ℹ️ ¿Cómo funciona?</strong><br>
|
||||||
|
• El sistema mantiene un orden cíclico de ayudantes<br>
|
||||||
|
• Cada semana (Dom→Vie) asigna automáticamente al siguiente en la lista<br>
|
||||||
|
• Al agregar nuevos ayudantes, se integran automáticamente en el ciclo<br>
|
||||||
|
• Usa el botón para generar las próximas 12 semanas
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sección de Reordenar Rotación -->
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<h5 class="mb-0">Reordenar Rotación</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" id="reordenarForm">
|
||||||
|
<input type="hidden" name="action" value="reordenar">
|
||||||
|
|
||||||
|
<p class="text-muted">
|
||||||
|
Arrastra los elementos para cambiar el orden de rotación.
|
||||||
|
Los cambios afectarán las asignaciones futuras.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul id="sortableList" class="list-group">
|
||||||
|
<?php
|
||||||
|
$ayudantesOrdenados = $asignacionModel->getAyudantesPorOrden();
|
||||||
|
foreach ($ayudantesOrdenados as $index => $ayudante):
|
||||||
|
?>
|
||||||
|
<li class="list-group-item d-flex align-items-center" data-id="<?= $ayudante['id'] ?>">
|
||||||
|
<input type="hidden" name="ordenes[<?= $index ?>]" value="<?= $ayudante['id'] ?>">
|
||||||
|
<span class="badge bg-primary me-2" style="cursor: grab;">☰ <?= ($index + 1) ?></span>
|
||||||
|
<span><?= htmlspecialchars($ayudante['nombre']) ?></span>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="mt-3 d-flex gap-2">
|
||||||
|
<button type="submit" class="btn btn-dark">
|
||||||
|
<i class="fas fa-save"></i> Guardar Nuevo Orden
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" onclick="invertirOrden()">
|
||||||
|
<i class="fas fa-exchange-alt"></i> Invertir Orden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'reordenar'): ?>
|
||||||
|
<?php
|
||||||
|
$nuevosOrdenes = $_POST['ordenes'] ?? [];
|
||||||
|
$errores = [];
|
||||||
|
|
||||||
|
if (!empty($nuevosOrdenes)) {
|
||||||
|
foreach ($nuevosOrdenes as $index => $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 '<div class="alert alert-success mt-3">';
|
||||||
|
echo 'Orden actualizado correctamente. ';
|
||||||
|
echo "Se recalcularon {$resultado['success']} semanas futuras.";
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// Inicializar sortable
|
||||||
|
$(function() {
|
||||||
|
$("#sortableList").sortable({
|
||||||
|
placeholder: "ui-state-highlight",
|
||||||
|
update: function(event, ui) {
|
||||||
|
actualizarNumeros();
|
||||||
|
}
|
||||||
|
}).disableSelection();
|
||||||
|
});
|
||||||
|
|
||||||
|
function actualizarNumeros() {
|
||||||
|
$("#sortableList li").each(function(index) {
|
||||||
|
$(this).find('.badge').text('☰ ' + (index + 1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function invertirOrden() {
|
||||||
|
var items = $("#sortableList li").get().reverse();
|
||||||
|
$("#sortableList").append(items);
|
||||||
|
actualizarNumeros();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seleccionar/deseleccionar todos
|
||||||
|
document.getElementById('selectAll').addEventListener('change', function() {
|
||||||
|
const checkboxes = document.querySelectorAll('.user-checkbox');
|
||||||
|
checkboxes.forEach(checkbox => checkbox.checked = this.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectAllUsers() {
|
||||||
|
document.querySelectorAll('.user-checkbox').forEach(checkbox => checkbox.checked = true);
|
||||||
|
document.getElementById('selectAll').checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deselectAllUsers() {
|
||||||
|
document.querySelectorAll('.user-checkbox').forEach(checkbox => checkbox.checked = false);
|
||||||
|
document.getElementById('selectAll').checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validar que al menos un usuario esté seleccionado
|
||||||
|
document.getElementById('asignacionMasivaForm').addEventListener('submit', function(e) {
|
||||||
|
const selectedUsers = document.querySelectorAll('.user-checkbox:checked');
|
||||||
|
if (selectedUsers.length === 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('Debes seleccionar al menos un ayudante');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Establecer fecha por defecto al domingo actual
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const hoy = new Date();
|
||||||
|
const diaSemana = hoy.getDay(); // 0 = domingo, 6 = sábado
|
||||||
|
const diasParaDomingo = diaSemana === 0 ? 0 : (7 - diaSemana);
|
||||||
|
const domingoActual = new Date(hoy);
|
||||||
|
domingoActual.setDate(hoy.getDate() - diasParaDomingo);
|
||||||
|
|
||||||
|
const fechaInput = document.querySelector('input[name="semana_inicio"]');
|
||||||
|
if (fechaInput) {
|
||||||
|
fechaInput.value = domingoActual.toISOString().split('T')[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
139
public/admin/horarios.php
Executable file
139
public/admin/horarios.php
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__, 2));
|
||||||
|
}
|
||||||
|
require_once BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/Auth.php';
|
||||||
|
require_once BASE_PATH . '/src/DiasHorarios.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Horarios - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include BASE_PATH . '/public/partials/navbar.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2 class="mb-4">Configuración de Horarios</h2>
|
||||||
|
|
||||||
|
<?php if ($message): ?>
|
||||||
|
<div class="alert alert-<?= $messageType ?>"><?= htmlspecialchars($message) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" id="horariosForm">
|
||||||
|
<input type="hidden" name="dia" id="selectedDia" value="">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Día</th>
|
||||||
|
<th>Hora Apertura</th>
|
||||||
|
<th>Hora Cierre</th>
|
||||||
|
<th>Activo</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($horarios as $h): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?= $diasNombres[$h['dia_semana']] ?? $h['dia_semana'] ?></strong></td>
|
||||||
|
<td>
|
||||||
|
<input type="time" class="form-control form-control-sm"
|
||||||
|
name="hora_apertura_<?= $h['dia_semana'] ?>"
|
||||||
|
value="<?= $h['hora_apertura'] ?>" required>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="time" class="form-control form-control-sm"
|
||||||
|
name="hora_cierre_<?= $h['dia_semana'] ?>"
|
||||||
|
value="<?= $h['hora_cierre'] ?>" required>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
name="activo_<?= $h['dia_semana'] ?>"
|
||||||
|
id="activo_<?= $h['dia_semana'] ?>"
|
||||||
|
<?= $h['activo'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary"
|
||||||
|
onclick="guardarHorario('<?= $h['dia_semana'] ?>')">
|
||||||
|
Guardar
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4 shadow-sm">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h5 class="mb-0">Información</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li>Los días marcados como "Activo" aparecerán en los turnos.</li>
|
||||||
|
<li>Los horarios pueden modificarse en cualquier momento.</li>
|
||||||
|
<li>La hora de cierre debe ser posterior a la hora de apertura.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function guardarHorario(dia) {
|
||||||
|
document.getElementById('selectedDia').value = dia;
|
||||||
|
document.getElementById('horariosForm').submit();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
120
public/admin/index.php
Executable file
120
public/admin/index.php
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__, 2));
|
||||||
|
}
|
||||||
|
require_once BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/Auth.php';
|
||||||
|
require_once BASE_PATH . '/src/User.php';
|
||||||
|
require_once BASE_PATH . '/src/DiasHorarios.php';
|
||||||
|
require_once BASE_PATH . '/src/Asignacion.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Dashboard - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include BASE_PATH . '/public/partials/navbar.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2 class="mb-4">Panel de Administración</h2>
|
||||||
|
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-center shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary"><?= $totalUsuarios ?></h5>
|
||||||
|
<p class="card-text">Total Usuarios</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-center shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-success"><?= $totalAyudantes ?></h5>
|
||||||
|
<p class="card-text">Ayudantes Activos</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-center shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-warning"><?= $totalHorarios ?></h5>
|
||||||
|
<p class="card-text">Días Configurados</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-center shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-info">
|
||||||
|
<?= $asignacionActual ? htmlspecialchars($asignacionActual['nombre']) : 'Sin asignar' ?>
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">Turno Actual</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0">Acciones Rápidas</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
|
<a href="/admin/usuarios.php" class="btn btn-outline-primary">Gestionar Usuarios</a>
|
||||||
|
<a href="/admin/horarios.php" class="btn btn-outline-primary">Configurar Horarios</a>
|
||||||
|
<a href="/admin/asignaciones.php" class="btn btn-outline-primary">Ver Asignaciones</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5 class="mb-0">Información del Sistema</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Semana actual:</span>
|
||||||
|
<strong><?= date('W') ?></strong>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Inicio semana:</span>
|
||||||
|
<strong><?= date('d/m/Y', strtotime('monday this week')) ?></strong>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Rol actual:</span>
|
||||||
|
<strong><?= ucfirst($_SESSION['user_rol']) ?></strong>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
112
public/admin/logs.php
Executable file
112
public/admin/logs.php
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__, 2));
|
||||||
|
}
|
||||||
|
$config = require_once BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/Auth.php';
|
||||||
|
require_once BASE_PATH . '/src/Database.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Logs - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.log-entry {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.log-entry:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
.log-error { color: #dc3545; }
|
||||||
|
.log-warning { color: #ffc107; }
|
||||||
|
.log-notice { color: #0dcaf0; }
|
||||||
|
.log-info { color: #6c757d; }
|
||||||
|
.log-container {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include BASE_PATH . '/public/partials/navbar.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Logs del Sistema</h2>
|
||||||
|
<div>
|
||||||
|
<a href="?clear=1" class="btn btn-outline-danger btn-sm" onclick="return confirm('¿Vaciar todos los logs?')">Vaciar Logs</a>
|
||||||
|
<a href="<?= rtrim($config['site_url'] ?? '', '/') ?>/logs/error.log" target="_blank" class="btn btn-outline-secondary btn-sm">Ver Archivo Completo</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if (isset($_GET['clear']) && $_GET['clear'] == '1') {
|
||||||
|
file_put_contents($logFile, '');
|
||||||
|
header('Location: logs.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="log-container">
|
||||||
|
<?php if (empty($logs)): ?>
|
||||||
|
<div class="p-3 text-muted">No hay logs registrados.</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($logs as $log): ?>
|
||||||
|
<?php
|
||||||
|
$class = 'log-info';
|
||||||
|
if (stripos($log, 'ERROR') !== false || stripos($log, 'FATAL') !== false) {
|
||||||
|
$class = 'log-error';
|
||||||
|
} elseif (stripos($log, 'WARNING') !== false) {
|
||||||
|
$class = 'log-warning';
|
||||||
|
} elseif (stripos($log, 'NOTICE') !== false) {
|
||||||
|
$class = 'log-notice';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="log-entry <?= $class ?>"><?= htmlspecialchars($log) ?></div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4 shadow-sm">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h5 class="mb-0">Información</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li>Los errores se guardan automáticamente en: <code>logs/error.log</code></li>
|
||||||
|
<li>Se registran errores de PHP, excepciones y errores fatales.</li>
|
||||||
|
<li>La configuración está en: <code>config/error_logging.php</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
223
public/admin/usuarios.php
Executable file
223
public/admin/usuarios.php
Executable file
@@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__, 2));
|
||||||
|
}
|
||||||
|
require_once BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/Auth.php';
|
||||||
|
require_once BASE_PATH . '/src/User.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Usuarios - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include BASE_PATH . '/public/partials/navbar.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2>Gestión de Usuarios</h2>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#userModal" onclick="resetForm()">
|
||||||
|
+ Nuevo Usuario
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($message): ?>
|
||||||
|
<div class="alert alert-<?= $messageType ?>"><?= htmlspecialchars($message) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Rol</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($users as $u): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($u['username'] ?? '-') ?></td>
|
||||||
|
<td><?= htmlspecialchars($u['nombre']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($u['email']) ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $u['rol'] === 'admin' ? 'danger' : 'primary' ?>">
|
||||||
|
<?= ucfirst($u['rol']) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $u['activo'] ? 'success' : 'secondary' ?>">
|
||||||
|
<?= $u['activo'] ? 'Activo' : 'Inactivo' ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#userModal"
|
||||||
|
onclick="editUser(<?= $u['id'] ?>, '<?= htmlspecialchars($u['nombre']) ?>', '<?= htmlspecialchars($u['email']) ?>', '<?= htmlspecialchars($u['username'] ?? '') ?>', '<?= $u['rol'] ?>')">
|
||||||
|
Editar
|
||||||
|
</button>
|
||||||
|
<?php if ($u['id'] != $_SESSION['user_id']): ?>
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="action" value="toggle">
|
||||||
|
<input type="hidden" name="id" value="<?= $u['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-<?= $u['activo'] ? 'outline-warning' : 'outline-success' ?>">
|
||||||
|
<?= $u['activo'] ? 'Desactivar' : 'Activar' ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="userModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalTitle">Nuevo Usuario</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" id="userForm">
|
||||||
|
<input type="hidden" name="action" value="create" id="formAction">
|
||||||
|
<input type="hidden" name="id" value="" id="userId">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nombre" class="form-label">Nombre Completo</label>
|
||||||
|
<input type="text" class="form-control" id="nombre" name="nombre" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" class="form-control" id="username" name="username" placeholder="Opcional">
|
||||||
|
<small class="text-muted">Para iniciar sesión con nombre de usuario</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Contraseña</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password">
|
||||||
|
<small class="text-muted">Dejar en blanco para mantener la actual</small>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="rol" class="form-label">Rol</label>
|
||||||
|
<select class="form-select" id="rol" name="rol">
|
||||||
|
<option value="ayudante">Ayudante</option>
|
||||||
|
<option value="admin">Administrador</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function resetForm() {
|
||||||
|
document.getElementById('modalTitle').textContent = 'Nuevo Usuario';
|
||||||
|
document.getElementById('formAction').value = 'create';
|
||||||
|
document.getElementById('userId').value = '';
|
||||||
|
document.getElementById('userForm').reset();
|
||||||
|
document.getElementById('password').required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function editUser(id, nombre, email, username, rol) {
|
||||||
|
document.getElementById('modalTitle').textContent = 'Editar Usuario';
|
||||||
|
document.getElementById('formAction').value = 'update';
|
||||||
|
document.getElementById('userId').value = id;
|
||||||
|
document.getElementById('nombre').value = nombre;
|
||||||
|
document.getElementById('email').value = email;
|
||||||
|
document.getElementById('username').value = username;
|
||||||
|
document.getElementById('rol').value = rol;
|
||||||
|
document.getElementById('password').required = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
327
public/admin/webhook.php
Executable file
327
public/admin/webhook.php
Executable file
@@ -0,0 +1,327 @@
|
|||||||
|
<?php
|
||||||
|
if (!defined('BASE_PATH')) {
|
||||||
|
define('BASE_PATH', dirname(__DIR__, 2));
|
||||||
|
}
|
||||||
|
$config = require BASE_PATH . '/config/config.php';
|
||||||
|
require_once BASE_PATH . '/src/Auth.php';
|
||||||
|
require_once BASE_PATH . '/bot/TelegramBot.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?= $pageTitle ?> - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php include BASE_PATH . '/public/partials/navbar.php'; ?>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2 class="mb-4">🤖 Administración del Bot de Telegram</h2>
|
||||||
|
|
||||||
|
<?php if ($message): ?>
|
||||||
|
<div class="alert alert-<?= $messageType ?>"><?= nl2br(htmlspecialchars($message)) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Información del Bot -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0">Información del Bot</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($botInfo): ?>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p><strong>Nombre:</strong> <?= htmlspecialchars($botInfo['first_name']) ?></p>
|
||||||
|
<p><strong>Username:</strong> @<?= htmlspecialchars($botInfo['username']) ?></p>
|
||||||
|
<p><strong>ID:</strong> <?= $botInfo['id'] ?></p>
|
||||||
|
<p><strong>Estado:</strong>
|
||||||
|
<span class="badge bg-success">Conectado</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-end">
|
||||||
|
<span class="text-muted">Token configurado correctamente</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Error:</strong> No se pudo conectar con el bot.
|
||||||
|
Verifica que el TELEGRAM_BOT_TOKEN esté configurado correctamente en .env
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estado del Webhook -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5 class="mb-0">Estado del Webhook</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($webhookInfo && isset($webhookInfo['ok']) && $webhookInfo['ok']): ?>
|
||||||
|
<?php if (!empty($webhookInfo['result']['url'])): ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<strong>✅ Webhook activo</strong>
|
||||||
|
<p class="mb-0 mt-2">
|
||||||
|
<strong>URL:</strong> <?= htmlspecialchars($webhookInfo['result']['url']) ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul class="list-group mb-3">
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Última actualización:</span>
|
||||||
|
<span><?= date('d/m/Y H:i:s', $webhookInfo['result']['last_synchronization_unix_time'] ?? 0) ?></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>IP permitida:</span>
|
||||||
|
<span><?= $webhookInfo['result']['ip_address'] ?? 'No disponible' ?></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Errores acumulados:</span>
|
||||||
|
<span><?= $webhookInfo['result']['last_error_date'] ?? 0 ?></span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex justify-content-between">
|
||||||
|
<span>Actualizaciones pendientes:</span>
|
||||||
|
<span><?= $webhookInfo['result']['pending_update_count'] ?? 0 ?></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>⚠️ Webhook no configurado</strong>
|
||||||
|
<p class="mb-0 mt-2">No hay webhook configurado para este bot.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<strong>Error:</strong> No se pudo obtener información del webhook
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Acciones -->
|
||||||
|
<div class="d-flex gap-2 mt-3">
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="action" value="verificar">
|
||||||
|
<button type="submit" class="btn btn-outline-primary">
|
||||||
|
🔄 Verificar Estado
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php if ($webhookInfo && isset($webhookInfo['result']['url']) && !empty($webhookInfo['result']['url'])): ?>
|
||||||
|
<form method="POST" class="d-inline" onsubmit="return confirm('¿Estás seguro de eliminar el webhook?');">
|
||||||
|
<input type="hidden" name="action" value="borrar">
|
||||||
|
<button type="submit" class="btn btn-outline-danger">
|
||||||
|
🗑️ Eliminar Webhook
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Configurar Webhook -->
|
||||||
|
<div class="card shadow-sm mb-4">
|
||||||
|
<div class="card-header bg-success text-white">
|
||||||
|
<h5 class="mb-0">⚙️ Configurar Webhook</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php
|
||||||
|
// Construir URL sugerida usando SITE_URL del .env
|
||||||
|
$urlSugerida = ($config['site_url'] ?? '') . '/bot/webhook.php';
|
||||||
|
$urlActual = $webhookInfo['result']['url'] ?? '';
|
||||||
|
$urlParaInput = $urlActual ?: $urlSugerida;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="configurar">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="webhook_url" class="form-label">URL del Webhook:</label>
|
||||||
|
<input type="url" class="form-control" id="webhook_url" name="webhook_url"
|
||||||
|
value="<?= htmlspecialchars($urlParaInput) ?>"
|
||||||
|
placeholder="https://tu-dominio.com/bot/webhook.php" required>
|
||||||
|
<?php if (!empty($config['site_url'])): ?>
|
||||||
|
<div class="form-text">
|
||||||
|
URL sugerida basada en SITE_URL: <code><?= htmlspecialchars($config['site_url']) ?></code>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info mb-3">
|
||||||
|
<strong>📝 Instrucciones:</strong>
|
||||||
|
<ol class="mb-0">
|
||||||
|
<li>Asegúrate de que la URL sea accesible públicamente (no localhost)</li>
|
||||||
|
<li>El dominio debe tener certificado SSL (HTTPS)</li>
|
||||||
|
<li>Ejemplo de URL: <code>https://contenedor-test.local:82/bot/webhook.php</code></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
✅ Configurar Webhook
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comandos del Bot -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-secondary text-white">
|
||||||
|
<h5 class="mb-0">📋 Comandos Disponibles</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Comando</th>
|
||||||
|
<th>Descripción</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>/start</code> o <code>/menu</code></td>
|
||||||
|
<td>Muestra el menú interactivo con botones</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/turnos</code></td>
|
||||||
|
<td>Muestra la tabla completa de asignaciones</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/semana</code> o <code>hoy</code></td>
|
||||||
|
<td>Muestra quién tiene turno esta semana</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/ayudantes</code></td>
|
||||||
|
<td>Lista de todos los ayudantes</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>[Nombre]</code></td>
|
||||||
|
<td>Busca los turnos de un ayudante específico</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- URLs de Referencia -->
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<h5 class="mb-0">🔗 URLs de Referencia</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Webhook:</strong><br>
|
||||||
|
<code class="text-primary"><?= htmlspecialchars($config['site_url'] ?? 'https://tu-dominio.com') ?>/bot/webhook.php</code>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<strong>Test del bot:</strong><br>
|
||||||
|
<code class="text-primary"><?= $site_url ?? 'https://tu-dominio.com' ?>/bot/test.php</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
231
public/ayudante.php
Executable file
231
public/ayudante.php
Executable file
@@ -0,0 +1,231 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../src/Auth.php';
|
||||||
|
require_once __DIR__ . '/../src/User.php';
|
||||||
|
require_once __DIR__ . '/../src/DiasHorarios.php';
|
||||||
|
require_once __DIR__ . '/../src/Asignacion.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->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
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mis Turnos - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-dark bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/ayudante.php">Contenedor Ibiza</a>
|
||||||
|
<span class="navbar-text">
|
||||||
|
Hola, <?= htmlspecialchars($user['nombre']) ?>
|
||||||
|
</span>
|
||||||
|
<a href="/logout.php" class="btn btn-outline-light btn-sm">Cerrar Sesión</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2 class="mb-4">Mis Turnos</h2>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Verificar si tiene turno esta semana (hoy está entre domingo y viernes de la semana 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
|
||||||
|
$viernesActual = clone $domingoActual;
|
||||||
|
$viernesActual->modify('+5 days');
|
||||||
|
|
||||||
|
$asignacionEstaSemana = $asignacionModel->getAsignacionPorSemana($domingoActual->format('Y-m-d'));
|
||||||
|
$tengoTurnoEstaSemana = $asignacionEstaSemana && $asignacionEstaSemana['id'] == $user['id'];
|
||||||
|
|
||||||
|
if ($tengoTurnoEstaSemana):
|
||||||
|
?>
|
||||||
|
<div class="alert alert-success mb-4">
|
||||||
|
<strong>¡Tienes turno esta semana!</strong><br>
|
||||||
|
Del <?= date('d/m/y', strtotime($asignacionEstaSemana['semana_inicio'])) ?>
|
||||||
|
al <?= date('d/m/y', strtotime($asignacionEstaSemana['semana_fin'])) ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($asignacionEstaSemana): ?>
|
||||||
|
<div class="alert alert-secondary mb-4">
|
||||||
|
<strong>Turno esta semana:</strong> <?= htmlspecialchars($asignacionEstaSemana['nombre']) ?><br>
|
||||||
|
<?php if (!empty($misAsignacionesFuturas)): ?>
|
||||||
|
Tu próximo turno: <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['inicio'])) ?>
|
||||||
|
al <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['fin'])) ?>
|
||||||
|
<?php else: ?>
|
||||||
|
Tu próximo turno será en las próximas semanas.
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif (!empty($misAsignacionesFuturas)): ?>
|
||||||
|
<div class="alert alert-info mb-4">
|
||||||
|
<strong>Próximo turno:</strong><br>
|
||||||
|
Del <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['inicio'])) ?>
|
||||||
|
al <?= date('d/m/y', strtotime($misAsignacionesFuturas[0]['semana']['fin'])) ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="alert alert-warning mb-4">
|
||||||
|
No hay turnos asignados para las próximas semanas.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0">Horarios de Apertura del Contenedor</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Día</th>
|
||||||
|
<th>Hora Apertura</th>
|
||||||
|
<th>Hora Cierre</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; ?>
|
||||||
|
</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>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($semanasFuturas as $index => $semana): ?>
|
||||||
|
<tr class="<?= !empty($semana['asignaciones']) && in_array($user['id'], array_column($semana['asignaciones'], 'id')) ? 'table-success' : '' ?>">
|
||||||
|
<td>
|
||||||
|
<strong>Semana <?= date('d/m/y', strtotime($semana['inicio'])) ?></strong>
|
||||||
|
<?php if ($index === 0): ?>
|
||||||
|
<span class="badge bg-primary ms-1">Actual</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?= 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; ?>
|
||||||
|
</div>
|
||||||
|
<?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>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mt-4 shadow-sm">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5 class="mb-0">Información</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li>Los turnos se asignan de forma rotativa semanalmente.</li>
|
||||||
|
<li>Cada semana inicia en lunes y termina en domingo.</li>
|
||||||
|
<li>Recuerda estar atento a tu turno para abrir y cerrar el contenedor.</li>
|
||||||
|
<li>Las filas en verde indican tus turnos asignados.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
public/index.php
Executable file
16
public/index.php
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../src/Auth.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
|
||||||
|
if (!$auth->isLoggedIn()) {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($auth->isAdmin()) {
|
||||||
|
header('Location: /admin/index.php');
|
||||||
|
} else {
|
||||||
|
header('Location: /ayudante.php');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
82
public/login.php
Executable file
82
public/login.php
Executable file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/Auth.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
|
||||||
|
if ($auth->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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Login - Contenedor Ibiza</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light d-flex align-items-center justify-content-center" style="min-height: 100vh;">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-body p-5">
|
||||||
|
<h3 class="text-center mb-4">Contenedor Ibiza</h3>
|
||||||
|
<h5 class="text-center text-muted mb-4">Iniciar Sesión</h5>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="login" class="form-label">Usuario o Email</label>
|
||||||
|
<input type="text" class="form-control" id="login" name="login"
|
||||||
|
value="<?= htmlspecialchars($loginInput) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="password" class="form-label">Contraseña</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100 py-2">Ingresar</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
public/logout.php
Executable file
8
public/logout.php
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../src/Auth.php';
|
||||||
|
|
||||||
|
$auth = new Auth();
|
||||||
|
$auth->logout();
|
||||||
|
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
3
public/logs.php
Executable file
3
public/logs.php
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
header('Location: /admin/logs.php');
|
||||||
|
exit;
|
||||||
48
public/partials/navbar.php
Executable file
48
public/partials/navbar.php
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
require_once '/var/www/html/contenedor/src/Auth.php';
|
||||||
|
$auth = new Auth();
|
||||||
|
$user = $auth->getCurrentUser();
|
||||||
|
$currentPage = $currentPage ?? '';
|
||||||
|
?>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="/admin/index.php">Contenedor Ibiza</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $currentPage === 'dashboard' ? 'active' : '' ?>" href="/admin/index.php">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $currentPage === 'usuarios' ? 'active' : '' ?>" href="/admin/usuarios.php">Usuarios</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $currentPage === 'horarios' ? 'active' : '' ?>" href="/admin/horarios.php">Horarios</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $currentPage === 'asignaciones' ? 'active' : '' ?>" href="/admin/asignaciones.php">Asignaciones</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $currentPage === 'logs' ? 'active' : '' ?>" href="/admin/logs.php">Logs</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $currentPage === 'webhook' ? 'active' : '' ?>" href="/admin/webhook.php">🤖 Bot</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||||
|
<?= htmlspecialchars($user['nombre'] ?? 'Usuario') ?>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><span class="dropdown-item-text"><strong>Rol:</strong> <?= htmlspecialchars(ucfirst($user['rol'] ?? '')) ?></span></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="/logout.php">Cerrar Sesión</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
24
scripts/rotar.php
Executable file
24
scripts/rotar.php
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../src/RotacionTurnos.php';
|
||||||
|
|
||||||
|
$rotacion = new RotacionTurnos();
|
||||||
|
$resultado = $rotacion->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);
|
||||||
|
}
|
||||||
32
scripts/rotar_automatico.php
Executable file
32
scripts/rotar_automatico.php
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../config/config.php';
|
||||||
|
require_once __DIR__ . '/../src/Asignacion.php';
|
||||||
|
|
||||||
|
$asignacion = new Asignacion();
|
||||||
|
|
||||||
|
echo "=== Sistema de Rotación Automática ===\n\n";
|
||||||
|
|
||||||
|
// 1. Inicializar orden de rotación si no existe
|
||||||
|
echo "1. Verificando orden de rotación...\n";
|
||||||
|
$resultado = $asignacion->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";
|
||||||
0
sessions/sess_00bl6h856q88l76ba1voh6fjar
Executable file
0
sessions/sess_00bl6h856q88l76ba1voh6fjar
Executable file
0
sessions/sess_1iq2ih0bf7i2n4dh1dotp5jbsl
Executable file
0
sessions/sess_1iq2ih0bf7i2n4dh1dotp5jbsl
Executable file
0
sessions/sess_1qorjhdal1pmvrcoo0bnfcfgk8
Executable file
0
sessions/sess_1qorjhdal1pmvrcoo0bnfcfgk8
Executable file
0
sessions/sess_1th6bp3uek4rm6u7ua9ui2pb47
Executable file
0
sessions/sess_1th6bp3uek4rm6u7ua9ui2pb47
Executable file
0
sessions/sess_21b815bksed3hj8urv2ru75b92
Executable file
0
sessions/sess_21b815bksed3hj8urv2ru75b92
Executable file
0
sessions/sess_2bbciohucqm3vkt1i50932hcis
Executable file
0
sessions/sess_2bbciohucqm3vkt1i50932hcis
Executable file
1
sessions/sess_2n5h5gch3nj38il7o244eorbom
Executable file
1
sessions/sess_2n5h5gch3nj38il7o244eorbom
Executable file
@@ -0,0 +1 @@
|
|||||||
|
user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1;
|
||||||
0
sessions/sess_35u20e7gas4k0v2e4kf1n52194
Executable file
0
sessions/sess_35u20e7gas4k0v2e4kf1n52194
Executable file
0
sessions/sess_43dk4j9tbdh3lc5pl1li935o7o
Executable file
0
sessions/sess_43dk4j9tbdh3lc5pl1li935o7o
Executable file
0
sessions/sess_4b8ji012s30kabi62jskd7vh1b
Executable file
0
sessions/sess_4b8ji012s30kabi62jskd7vh1b
Executable file
0
sessions/sess_4ofnavtltl5kam8gscb5m4ob09
Executable file
0
sessions/sess_4ofnavtltl5kam8gscb5m4ob09
Executable file
0
sessions/sess_4uo75t4lsoauaet8u2b9lcdreg
Executable file
0
sessions/sess_4uo75t4lsoauaet8u2b9lcdreg
Executable file
0
sessions/sess_52n4t1en33kk4mmhm8d8hi425d
Executable file
0
sessions/sess_52n4t1en33kk4mmhm8d8hi425d
Executable file
0
sessions/sess_55hetos1v8erfdojstagrok3cg
Executable file
0
sessions/sess_55hetos1v8erfdojstagrok3cg
Executable file
0
sessions/sess_55igfik2tqlp9fv73n32eoevuk
Executable file
0
sessions/sess_55igfik2tqlp9fv73n32eoevuk
Executable file
0
sessions/sess_569qght58r0d0jjcoo9nb6rat2
Executable file
0
sessions/sess_569qght58r0d0jjcoo9nb6rat2
Executable file
0
sessions/sess_6n6c0fhi7qe1esqokoo997cdul
Executable file
0
sessions/sess_6n6c0fhi7qe1esqokoo997cdul
Executable file
0
sessions/sess_6q7blq7h172qk2spnlsk1qcfdi
Executable file
0
sessions/sess_6q7blq7h172qk2spnlsk1qcfdi
Executable file
0
sessions/sess_6qmvlpm64niup8urblt4qulu6g
Executable file
0
sessions/sess_6qmvlpm64niup8urblt4qulu6g
Executable file
0
sessions/sess_70jnhjdapqi3so3a7tlivk12ig
Executable file
0
sessions/sess_70jnhjdapqi3so3a7tlivk12ig
Executable file
0
sessions/sess_75pk2rjt9qlfraehnlfifut2c1
Executable file
0
sessions/sess_75pk2rjt9qlfraehnlfifut2c1
Executable file
0
sessions/sess_7ik362psdh2m9s6ocpq0717aka
Executable file
0
sessions/sess_7ik362psdh2m9s6ocpq0717aka
Executable file
0
sessions/sess_7k50rk5l31lir0sfkm37ja5e5a
Executable file
0
sessions/sess_7k50rk5l31lir0sfkm37ja5e5a
Executable file
0
sessions/sess_7k8b343ivebeqtnkva3iqb5p3o
Executable file
0
sessions/sess_7k8b343ivebeqtnkva3iqb5p3o
Executable file
0
sessions/sess_7m8uq06462bsl8f91b69vls8kc
Executable file
0
sessions/sess_7m8uq06462bsl8f91b69vls8kc
Executable file
0
sessions/sess_7r7o6e9pa84074u0ljacmnrkor
Executable file
0
sessions/sess_7r7o6e9pa84074u0ljacmnrkor
Executable file
0
sessions/sess_8rpncmmfl40cu757v33kvet87o
Executable file
0
sessions/sess_8rpncmmfl40cu757v33kvet87o
Executable file
0
sessions/sess_8v3ajfdid4fhhak4ivrgaepf1g
Executable file
0
sessions/sess_8v3ajfdid4fhhak4ivrgaepf1g
Executable file
0
sessions/sess_95saljdgo1bdbpu51aod75eivv
Executable file
0
sessions/sess_95saljdgo1bdbpu51aod75eivv
Executable file
0
sessions/sess_9a79chd621vqopm2qkt61ot3kt
Executable file
0
sessions/sess_9a79chd621vqopm2qkt61ot3kt
Executable file
0
sessions/sess_9eflrgl7j1hkre50l8o9un31l0
Executable file
0
sessions/sess_9eflrgl7j1hkre50l8o9un31l0
Executable file
1
sessions/sess_9h5m8merop5algdv2s495t1j4k
Executable file
1
sessions/sess_9h5m8merop5algdv2s495t1j4k
Executable file
@@ -0,0 +1 @@
|
|||||||
|
user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1;
|
||||||
0
sessions/sess_9nhl79kj4di41dkb2f4hk24dke
Executable file
0
sessions/sess_9nhl79kj4di41dkb2f4hk24dke
Executable file
1
sessions/sess_9ojk9le1om4qgm16k51bp5rjq2
Executable file
1
sessions/sess_9ojk9le1om4qgm16k51bp5rjq2
Executable file
@@ -0,0 +1 @@
|
|||||||
|
user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1;
|
||||||
0
sessions/sess_9ot0ujn4rfd1kobtke6tn4o6oq
Executable file
0
sessions/sess_9ot0ujn4rfd1kobtke6tn4o6oq
Executable file
0
sessions/sess_9raj925f1d6kkcqnpkuk6mv0rv
Executable file
0
sessions/sess_9raj925f1d6kkcqnpkuk6mv0rv
Executable file
0
sessions/sess_9sailnre4vnuq0bguqlq6jf4a8
Executable file
0
sessions/sess_9sailnre4vnuq0bguqlq6jf4a8
Executable file
0
sessions/sess_aclc0fjdoms8tr9mciqjv5r94l
Executable file
0
sessions/sess_aclc0fjdoms8tr9mciqjv5r94l
Executable file
0
sessions/sess_af76mblkugo7gtm61i9oqi6mlm
Executable file
0
sessions/sess_af76mblkugo7gtm61i9oqi6mlm
Executable file
0
sessions/sess_al5o42ung8vr5kaigoaul94fjv
Executable file
0
sessions/sess_al5o42ung8vr5kaigoaul94fjv
Executable file
0
sessions/sess_aqu1vk69dr4svpff9r6n45ngn8
Executable file
0
sessions/sess_aqu1vk69dr4svpff9r6n45ngn8
Executable file
0
sessions/sess_b13295vdb1mft1u3jl14981jct
Executable file
0
sessions/sess_b13295vdb1mft1u3jl14981jct
Executable file
0
sessions/sess_b6ar2325dsbpc152hic8bo2hbt
Executable file
0
sessions/sess_b6ar2325dsbpc152hic8bo2hbt
Executable file
0
sessions/sess_bjojn9rneht9ics9b9f5jf97go
Executable file
0
sessions/sess_bjojn9rneht9ics9b9f5jf97go
Executable file
0
sessions/sess_bvo8li65tst17fqfigu96crbda
Executable file
0
sessions/sess_bvo8li65tst17fqfigu96crbda
Executable file
0
sessions/sess_cgkh5mvaislhl4mshdc995h32u
Executable file
0
sessions/sess_cgkh5mvaislhl4mshdc995h32u
Executable file
0
sessions/sess_cimh1u3spbbspdtgs711ubihg4
Executable file
0
sessions/sess_cimh1u3spbbspdtgs711ubihg4
Executable file
0
sessions/sess_cmbf53uqvcrg9fv6elrhnnvnm1
Executable file
0
sessions/sess_cmbf53uqvcrg9fv6elrhnnvnm1
Executable file
0
sessions/sess_cs1qu9cmtus6qjr0lihvpbp545
Executable file
0
sessions/sess_cs1qu9cmtus6qjr0lihvpbp545
Executable file
1
sessions/sess_cuie31c5163ttvmoj2g6l03bda
Executable file
1
sessions/sess_cuie31c5163ttvmoj2g6l03bda
Executable file
@@ -0,0 +1 @@
|
|||||||
|
user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1;
|
||||||
0
sessions/sess_d12vv5n3onbq6erl7koqofcfh3
Executable file
0
sessions/sess_d12vv5n3onbq6erl7koqofcfh3
Executable file
0
sessions/sess_d3i9h8i8kdqrgm4d44o2f2t71h
Executable file
0
sessions/sess_d3i9h8i8kdqrgm4d44o2f2t71h
Executable file
0
sessions/sess_d9bhpnd968qtjp2bun2fcbekfg
Executable file
0
sessions/sess_d9bhpnd968qtjp2bun2fcbekfg
Executable file
0
sessions/sess_dcdm3nv9jeuq4p201afi8kseua
Executable file
0
sessions/sess_dcdm3nv9jeuq4p201afi8kseua
Executable file
0
sessions/sess_dcem3cgs9ll4ap0cviuj8f9q6p
Executable file
0
sessions/sess_dcem3cgs9ll4ap0cviuj8f9q6p
Executable file
0
sessions/sess_dddjo346pr4dgmijkpk81mai2l
Executable file
0
sessions/sess_dddjo346pr4dgmijkpk81mai2l
Executable file
0
sessions/sess_dr2r20k9h23hrsq5i1f26eijmb
Executable file
0
sessions/sess_dr2r20k9h23hrsq5i1f26eijmb
Executable file
0
sessions/sess_dsnfbkkoom2dt08gk3oibg874u
Executable file
0
sessions/sess_dsnfbkkoom2dt08gk3oibg874u
Executable file
0
sessions/sess_e4uv39asdligponb8p9fgvm7g3
Executable file
0
sessions/sess_e4uv39asdligponb8p9fgvm7g3
Executable file
0
sessions/sess_e9ciopb4n3mtfg840arlprm4qg
Executable file
0
sessions/sess_e9ciopb4n3mtfg840arlprm4qg
Executable file
0
sessions/sess_ei6j6ed6khvrt46hro47fopkjd
Executable file
0
sessions/sess_ei6j6ed6khvrt46hro47fopkjd
Executable file
0
sessions/sess_fbd7nirtqh8bb7muo9456u3t90
Executable file
0
sessions/sess_fbd7nirtqh8bb7muo9456u3t90
Executable file
0
sessions/sess_fk1hpvrhh7d44vjt1jgim51ud2
Executable file
0
sessions/sess_fk1hpvrhh7d44vjt1jgim51ud2
Executable file
0
sessions/sess_fnmbtv5bqi28pbpcrdma395gqg
Executable file
0
sessions/sess_fnmbtv5bqi28pbpcrdma395gqg
Executable file
0
sessions/sess_g0gmbnb8jmk8bpj413tnnmhloo
Executable file
0
sessions/sess_g0gmbnb8jmk8bpj413tnnmhloo
Executable file
0
sessions/sess_gdmkamsrs5tqlns339sc6f0rrr
Executable file
0
sessions/sess_gdmkamsrs5tqlns339sc6f0rrr
Executable file
0
sessions/sess_gq1q96v1umot7ubntbspfee6em
Executable file
0
sessions/sess_gq1q96v1umot7ubntbspfee6em
Executable file
0
sessions/sess_gt9h4bk40n1mgrqkglaeot9fl0
Executable file
0
sessions/sess_gt9h4bk40n1mgrqkglaeot9fl0
Executable file
0
sessions/sess_hq897eqaih8190m9c06bl279vu
Executable file
0
sessions/sess_hq897eqaih8190m9c06bl279vu
Executable file
0
sessions/sess_htp0ti3lumr8t177mertkkhmfa
Executable file
0
sessions/sess_htp0ti3lumr8t177mertkkhmfa
Executable file
0
sessions/sess_i9nbkn73ncsheeferis3si64hn
Executable file
0
sessions/sess_i9nbkn73ncsheeferis3si64hn
Executable file
0
sessions/sess_ip7rvqa024ehfd6ks56tld2fmp
Executable file
0
sessions/sess_ip7rvqa024ehfd6ks56tld2fmp
Executable file
1
sessions/sess_ir1ok0d5p7ndlotvflu2uddhh5
Executable file
1
sessions/sess_ir1ok0d5p7ndlotvflu2uddhh5
Executable file
@@ -0,0 +1 @@
|
|||||||
|
user_id|i:1;user_name|s:13:"Administrador";user_rol|s:5:"admin";logged_in|b:1;
|
||||||
0
sessions/sess_j17rcie1m94g9bvhamnbndt0si
Executable file
0
sessions/sess_j17rcie1m94g9bvhamnbndt0si
Executable file
0
sessions/sess_j4mm6t8o3c31vaoo8ia1mhpje6
Executable file
0
sessions/sess_j4mm6t8o3c31vaoo8ia1mhpje6
Executable file
0
sessions/sess_jq1kt76ehvqs9u6bipc87uf9lq
Executable file
0
sessions/sess_jq1kt76ehvqs9u6bipc87uf9lq
Executable file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user