Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jpbarbatic/webapp/llms.txt

Use this file to discover all available pages before exploring further.

El panel utiliza autenticación basada en sesiones PHP nativas. No existe ningún framework de autenticación externo: el estado del usuario se almacena en $_SESSION['usuario'] tras verificar las credenciales contra la base de datos con password_verify. Cada controlador protegido delega la verificación de sesión y permisos a includes/backend.php, que actúa como un guard centralizado.

Flujo de login

1

El usuario accede a public/index.php

index.php es el punto de entrada de la aplicación. Inicia la sesión y comprueba si $_SESSION['usuario'] ya existe:
<?php
include '../config.php';

session_start();
if (isset($_SESSION['usuario'])) {
    header('Location: dashboard/');
    exit;
}

require('../html/login.html.php');
unset($_SESSION['mensaje']);
Si la sesión está activa, redirige directamente a dashboard/. Si no lo está, renderiza el formulario de login (html/login.html.php) y limpia cualquier mensaje flash pendiente.
2

El formulario envía un POST a public/login.php

El formulario de login.html.php hace POST a login.php con los campos email y password. Si la petición no es POST, el script termina inmediatamente:
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
    exit;
}
3

login.php busca el usuario por email

Se abre una conexión PDO y se ejecuta una consulta preparada para buscar el usuario:
$db = db_open();

$res = db_query($db, "SELECT * FROM usuarios WHERE email=?", [$_POST['email']]);
El uso de sentencias preparadas garantiza que el valor de $_POST['email'] nunca se interpola directamente en el SQL, previniendo inyección SQL.
4

Verificación de contraseña y creación de sesión

Si se encontró el usuario, se verifica la contraseña con password_verify, que compara el texto plano introducido con el hash almacenado en la base de datos (generado con password_hash):
if (!empty($res) and password_verify($_POST['password'], $res[0]['password'])) {
    session_regenerate_id(true);
    $_SESSION['usuario'] = $res[0];
    $_SESSION['permisos'] = obtener_permisos_rol($db, $_SESSION['usuario']['id_rol']);
    header('Location: '.URL_BASE);
    exit;
}
session_regenerate_id(true) regenera el ID de sesión y destruye la sesión antigua, protegiendo contra ataques de session fixation. A continuación se almacenan el registro completo del usuario y sus permisos en sesión, y se redirige a la página principal.
5

Credenciales incorrectas

Si las credenciales no son válidas, se guarda un mensaje de error en sesión y se redirige de vuelta al formulario:
} else {
    $_SESSION['mensaje'] = 'Usuario y/o contraseña incorrectos';
    header('Location: index.php');
    exit;
}
El mensaje se mostrará como alerta en el formulario de login en la siguiente petición.

Datos en sesión

Tras un login exitoso, la sesión contiene dos claves principales: $_SESSION['usuario'] — Array asociativo con la fila completa del usuario de la base de datos:
CampoTipoDescripción
idintIdentificador único del usuario
nombrestringNombre del usuario
apellidosstringApellidos del usuario
emailstringDirección de correo electrónico (usada como login)
telefonostringNúmero de teléfono de contacto
id_rolintIdentificador del rol asignado al usuario
passwordstringHash bcrypt de la contraseña (no se debe exponer)
$_SESSION['permisos'] — Array de objetos de permiso obtenidos por obtener_permisos_rol($db, $id_rol). Cada elemento tiene la estructura:
CampoTipoDescripción
idintIdentificador del permiso
nombrestringNombre del permiso en formato modulo.accion (p. ej. usuarios.consulta)
La función obtener_permisos_rol realiza un JOIN entre roles_permisos y permisos:
function obtener_permisos_rol($db, $id_rol){
  $sql='select p.id, p.nombre
  from roles_permisos rp
  join permisos p on rp.id_permiso=p.id
  where rp.id_rol=?
  order by p.nombre';

  $res=db_query($db, $sql, [$id_rol]);
  return $res;
}

Guard pattern: backend.php

Todos los controladores protegidos del panel incluyen backend.php como primera instrucción y reciben la conexión PDO a través de su valor de retorno:
$db = require_once('../../includes/backend.php');
El código completo del guard es:
<?php
require(__DIR__.'/../config.php');
require_once('permisos.php');
require_once('utilidades.php');
// Iniciamos las sesiones
session_start();

// Comprobamos si la sesión está inicializada. Si no lo está, redirigimos a login
if (!isset($_SESSION['usuario'])) {
    header('Location: '.URL_BASE);
    exit;
}

require('db_pdo.php');
// Creamos una conexión de base de datos
$db = db_open();

if (!$db) {
    die('Se ha producido un error');
}

$_SESSION['permisos'] = obtener_permisos_rol($db, $_SESSION['usuario']['id_rol']);
if(!$_SESSION['usuario']['id_rol']===1 && (isset($permiso) && !in_array($permiso, array_column($_SESSION['permisos'], 'nombre')))){
  die('No tienes permiso');
}

return $db;
Cada paso cumple una función precisa:
  1. session_start() — reanuda la sesión PHP persistente del usuario.
  2. Comprobación de $_SESSION['usuario'] — si no existe, el usuario no está autenticado y se redirige a URL_BASE (la raíz del panel, que muestra el login).
  3. db_open() — crea la conexión PDO usando las constantes definidas en config.php (DB_TYPE, DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME).
  4. obtener_permisos_rol() — recarga los permisos desde la base de datos en cada petición, garantizando que los cambios de rol surtan efecto de inmediato.
  5. Comprobación de permiso — si el controlador declaró $permiso antes del require_once, verifica que ese permiso está en $_SESSION['permisos']. Si no, termina con die('No tienes permiso').
  6. return $db — devuelve la conexión PDO al controlador invocante.

Control de acceso por rol

Las páginas que requieren un permiso específico declaran la variable $permiso antes de incluir backend.php. De esta forma el guard puede leer su valor y aplicar la comprobación RBAC:
<?php
// public/usuarios/index.php
$permiso = 'usuarios.consulta';

$db = require_once('../../includes/backend.php');
// ...
Los nombres de permiso siguen el convenio modulo.accion. Los permisos existentes en la aplicación incluyen, entre otros:
  • usuarios.consulta
  • usuarios.edicion
  • productos.consulta
  • categorias.consulta
La barra de navegación (html/nav.html.php) también usa este sistema para ocultar los enlaces a módulos para los que el usuario no tiene permisos, mediante la función auxiliar es_visible:
function es_visible($vista){
  return preg_grep("/^$vista\.*/", array_column($_SESSION['permisos'], 'nombre'));
}
<?php if(es_visible('usuarios')):?>
<li class="nav-item">
    <a class="nav-link" href="usuarios/">Usuarios</a>
</li>
<?php endif;?>

Cierre de sesión

public/logout.php destruye completamente la sesión y redirige al inicio:
<?php
include '../config.php';
session_start();
session_unset();
session_destroy();
session_regenerate_id(true);

header('Location: '.URL_BASE);
El proceso llama a session_unset() para limpiar todas las variables de sesión, session_destroy() para eliminar los datos del servidor y session_regenerate_id(true) para invalidar el ID de sesión anterior, evitando su reutilización.
Bug de precedencia de operadores en la comprobación RBAC. La condición del guard que pretende eximir al rol 1 (administrador) de las restricciones de permiso contiene un error:
if(!$_SESSION['usuario']['id_rol']===1 && ...)
PHP evalúa ! antes que ===, por lo que la expresión se interpreta como:
if( (!$_SESSION['usuario']['id_rol']) === 1 && ...)
(!$_SESSION['usuario']['id_rol']) produce un booleano (true o false), que comparado con === 1 siempre devuelve false. Como consecuencia, la exención del rol administrador nunca se aplica y todos los usuarios, incluyendo los de rol 1, están sujetos a la comprobación de $permiso.La intención correcta sería:
if(!($_SESSION['usuario']['id_rol'] === 1) && ...)
// o equivalentemente:
if($_SESSION['usuario']['id_rol'] !== 1 && ...)

Build docs developers (and LLMs) love