Files
contenedor_ibiza/public/admin/asignaciones.php
2026-01-19 15:20:36 -06:00

560 lines
26 KiB
PHP
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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>