feat(authorization): implement AuthorizationSeeder with roles and permissions, add tests

This commit is contained in:
2026-07-28 14:58:58 -03:00
parent a674bcfabc
commit cba32e90e7
4 changed files with 308 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
<?php
namespace Database\Seeders;
use App\Domains\Authorization\Models\Permission;
use App\Domains\Authorization\Models\Role;
use Illuminate\Database\Seeder;
class AuthorizationSeeder extends Seeder
{
/**
* @var array<string, array{nombre: string, descripcion: string}>
*/
private const PERMISSIONS = [
'tenant.ver' => [
'nombre' => 'Ver configuración del tenant',
'descripcion' => 'Permite consultar la configuración general de un tenant.',
],
'tenant.editar' => [
'nombre' => 'Editar configuración del tenant',
'descripcion' => 'Permite modificar los datos generales, dominio, colores y recursos visuales del tenant.',
],
'usuarios.ver' => [
'nombre' => 'Ver usuarios',
'descripcion' => 'Permite consultar los usuarios asociados al tenant.',
],
'usuarios.gestionar' => [
'nombre' => 'Gestionar usuarios',
'descripcion' => 'Permite invitar, editar, desactivar y asignar roles permitidos a los usuarios del tenant.',
],
'roles.ver' => [
'nombre' => 'Ver roles',
'descripcion' => 'Permite consultar los roles y sus permisos.',
],
'roles.gestionar' => [
'nombre' => 'Gestionar roles',
'descripcion' => 'Permite crear, modificar y asignar permisos a roles.',
],
'catalogo.ver' => [
'nombre' => 'Ver catálogo',
'descripcion' => 'Permite consultar administrativamente productos, variantes, categorías, marcas y bundles.',
],
'catalogo.gestionar' => [
'nombre' => 'Gestionar catálogo',
'descripcion' => 'Permite crear, modificar, archivar y eliminar productos, variantes, categorías, marcas y bundles.',
],
'inventario.ver' => [
'nombre' => 'Ver inventario',
'descripcion' => 'Permite consultar el stock y la información de inventario.',
],
'inventario.ajustar' => [
'nombre' => 'Ajustar inventario',
'descripcion' => 'Permite modificar el stock disponible.',
],
'pedidos.ver' => [
'nombre' => 'Ver pedidos',
'descripcion' => 'Permite consultar los pedidos del tenant.',
],
'pedidos.gestionar' => [
'nombre' => 'Gestionar pedidos',
'descripcion' => 'Permite modificar estados y datos operativos de los pedidos.',
],
'pedidos.cancelar' => [
'nombre' => 'Cancelar pedidos',
'descripcion' => 'Permite cancelar pedidos y liberar sus reservas de inventario.',
],
'pagos.ver' => [
'nombre' => 'Ver pagos',
'descripcion' => 'Permite consultar el estado y la información de los pagos.',
],
'pagos.gestionar' => [
'nombre' => 'Gestionar pagos',
'descripcion' => 'Permite confirmar, rechazar o devolver pagos.',
],
'tickets.ver' => [
'nombre' => 'Ver tickets',
'descripcion' => 'Permite consultar los tickets emitidos.',
],
'tickets.gestionar' => [
'nombre' => 'Gestionar tickets',
'descripcion' => 'Permite emitir, invalidar o regenerar tickets.',
],
'contenido.gestionar' => [
'nombre' => 'Gestionar contenido',
'descripcion' => 'Permite administrar menús, carruseles, destacados y redes sociales.',
],
'integraciones.ver' => [
'nombre' => 'Ver integraciones',
'descripcion' => 'Permite consultar la configuración y el estado de las integraciones.',
],
'integraciones.gestionar' => [
'nombre' => 'Gestionar integraciones',
'descripcion' => 'Permite configurar credenciales y proveedores de integraciones.',
],
'reportes.ver' => [
'nombre' => 'Ver reportes',
'descripcion' => 'Permite consultar reportes de ventas y métricas.',
],
'reportes.exportar' => [
'nombre' => 'Exportar reportes',
'descripcion' => 'Permite descargar reportes e información comercial.',
],
];
/**
* @var array<string, array{nombre: string, descripcion: string, permisos: list<string>}>
*/
private const ROLES = [
'admin' => [
'nombre' => 'Administrador general',
'descripcion' => 'Administra la plataforma y todos sus tenants.',
'permisos' => [],
],
'tenant_admin' => [
'nombre' => 'Administrador del tenant',
'descripcion' => 'Administra la operación de su propio tenant.',
'permisos' => [
'tenant.ver',
'tenant.editar',
'usuarios.ver',
'usuarios.gestionar',
'roles.ver',
'catalogo.ver',
'catalogo.gestionar',
'inventario.ver',
'inventario.ajustar',
'pedidos.ver',
'pedidos.gestionar',
'pedidos.cancelar',
'pagos.ver',
'pagos.gestionar',
'tickets.ver',
'tickets.gestionar',
'contenido.gestionar',
'integraciones.ver',
'integraciones.gestionar',
'reportes.ver',
'reportes.exportar',
],
],
'user' => [
'nombre' => 'Usuario',
'descripcion' => 'Cliente final limitado a sus propios datos y operaciones.',
'permisos' => [],
],
];
public function run(): void
{
foreach (self::PERMISSIONS as $code => $attributes) {
Permission::query()->updateOrCreate(
['codigo' => $code],
$attributes
);
}
foreach (self::ROLES as $code => $attributes) {
$role = Role::query()->updateOrCreate(
['codigo' => $code],
[
'nombre' => $attributes['nombre'],
'descripcion' => $attributes['descripcion'],
]
);
$permissionCodes = $code === 'admin'
? array_keys(self::PERMISSIONS)
: $attributes['permisos'];
$role->permissions()->sync($permissionCodes);
}
}
}

View File

@@ -24,6 +24,7 @@ class DatabaseSeeder extends Seeder
$this->call([
WebsiteTypeSeeder::class,
AuthorizationSeeder::class,
SocialMediaSeeder::class,
TenantSeeder::class,
AttributeSeeder::class,

85
docs/authorization.md Normal file
View File

@@ -0,0 +1,85 @@
# Roles y permisos
El sistema utiliza control de acceso basado en roles. Los códigos son identificadores
estables para uso interno; los nombres y las descripciones pueden modificarse sin
afectar las reglas de autorización.
Los permisos administrativos deben evaluarse siempre dentro del tenant correspondiente.
El rol `admin` es la única excepción porque su alcance es toda la plataforma.
## Roles
| Código | Nombre | Alcance |
| --- | --- | --- |
| `admin` | Administrador general | Administra la plataforma, todos los tenants, roles y permisos. Tiene todos los permisos. |
| `tenant_admin` | Administrador del tenant | Administra usuarios y la operación de su tenant. No puede gestionar la definición de roles. |
| `user` | Usuario | Cliente final. No tiene permisos administrativos y solamente puede operar sobre sus propios datos, carrito, pedidos y tickets. |
## Permisos
| Código | Nombre | Descripción |
| --- | --- | --- |
| `tenant.ver` | Ver configuración del tenant | Consultar la configuración general de un tenant. |
| `tenant.editar` | Editar configuración del tenant | Modificar datos generales, dominio, colores y recursos visuales. |
| `usuarios.ver` | Ver usuarios | Consultar los usuarios asociados al tenant. |
| `usuarios.gestionar` | Gestionar usuarios | Invitar, editar, desactivar y asignar roles permitidos a usuarios del tenant. |
| `roles.ver` | Ver roles | Consultar los roles y sus permisos. |
| `roles.gestionar` | Gestionar roles | Crear, modificar y asignar permisos a roles. |
| `catalogo.ver` | Ver catálogo | Consultar productos, variantes, categorías, marcas y bundles administrativamente. |
| `catalogo.gestionar` | Gestionar catálogo | Crear, modificar, archivar y eliminar productos, variantes, categorías, marcas y bundles. |
| `inventario.ver` | Ver inventario | Consultar stock e información de inventario. |
| `inventario.ajustar` | Ajustar inventario | Modificar el stock disponible. |
| `pedidos.ver` | Ver pedidos | Consultar los pedidos del tenant. |
| `pedidos.gestionar` | Gestionar pedidos | Modificar estados y datos operativos de pedidos. |
| `pedidos.cancelar` | Cancelar pedidos | Cancelar pedidos y liberar sus reservas de inventario. |
| `pagos.ver` | Ver pagos | Consultar el estado y la información de pagos. |
| `pagos.gestionar` | Gestionar pagos | Confirmar, rechazar o devolver pagos. |
| `tickets.ver` | Ver tickets | Consultar tickets emitidos. |
| `tickets.gestionar` | Gestionar tickets | Emitir, invalidar o regenerar tickets. |
| `contenido.gestionar` | Gestionar contenido | Administrar menús, carruseles, destacados y redes sociales. |
| `integraciones.ver` | Ver integraciones | Consultar la configuración y el estado de integraciones. |
| `integraciones.gestionar` | Gestionar integraciones | Configurar credenciales y proveedores de integraciones. |
| `reportes.ver` | Ver reportes | Consultar reportes de ventas y métricas. |
| `reportes.exportar` | Exportar reportes | Descargar reportes e información comercial. |
## Matriz de asignación
| Permiso | `admin` | `tenant_admin` | `user` |
| --- | :---: | :---: | :---: |
| `tenant.ver` | Sí | Sí | No |
| `tenant.editar` | Sí | Sí | No |
| `usuarios.ver` | Sí | Sí | No |
| `usuarios.gestionar` | Sí | Sí | No |
| `roles.ver` | Sí | Sí | No |
| `roles.gestionar` | Sí | No | No |
| `catalogo.ver` | Sí | Sí | No |
| `catalogo.gestionar` | Sí | Sí | No |
| `inventario.ver` | Sí | Sí | No |
| `inventario.ajustar` | Sí | Sí | No |
| `pedidos.ver` | Sí | Sí | No |
| `pedidos.gestionar` | Sí | Sí | No |
| `pedidos.cancelar` | Sí | Sí | No |
| `pagos.ver` | Sí | Sí | No |
| `pagos.gestionar` | Sí | Sí | No |
| `tickets.ver` | Sí | Sí | No |
| `tickets.gestionar` | Sí | Sí | No |
| `contenido.gestionar` | Sí | Sí | No |
| `integraciones.ver` | Sí | Sí | No |
| `integraciones.gestionar` | Sí | Sí | No |
| `reportes.ver` | Sí | Sí | No |
| `reportes.exportar` | Sí | Sí | No |
## Carga inicial
El seeder `Database\Seeders\AuthorizationSeeder` crea o actualiza este catálogo y
sincroniza sus asignaciones. Puede ejecutarse de manera independiente:
```bash
php artisan db:seed --class=AuthorizationSeeder
```
También se ejecuta como parte de `DatabaseSeeder`.
Las acciones del rol `user` no se representan como permisos administrativos. El
acceso al perfil, carrito, pedidos y tickets se autoriza comprobando que el recurso
pertenezca al usuario autenticado y al tenant actual.

View File

@@ -0,0 +1,49 @@
<?php
namespace Tests\Feature\Seeders;
use App\Domains\Authorization\Models\Permission;
use App\Domains\Authorization\Models\Role;
use Database\Seeders\AuthorizationSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AuthorizationSeederTest extends TestCase
{
use RefreshDatabase;
public function test_it_creates_the_initial_roles_and_permissions(): void
{
$this->seed(AuthorizationSeeder::class);
$this->assertSame(
['admin', 'tenant_admin', 'user'],
Role::query()->orderBy('codigo')->pluck('codigo')->all()
);
$this->assertCount(22, Permission::query()->get());
}
public function test_it_assigns_the_expected_permissions_to_each_role(): void
{
$this->seed(AuthorizationSeeder::class);
$admin = Role::query()->where('codigo', 'admin')->firstOrFail();
$tenantAdmin = Role::query()->where('codigo', 'tenant_admin')->firstOrFail();
$user = Role::query()->where('codigo', 'user')->firstOrFail();
$this->assertCount(22, $admin->permissions);
$this->assertCount(21, $tenantAdmin->permissions);
$this->assertFalse($tenantAdmin->permissions->contains('codigo', 'roles.gestionar'));
$this->assertCount(0, $user->permissions);
}
public function test_it_can_run_more_than_once_without_creating_duplicates(): void
{
$this->seed(AuthorizationSeeder::class);
$this->seed(AuthorizationSeeder::class);
$this->assertCount(3, Role::query()->get());
$this->assertCount(22, Permission::query()->get());
$this->assertDatabaseCount('roles_permisos', 43);
}
}