feat: Implementar rol Coordinador con permisos granulares

- Crear nuevo rol Coordinador con permisos específicos de gestión
- Modificar Auth.php para soportar isCoordinador() y requireCoordinador()
- Actualizar User.php con método getUsuariosGestion() para incluir coordinadores
- Corregir Asignacion.php para que getAyudantesPorOrden() incluya coordinadores
- Crear panel especial para coordinadores en coordinador.php
- Implementar restricciones granulares en usuarios.php
  • Coordinadores no pueden ver/editar/desactivar administradores
  • No pueden crear otros administradores (se convierte a coordinador)
  • Solo pueden gestionar ayudantes y otros coordinadores
- Actualizar navbar para mostrar rol específico con badges
- Mejorar ayudante.php para que coordinadores puedan usar navbar completo
- Añadir secciones especiales de gestión para coordinadores
- Actualizar todos los PDFs y bot de Telegram para incluir coordinadores
- Mantener retrocompatibilidad con usuarios y administradores existentes

Permisos Coordinador:
 Ver/editar usuarios y ayudantes
 Gestionar turnos y orden de rotación
 Generar turnos automáticamente
 Exportar PDFs y usar bot de Telegram
 Acceder a configuración general, logs, webhook
 Administrar otros administradores
This commit is contained in:
nickpons666
2026-01-31 01:54:14 -06:00
parent 8bd34c8ddb
commit 67c4d8173f
4 changed files with 130 additions and 33 deletions

View File

@@ -9,7 +9,7 @@ require_once BASE_PATH . '/src/CSRF.php';
require_once BASE_PATH . '/src/Session.php';
$auth = new Auth();
$auth->requireAdmin();
$auth->requireCoordinador();
$userModel = new User();
$message = '';
@@ -51,13 +51,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['password'] ?? '';
$rol = $_POST['rol'] ?? 'ayudante';
if (empty($nombre) || empty($email)) {
// Obtener usuario actual para verificar su rol
$usuarioActual = $userModel->getById($id);
// Prevenir que coordinadores editen administradores
if ($usuarioActual && $usuarioActual['rol'] === 'admin' && !$auth->isAdmin()) {
$message = 'No tienes permisos para editar administradores';
$messageType = 'danger';
}
// Solo los administradores pueden asignar rol de administrador
elseif ($rol === 'admin' && !$auth->isAdmin()) {
$rol = 'coordinador';
}
elseif (empty($nombre) || empty($email)) {
$message = 'Nombre y email son obligatorios';
$messageType = 'danger';
} elseif ($userModel->usernameExists($username, $id)) {
}
elseif ($userModel->usernameExists($username, $id)) {
$message = 'El username ya está en uso';
$messageType = 'danger';
} else {
}
else {
$userModel->update($id, compact('nombre', 'email', 'username', 'password', 'rol'));
$message = 'Usuario actualizado exitosamente';
$messageType = 'success';
@@ -65,6 +79,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} elseif ($action === 'toggle') {
$id = $_POST['id'] ?? 0;
$user = $userModel->getById($id);
// Prevenir que coordinadores desactiven administradores
if ($user && $user['rol'] === 'admin' && !$auth->isAdmin()) {
$message = 'No tienes permisos para modificar administradores';
$messageType = 'danger';
}
elseif ($user && $user['id'] == Session::get('user_id')) {
$message = 'No puedes desactivar tu propio usuario';
$messageType = 'danger';
}
else {
if ($user) {
if ($user['activo']) {
$userModel->deactivate($id);
@@ -77,8 +102,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
}
}
$users = $userModel->getAll(true);
// Filtrar administradores para coordinadores
if ($auth->isCoordinador() && !$auth->isAdmin()) {
$users = array_filter($users, function($user) {
return $user['rol'] !== 'admin';
});
}
$currentPage = 'usuarios';
$pageTitle = 'Gestión de Usuarios';
?>
@@ -126,8 +159,19 @@ $pageTitle = 'Gestión de Usuarios';
<td><?= htmlspecialchars($u['nombre']) ?></td>
<td><?= htmlspecialchars($u['email']) ?></td>
<td>
<span class="badge bg-<?= $u['rol'] === 'admin' ? 'danger' : 'primary' ?>">
<?= ucfirst($u['rol']) ?>
<?php
$badgeClass = 'primary';
if ($u['rol'] === 'admin') $badgeClass = 'danger';
elseif ($u['rol'] === 'coordinador') $badgeClass = 'success';
?>
<span class="badge bg-<?= $badgeClass ?>">
<?php
if ($u['rol'] === 'coordinador') {
echo '🎯 Coordinador';
} else {
echo ucfirst($u['rol']);
}
?>
</span>
</td>
<td>
@@ -140,7 +184,12 @@ $pageTitle = 'Gestión de Usuarios';
onclick="editUser(<?= $u['id'] ?>, '<?= htmlspecialchars($u['nombre']) ?>', '<?= htmlspecialchars($u['email']) ?>', '<?= htmlspecialchars($u['username'] ?? '') ?>', '<?= $u['rol'] ?>')">
Editar
</button>
<?php if ($u['id'] != Session::get('user_id')): ?>
<?php
// Determinar si puede desactivar este usuario
$puedeDesactivar = ($u['id'] != Session::get('user_id')) && $u['rol'] !== 'admin';
?>
<?php if ($puedeDesactivar): ?>
<form method="POST" class="d-inline">
<?= CSRF::getTokenField() ?>
<input type="hidden" name="action" value="toggle">
@@ -194,7 +243,10 @@ $pageTitle = 'Gestión de Usuarios';
<label for="rol" class="form-label">Rol</label>
<select class="form-select" id="rol" name="rol">
<option value="ayudante">Ayudante</option>
<option value="coordinador">🎯 Coordinador</option>
<?php if ($auth->isAdmin()): ?>
<option value="admin">Administrador</option>
<?php endif; ?>
</select>
</div>
</div>

View File

@@ -15,24 +15,41 @@ $dbName = getenv('DB_NAME') ?: 'No configurado';
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<?php if ($auth->isAdmin()): ?>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'dashboard' ? 'active' : '' ?>" href="/admin/index.php">Dashboard</a>
</li>
<?php else: ?>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'coordinador' ? 'active' : '' ?>" href="/admin/coordinador.php">🎯 Panel</a>
</li>
<?php endif; ?>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'usuarios' ? 'active' : '' ?>" href="/admin/usuarios.php">Usuarios</a>
</li>
<?php if ($auth->isAdmin()): ?>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'horarios' ? 'active' : '' ?>" href="/admin/horarios.php">Horarios</a>
</li>
<?php endif; ?>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'asignaciones' ? 'active' : '' ?>" href="/admin/asignaciones.php">Asignaciones</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'vista-ayudante' ? 'active' : '' ?>" href="/ayudante.php">👥 Vista Ayudante</a>
</li>
<?php if ($auth->isAdmin()): ?>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'webhook' ? 'active' : '' ?>" href="/admin/webhook.php">🤖 Bot</a>
</li>
<li class="nav-item">
<a class="nav-link <?= $currentPage === 'logs' ? 'active' : '' ?>" href="/admin/logs.php">Logs</a>
</li>
<?php endif; ?>
</ul>
<ul class="navbar-nav">
<li class="nav-item">
@@ -44,7 +61,18 @@ $dbName = getenv('DB_NAME') ?: 'No configurado';
</a>
<ul class="dropdown-menu">
<li><span class="dropdown-item-text d-block"><strong>Usuario:</strong> <?= htmlspecialchars($user['nombre'] ?? 'Usuario') ?></span></li>
<li><span class="dropdown-item-text d-block"><strong>Rol:</strong> <?= htmlspecialchars(ucfirst($user['rol'] ?? '')) ?></span></li>
<li><span class="dropdown-item-text d-block"><strong>Rol:</strong>
<?php
$rol = $user['rol'] ?? '';
if ($rol === 'admin') {
echo 'Administrador';
} elseif ($rol === 'coordinador') {
echo '🎯 Coordinador';
} else {
echo htmlspecialchars(ucfirst($rol));
}
?>
</span></li>
<li><span class="dropdown-item-text d-block small text-muted"><strong>DB Host:</strong> <?= htmlspecialchars($dbHost) ?></span></li>
<li><span class="dropdown-item-text d-block small text-muted"><strong>DB Name:</strong> <?= htmlspecialchars($dbName) ?></span></li>
<li><hr class="dropdown-divider"></li>

View File

@@ -221,7 +221,7 @@ public function asignar($userId, $semanaInicio) {
SELECT u.*, ro.orden
FROM users u
LEFT JOIN rotacion_orden ro ON u.id = ro.user_id AND ro.activo = 1
WHERE u.rol = 'ayudante' AND u.activo = 1
WHERE (u.rol = 'ayudante' OR u.rol = 'coordinador') AND u.activo = 1
ORDER BY COALESCE(ro.orden, 999), u.nombre
");
return $stmt->fetchAll();

View File

@@ -43,10 +43,19 @@ class Auth {
return Session::get('user_rol') === 'ayudante';
}
public function isCoordinador() {
return Session::get('user_rol') === 'coordinador';
}
public function hasRole($role) {
return Session::get('user_rol') === $role;
}
public function hasAnyRole($roles) {
$userRole = Session::get('user_rol');
return in_array($userRole, (array)$roles);
}
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
@@ -73,6 +82,14 @@ class Auth {
}
}
public function requireCoordinador($redirectUrl = '/ayudante.php') {
$this->requireAuth($redirectUrl);
if (!$this->isCoordinador() && !$this->isAdmin()) {
header('Location: ' . $redirectUrl);
exit;
}
}
public function requireRole($roles, $redirectUrl = '/') {
$this->requireAuth($redirectUrl);
$userRole = Session::get('user_rol');