From b7e1332b8c117b5362f382719f5ef5f0a0bff788 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Fri, 31 Jul 2026 08:32:00 -0300 Subject: [PATCH] feat(authorization): refactor role management to use RoleCode enum, update migrations, seeders, and tests --- app/Domains/Auth/Models/User.php | 18 +---- app/Domains/Authorization/Enums/RoleCode.php | 10 +++ .../Controllers/BootstrapTenantController.php | 3 +- ...d_role_and_tenant_codes_to_users_table.php | 14 +--- ..._07_31_000000_remove_tenant_admin_role.php | 35 ++++++++ database/seeders/AuthorizationSeeder.php | 36 ++------- database/seeders/MenuSeeder.php | 5 +- .../Auth/UserAuthorizationRelationsTest.php | 80 +------------------ .../Seeders/AuthorizationSeederTest.php | 20 ++--- tests/Feature/Seeders/MenuSeederTest.php | 5 +- .../Tenant/BootstrapTenantControllerTest.php | 7 +- 11 files changed, 82 insertions(+), 151 deletions(-) create mode 100644 app/Domains/Authorization/Enums/RoleCode.php create mode 100644 database/migrations/2026_07_31_000000_remove_tenant_admin_role.php diff --git a/app/Domains/Auth/Models/User.php b/app/Domains/Auth/Models/User.php index 621da0c..b360049 100644 --- a/app/Domains/Auth/Models/User.php +++ b/app/Domains/Auth/Models/User.php @@ -2,6 +2,7 @@ namespace App\Domains\Auth\Models; +use App\Domains\Authorization\Enums\RoleCode; use App\Domains\Authorization\Models\Role; use App\Domains\Tenant\Models\Tenant; use Database\Factories\UserFactory; @@ -13,7 +14,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; -use LogicException; #[Fillable(['nombre_apellido', 'email', 'password', 'dni', 'telefono', 'google_id', 'rol_codigo', 'tenant_codigo'])] #[Hidden(['password', 'remember_token'])] @@ -23,7 +23,7 @@ class User extends Authenticatable use HasApiTokens, HasFactory, Notifiable; protected $attributes = [ - 'rol_codigo' => 'user', + 'rol_codigo' => RoleCode::User->value, ]; protected static function newFactory(): UserFactory @@ -31,20 +31,6 @@ class User extends Authenticatable return UserFactory::new(); } - protected static function booted(): void - { - static::saving(function (User $user): void { - $hasTenant = $user->tenant_codigo !== null; - $isTenantAdmin = $user->rol_codigo === 'tenant_admin'; - - if ($hasTenant !== $isTenantAdmin) { - throw new LogicException( - 'tenant_codigo debe estar informado únicamente para usuarios con rol tenant_admin.' - ); - } - }); - } - /** @return HasMany */ public function resetPasswordAttempts(): HasMany { diff --git a/app/Domains/Authorization/Enums/RoleCode.php b/app/Domains/Authorization/Enums/RoleCode.php new file mode 100644 index 0000000..373b27b --- /dev/null +++ b/app/Domains/Authorization/Enums/RoleCode.php @@ -0,0 +1,10 @@ + fn ($query) => $query->whereHas( 'roles', - fn ($query) => $query->where('codigo', 'user') + fn ($query) => $query->where('codigo', RoleCode::User->value) ), 'categories' => fn ($query) => $query->orderBy('nombre'), ] diff --git a/database/migrations/2026_07_28_000100_add_role_and_tenant_codes_to_users_table.php b/database/migrations/2026_07_28_000100_add_role_and_tenant_codes_to_users_table.php index afd24de..89a8525 100644 --- a/database/migrations/2026_07_28_000100_add_role_and_tenant_codes_to_users_table.php +++ b/database/migrations/2026_07_28_000100_add_role_and_tenant_codes_to_users_table.php @@ -1,5 +1,6 @@ insertOrIgnore([ [ - 'codigo' => 'admin', + 'codigo' => RoleCode::Admin->value, 'nombre' => 'Administrador general', 'descripcion' => 'Administra la plataforma y todos sus tenants.', 'created_at' => $now, 'updated_at' => $now, ], [ - 'codigo' => 'tenant_admin', - 'nombre' => 'Administrador del tenant', - 'descripcion' => 'Administra la operación de su propio tenant.', - 'created_at' => $now, - 'updated_at' => $now, - ], - [ - 'codigo' => 'user', + 'codigo' => RoleCode::User->value, 'nombre' => 'Usuario', 'descripcion' => 'Cliente final limitado a sus propios datos y operaciones.', 'created_at' => $now, @@ -36,7 +30,7 @@ return new class extends Migration ]); Schema::table('users', function (Blueprint $table): void { - $table->string('rol_codigo')->default('user')->after('id'); + $table->string('rol_codigo')->default(RoleCode::User->value)->after('id'); $table->string('tenant_codigo')->nullable()->after('rol_codigo'); $table->foreign('rol_codigo') diff --git a/database/migrations/2026_07_31_000000_remove_tenant_admin_role.php b/database/migrations/2026_07_31_000000_remove_tenant_admin_role.php new file mode 100644 index 0000000..4d636bc --- /dev/null +++ b/database/migrations/2026_07_31_000000_remove_tenant_admin_role.php @@ -0,0 +1,35 @@ +where('rol_codigo', self::REMOVED_ROLE_CODE) + ->update([ + 'rol_codigo' => RoleCode::User->value, + 'tenant_codigo' => null, + ]); + + DB::table('roles') + ->where('codigo', self::REMOVED_ROLE_CODE) + ->delete(); + } + + public function down(): void + { + DB::table('roles')->insertOrIgnore([ + 'codigo' => self::REMOVED_ROLE_CODE, + 'nombre' => 'Administrador del tenant', + 'descripcion' => 'Administra la operación de su propio tenant.', + 'created_at' => now(), + 'updated_at' => now(), + ]); + } +}; diff --git a/database/seeders/AuthorizationSeeder.php b/database/seeders/AuthorizationSeeder.php index 28428fd..9fb6203 100644 --- a/database/seeders/AuthorizationSeeder.php +++ b/database/seeders/AuthorizationSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Domains\Authorization\Enums\RoleCode; use App\Domains\Authorization\Models\Permission; use App\Domains\Authorization\Models\Role; use Illuminate\Database\Seeder; @@ -106,44 +107,17 @@ class AuthorizationSeeder extends Seeder * @var array}> */ private const ROLES = [ - 'admin' => [ + RoleCode::Admin->value => [ 'nombre' => 'Administrador general', 'descripcion' => 'Administra la plataforma y todos sus tenants.', 'permisos' => [], ], - 'adminapp' => [ + RoleCode::AdminApp->value => [ 'nombre' => 'Administrador de la aplicación', 'descripcion' => 'Accede a los menús administrativos de la aplicación.', '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' => [ + RoleCode::User->value => [ 'nombre' => 'Usuario', 'descripcion' => 'Cliente final limitado a sus propios datos y operaciones.', 'permisos' => [], @@ -168,7 +142,7 @@ class AuthorizationSeeder extends Seeder ] ); - $permissionCodes = $code === 'admin' + $permissionCodes = $code === RoleCode::Admin->value ? array_keys(self::PERMISSIONS) : $attributes['permisos']; diff --git a/database/seeders/MenuSeeder.php b/database/seeders/MenuSeeder.php index c4afb33..075b9bc 100644 --- a/database/seeders/MenuSeeder.php +++ b/database/seeders/MenuSeeder.php @@ -2,6 +2,7 @@ namespace Database\Seeders; +use App\Domains\Authorization\Enums\RoleCode; use App\Domains\Authorization\Models\Role; use App\Domains\Menu\Models\Menu; use App\Domains\Tenant\Models\Tenant; @@ -140,11 +141,11 @@ class MenuSeeder extends Seeder ->pluck('code'); Role::query() - ->whereIn('codigo', ['admin', 'adminapp']) + ->whereIn('codigo', [RoleCode::Admin->value, RoleCode::AdminApp->value]) ->each(fn (Role $role) => $role->menus()->sync($allRoleMenuCodes)); Role::query() - ->where('codigo', 'user') + ->where('codigo', RoleCode::User->value) ->each(fn (Role $role) => $role->menus()->sync($userMenuCodes)); $tenants = Tenant::all(); diff --git a/tests/Feature/Auth/UserAuthorizationRelationsTest.php b/tests/Feature/Auth/UserAuthorizationRelationsTest.php index 73b6924..9565d81 100644 --- a/tests/Feature/Auth/UserAuthorizationRelationsTest.php +++ b/tests/Feature/Auth/UserAuthorizationRelationsTest.php @@ -2,14 +2,10 @@ namespace Tests\Feature\Auth; -use App\Domains\Attachable\Enums\AttachmentType; -use App\Domains\Attachable\Models\Attachment; use App\Domains\Auth\Models\User; -use App\Domains\Authorization\Models\Role; -use App\Domains\Tenant\Models\Tenant; +use App\Domains\Authorization\Enums\RoleCode; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Schema; -use LogicException; use Tests\TestCase; class UserAuthorizationRelationsTest extends TestCase @@ -28,79 +24,9 @@ class UserAuthorizationRelationsTest extends TestCase { $user = User::factory()->create(); - $this->assertSame('user', $user->rol_codigo); + $this->assertSame(RoleCode::User->value, $user->rol_codigo); $this->assertNull($user->tenant_codigo); - $this->assertSame('user', $user->role->codigo); + $this->assertSame(RoleCode::User->value, $user->role->codigo); $this->assertNull($user->tenant); } - - public function test_a_tenant_admin_belongs_to_its_role_and_tenant(): void - { - $tenant = $this->createTenant(); - $user = User::factory()->create([ - 'rol_codigo' => 'tenant_admin', - 'tenant_codigo' => $tenant->codigo, - ]); - - $this->assertSame('tenant_admin', $user->role->codigo); - $this->assertTrue($user->tenant->is($tenant)); - $this->assertTrue( - Role::query() - ->where('codigo', 'tenant_admin') - ->firstOrFail() - ->users - ->contains($user) - ); - } - - public function test_only_a_tenant_admin_can_have_a_tenant_code(): void - { - $tenant = $this->createTenant(); - - $this->expectException(LogicException::class); - - User::factory()->create([ - 'rol_codigo' => 'user', - 'tenant_codigo' => $tenant->codigo, - ]); - } - - public function test_a_tenant_admin_must_have_a_tenant_code(): void - { - $this->expectException(LogicException::class); - - User::factory()->create([ - 'rol_codigo' => 'tenant_admin', - ]); - } - - private function createTenant(): Tenant - { - $headerLogo = $this->createAttachment('header.png'); - $footerLogo = $this->createAttachment('footer.png'); - - return Tenant::query()->create([ - 'codigo' => 'acme', - 'nombre' => 'Acme', - 'dominio' => 'acme.test', - 'primary_color' => '#111111', - 'secondary_color' => '#222222', - 'danger_color' => '#333333', - 'success_color' => '#444444', - 'header_bg_color' => '#ffffff', - 'footer_bg_color' => '#ffffff', - 'header_logo_id' => $headerLogo->id, - 'footer_logo_id' => $footerLogo->id, - ]); - } - - private function createAttachment(string $filename): Attachment - { - return Attachment::query()->create([ - 'path' => "test/{$filename}", - 'filename' => $filename, - 'type' => AttachmentType::Image, - 'mime_type' => 'image/png', - ]); - } } diff --git a/tests/Feature/Seeders/AuthorizationSeederTest.php b/tests/Feature/Seeders/AuthorizationSeederTest.php index 93d8b42..92834ae 100644 --- a/tests/Feature/Seeders/AuthorizationSeederTest.php +++ b/tests/Feature/Seeders/AuthorizationSeederTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Seeders; +use App\Domains\Authorization\Enums\RoleCode; use App\Domains\Authorization\Models\Permission; use App\Domains\Authorization\Models\Role; use Database\Seeders\AuthorizationSeeder; @@ -17,7 +18,11 @@ class AuthorizationSeederTest extends TestCase $this->seed(AuthorizationSeeder::class); $this->assertSame( - ['admin', 'adminapp', 'tenant_admin', 'user'], + [ + RoleCode::Admin->value, + RoleCode::AdminApp->value, + RoleCode::User->value, + ], Role::query()->orderBy('codigo')->pluck('codigo')->all() ); $this->assertCount(22, Permission::query()->get()); @@ -27,15 +32,12 @@ class AuthorizationSeederTest extends TestCase { $this->seed(AuthorizationSeeder::class); - $admin = Role::query()->where('codigo', 'admin')->firstOrFail(); - $appAdmin = Role::query()->where('codigo', 'adminapp')->firstOrFail(); - $tenantAdmin = Role::query()->where('codigo', 'tenant_admin')->firstOrFail(); - $user = Role::query()->where('codigo', 'user')->firstOrFail(); + $admin = Role::query()->where('codigo', RoleCode::Admin->value)->firstOrFail(); + $appAdmin = Role::query()->where('codigo', RoleCode::AdminApp->value)->firstOrFail(); + $user = Role::query()->where('codigo', RoleCode::User->value)->firstOrFail(); $this->assertCount(22, $admin->permissions); $this->assertCount(0, $appAdmin->permissions); - $this->assertCount(21, $tenantAdmin->permissions); - $this->assertFalse($tenantAdmin->permissions->contains('codigo', 'roles.gestionar')); $this->assertCount(0, $user->permissions); } @@ -44,8 +46,8 @@ class AuthorizationSeederTest extends TestCase $this->seed(AuthorizationSeeder::class); $this->seed(AuthorizationSeeder::class); - $this->assertCount(4, Role::query()->get()); + $this->assertCount(3, Role::query()->get()); $this->assertCount(22, Permission::query()->get()); - $this->assertDatabaseCount('roles_permisos', 43); + $this->assertDatabaseCount('roles_permisos', 22); } } diff --git a/tests/Feature/Seeders/MenuSeederTest.php b/tests/Feature/Seeders/MenuSeederTest.php index d980775..d59f3b9 100644 --- a/tests/Feature/Seeders/MenuSeederTest.php +++ b/tests/Feature/Seeders/MenuSeederTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Seeders; use App\Domains\Attachable\Enums\AttachmentType; use App\Domains\Attachable\Models\Attachment; +use App\Domains\Authorization\Enums\RoleCode; use App\Domains\Authorization\Models\Role; use App\Domains\Menu\Models\Menu; use App\Domains\Tenant\Models\Tenant; @@ -62,7 +63,7 @@ class MenuSeederTest extends TestCase ->pluck('code') ->all(); - foreach (['admin', 'adminapp'] as $roleCode) { + foreach ([RoleCode::Admin->value, RoleCode::AdminApp->value] as $roleCode) { $roleMenuCodes = Role::query() ->where('codigo', $roleCode) ->firstOrFail() @@ -75,7 +76,7 @@ class MenuSeederTest extends TestCase } $userRoleMenuCodes = Role::query() - ->where('codigo', 'user') + ->where('codigo', RoleCode::User->value) ->firstOrFail() ->menus() ->orderBy('menues.code') diff --git a/tests/Feature/Tenant/BootstrapTenantControllerTest.php b/tests/Feature/Tenant/BootstrapTenantControllerTest.php index 8fc30fa..5c19d33 100644 --- a/tests/Feature/Tenant/BootstrapTenantControllerTest.php +++ b/tests/Feature/Tenant/BootstrapTenantControllerTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Tenant; use App\Domains\Attachable\Enums\AttachmentType; use App\Domains\Attachable\Models\Attachment; +use App\Domains\Authorization\Enums\RoleCode; use App\Domains\Authorization\Models\Role; use App\Domains\Catalog\Models\Category; use App\Domains\Menu\Models\Menu; @@ -147,7 +148,7 @@ class BootstrapTenantControllerTest extends TestCase { $tenant = $this->createTenant(); $userRole = Role::query()->create([ - 'codigo' => 'user', + 'codigo' => RoleCode::User->value, 'nombre' => 'Usuario', ]); $parent = Menu::query()->create([ @@ -228,11 +229,11 @@ class BootstrapTenantControllerTest extends TestCase { $tenant = $this->createTenant(); $userRole = Role::query()->create([ - 'codigo' => 'user', + 'codigo' => RoleCode::User->value, 'nombre' => 'Usuario', ]); $adminRole = Role::query()->create([ - 'codigo' => 'admin', + 'codigo' => RoleCode::Admin->value, 'nombre' => 'Administrador', ]); $userMenu = Menu::query()->create([