Saltar al contenido
seguridad / · 3 min

El rol que no existia en el enum: 200 en todas las pantallas y cero filas

Los permisos estaban bien, el login funcionaba y las rutas respondian. Pero tryFrom devolvia null en silencio y el usuario se quedaba sin territorio.

Administrador · Desarrollador Laravel
El rol que no existia en el enum: 200 en todas las pantallas y cero filas

Creé un rol nuevo llamado demo, le asigné trece de los catorce permisos de la plataforma y se lo puse a un usuario. Entró bien. Todas las pantallas respondían 200.

Y todas salían vacías.

El permiso dice qué, el rol dice hasta dónde

En esa aplicación un rol carga dos cosas independientes. Los permisos —qué módulos ve— viven en spatie/permission y se administran desde la interfaz. Pero el ámbito geográfico —si ve todo el estado, un distrito o un municipio— sale de un enum de PHP:

enum Rol: string
{
    case Administrador = 'administrador';
    case Estatal = 'estatal';
    case DttoFederal = 'dtto_federal';
    case Municipal = 'municipal';

    public function nivel(): NivelAlcance
    {
        return match ($this) {
            self::Administrador, self::Estatal => NivelAlcance::Estatal,
            self::DttoFederal => NivelAlcance::DttoFederal,
            self::Municipal => NivelAlcance::Municipal,
        };
    }
}

Y el modelo los une así:

public function rol(): ?Rol
{
    $nombre = $this->roles->pluck('name')->first();

    return is_string($nombre) ? Rol::tryFrom($nombre) : null;
}

Ahí está. Rol::tryFrom('demo') devuelve null. No lanza excepción, no escribe en el log: devuelve null, que es exactamente para lo que existe tryFrom.

Con el rol en null, el usuario no tiene nivel. Sin nivel no tiene territorio. Sin territorio, cada consulta filtra por un conjunto vacío y devuelve cero filas. Con un HTTP 200 impecable.

Por qué el síntoma engaña tanto

Todo el instrumental apuntaba a que estaba bien. Los permisos: correctos, se podían listar. El login: funcionaba. Las rutas protegidas: 200 las que debían y 403 las que no. El middleware de permisos hacía su trabajo perfectamente.

El problema estaba una capa más adentro, en un lugar donde nadie pone un assert: la traducción del nombre del rol a un valor de dominio.

Un catálogo de datos y un enum de código son dos fuentes de verdad, y en cuanto una puede tener valores que la otra no reconoce, tienes un modo de fallo silencioso esperando.

La solución no era inventar un rol

Lo curioso es que la aplicación ya tenía lo que yo necesitaba. El rol estatal venía documentado en el propio código:

Ve todo el estado igual que el administrador, pero no administra usuarios ni roles. Es el perfil de quien consulta la plataforma completa sin poder repartir accesos.

Era literalmente el perfil de demostración que yo estaba construyendo a mano. Le puse ese rol al usuario, borré el mío, y las trece pantallas se llenaron.

Lo que me llevo

Antes de crear un rol, lee el enum. Si el sistema deriva algo del nombre del rol, la lista de roles no es abierta aunque la tabla lo parezca.

Un tryFrom que devuelve null merece un log. Si esa función hubiera escrito una advertencia al no reconocer un rol, me habría ahorrado el rodeo entero. El comportamiento silencioso es correcto para el código y pésimo para quien lo opera.

Y el aviso general: cuando la interfaz te deja crear registros que el código no puede interpretar, no es un bug del usuario. Es una invitación a equivocarse que dejamos ahí nosotros.

#laravel #roles #permisos #spatie
Comentarios 0

Nadie ha comentado todavía. Estrena la sección.

Deja tu comentario
Tu correo no se publica. Reviso los comentarios antes de publicarlos.

Seguir leyendo