Primer subida completa

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

559
public/admin/asignaciones.php Executable file
View 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
View 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
View 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
View 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
View 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
View 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>