Primer subida completa
This commit is contained in:
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>
|
||||
Reference in New Issue
Block a user