Skip to main content

Visión General

El sistema implementa un modelo de permisos granular que permite controlar con precisión qué acciones puede realizar cada rol en cada módulo del sistema.

Modelo de Datos

Permission

Modelo: App\Models\Permission Ubicación: app/Models/Permission.php Tabla: permissions Campos:
CampoTipoDescripción
permission_idintegerIdentificador único del permiso (PK)
namestringNombre único del permiso (ej: ventas.create)
display_namestringNombre legible del permiso (ej: Crear Ventas)
modulestringMódulo al que pertenece (ej: ventas)
actionstringAcción que permite (ej: create)
descriptiontextDescripción del permiso
created_attimestampFecha de creación
updated_attimestampFecha de actualización
Relación:
public function roles()
{
    return $this->belongsToMany(
        Rol::class,
        'role_permission',
        'permission_id',
        'rol_id'
    );
}

Tabla Pivot: role_permission

Tabla: role_permission Campos:
CampoTipoDescripción
idintegerIdentificador único (PK)
rol_idintegerID del rol (FK → roles.rol_id)
permission_idintegerID del permiso (FK → permissions.permission_id)
created_attimestampFecha de asignación
updated_attimestampFecha de actualización
Constraint único: [rol_id, permission_id] - Un rol no puede tener el mismo permiso duplicado.

Nomenclatura de Permisos

Los permisos siguen el formato: {módulo}.{acción}

Estructura

  • Módulo: Área funcional del sistema (ventas, productos, clientes, etc.)
  • Acción: Operación permitida (view, create, edit, delete)

Ejemplos

  • ventas.view → Ver ventas
  • ventas.create → Crear ventas
  • productos.edit → Editar productos
  • clientes.delete → Eliminar clientes

Permisos del Sistema

El sistema incluye los siguientes permisos por defecto:

Módulo: Ventas

PermisoNombre para MostrarDescripción
ventas.viewVer VentasPermiso para Ver en el módulo de Ventas
ventas.createCrear VentasPermiso para Crear en el módulo de Ventas
ventas.editEditar VentasPermiso para Editar en el módulo de Ventas
ventas.deleteEliminar VentasPermiso para Eliminar en el módulo de Ventas
Rutas protegidas:
  • GET /api/ventasventas.view
  • POST /api/ventasventas.create
  • PUT /api/ventas/{id}ventas.edit
  • DELETE /api/ventas/{id}ventas.delete
  • POST /api/ventas/{id}/anularventas.delete
  • POST /api/ventas/{id}/descontar-stockventas.edit
  • GET /api/ventas/exportar-*ventas.view

Módulo: Productos

PermisoNombre para MostrarDescripción
productos.viewVer ProductosPermiso para Ver en el módulo de Productos
productos.createCrear ProductosPermiso para Crear en el módulo de Productos
productos.editEditar ProductosPermiso para Editar en el módulo de Productos
productos.deleteEliminar ProductosPermiso para Eliminar en el módulo de Productos
Rutas protegidas:
  • GET /api/productosproductos.view
  • POST /api/productosproductos.create
  • PUT /api/productos/{id}productos.edit
  • DELETE /api/productos/{id}productos.delete
  • GET /api/movimientos-stockproductos.view
  • POST /api/productos/leer-excelproductos.create
  • POST /api/productos/importar-listaproductos.create

Módulo: Clientes

PermisoNombre para MostrarDescripción
clientes.viewVer ClientesPermiso para Ver en el módulo de Clientes
clientes.createCrear ClientesPermiso para Crear en el módulo de Clientes
clientes.editEditar ClientesPermiso para Editar en el módulo de Clientes
clientes.deleteEliminar ClientesPermiso para Eliminar en el módulo de Clientes
Rutas protegidas:
  • GET /api/clientesclientes.view
  • POST /api/clientesclientes.create
  • PUT /api/clientes/{id}clientes.edit
  • DELETE /api/clientes/{id}clientes.delete

Módulo: Proveedores

PermisoNombre para MostrarDescripción
proveedores.viewVer ProveedoresPermiso para Ver en el módulo de Proveedores
proveedores.createCrear ProveedoresPermiso para Crear en el módulo de Proveedores
proveedores.editEditar ProveedoresPermiso para Editar en el módulo de Proveedores
proveedores.deleteEliminar ProveedoresPermiso para Eliminar en el módulo de Proveedores
Rutas protegidas:
  • GET /api/proveedoresproveedores.view
  • POST /api/proveedoresproveedores.create
  • PUT /api/proveedores/{id}proveedores.edit
  • DELETE /api/proveedores/{id}proveedores.delete

Módulo: Compras

PermisoNombre para MostrarDescripción
compras.viewVer ComprasPermiso para Ver en el módulo de Compras
compras.createCrear ComprasPermiso para Crear en el módulo de Compras
compras.editEditar ComprasPermiso para Editar en el módulo de Compras
compras.deleteEliminar ComprasPermiso para Eliminar en el módulo de Compras
Rutas protegidas:
  • GET /api/comprascompras.view
  • POST /api/comprascompras.create
  • PUT /api/compras/{id}compras.edit
  • DELETE /api/compras/{id}compras.delete
  • POST /api/compras/{id}/anularcompras.delete

Módulo: Cotizaciones

PermisoNombre para MostrarDescripción
cotizaciones.viewVer CotizacionesPermiso para Ver en el módulo de Cotizaciones
cotizaciones.createCrear CotizacionesPermiso para Crear en el módulo de Cotizaciones
cotizaciones.editEditar CotizacionesPermiso para Editar en el módulo de Cotizaciones
cotizaciones.deleteEliminar CotizacionesPermiso para Eliminar en el módulo de Cotizaciones
Rutas protegidas:
  • GET /api/cotizacionescotizaciones.view
  • POST /api/cotizacionescotizaciones.create
  • PUT /api/cotizaciones/{id}cotizaciones.edit
  • DELETE /api/cotizaciones/{id}cotizaciones.delete
  • POST /api/cotizaciones/{id}/estadocotizaciones.edit

Módulo: Empresas

PermisoNombre para MostrarDescripción
empresas.viewVer EmpresasPermiso para Ver en el módulo de Empresas
empresas.createCrear EmpresasPermiso para Crear en el módulo de Empresas
empresas.editEditar EmpresasPermiso para Editar en el módulo de Empresas
empresas.deleteEliminar EmpresasPermiso para Eliminar en el módulo de Empresas
Nota: La gestión de empresas típicamente está restringida solo a administradores.

Módulo: Usuarios

PermisoNombre para MostrarDescripción
usuarios.viewVer UsuariosPermiso para Ver en el módulo de Usuarios
usuarios.createCrear UsuariosPermiso para Crear en el módulo de Usuarios
usuarios.editEditar UsuariosPermiso para Editar en el módulo de Usuarios
usuarios.deleteEliminar UsuariosPermiso para Eliminar en el módulo de Usuarios
Nota: La gestión de usuarios típicamente está restringida a roles administrativos.

Módulo: Reportes

PermisoNombre para MostrarDescripción
reportes.viewVer ReportesPermiso para Ver en el módulo de Reportes
reportes.createCrear ReportesPermiso para Crear en el módulo de Reportes
reportes.editEditar ReportesPermiso para Editar en el módulo de Reportes
reportes.deleteEliminar ReportesPermiso para Eliminar en el módulo de Reportes

Middleware CheckPermission

Ubicación: app/Http/Middleware/CheckPermission.php Registro: bootstrap/app.php:24 como 'permission'

Funcionamiento

public function handle(Request $request, Closure $next, string $permission): Response
{
    $user = $request->user();

    // Si no hay usuario autenticado, denegar acceso
    if (!$user) {
        return response()->json([
            'success' => false,
            'message' => 'No autenticado'
        ], 401);
    }

    // Admin (rol_id = 1) tiene acceso a todo
    if ($user->rol_id == 1) {
        return $next($request);
    }

    // Verificar si el usuario tiene el permiso
    if (!$user->hasPermission($permission)) {
        return response()->json([
            'success' => false,
            'message' => 'No tienes permiso para realizar esta acción'
        ], 403);
    }

    return $next($request);
}

Uso en Rutas

Route::middleware(['auth:sanctum', 'permission:ventas.create'])
    ->post('/ventas', [VentasController::class, 'store']);

// Forma abreviada
Route::post('/ventas', [VentasController::class, 'store'])
    ->middleware('permission:ventas.create');

Códigos de Respuesta

  • 401 Unauthorized: Usuario no autenticado
  • 403 Forbidden: Usuario autenticado pero sin el permiso necesario
  • 200 OK: Usuario tiene el permiso, la solicitud continúa

Gestión de Permisos

Asignar Permiso a Rol

$rol = Rol::find(2);
$permission = Permission::where('name', 'ventas.create')->first();

$rol->permissions()->attach($permission->permission_id);

Asignar Múltiples Permisos

$rol = Rol::find(2);
$permissionIds = Permission::whereIn('name', [
    'ventas.view',
    'ventas.create',
    'productos.view'
])->pluck('permission_id')->toArray();

$rol->permissions()->sync($permissionIds);

Remover Permiso de Rol

$rol = Rol::find(2);
$permission = Permission::where('name', 'ventas.delete')->first();

$rol->permissions()->detach($permission->permission_id);

Verificar si Rol Tiene Permiso

$rol = Rol::find(2);

if ($rol->hasPermission('ventas.create')) {
    echo "El rol puede crear ventas";
}

Obtener Todos los Permisos de un Rol

$rol = Rol::with('permissions')->find(2);

foreach ($rol->permissions as $permission) {
    echo $permission->display_name . "\n";
}

Obtener Roles que Tienen un Permiso

$permission = Permission::with('roles')
    ->where('name', 'ventas.create')
    ->first();

foreach ($permission->roles as $rol) {
    echo $rol->nombre . "\n";
}

Creación de Permisos

Los permisos se crean automáticamente mediante la migración: Archivo: database/migrations/2026_02_24_023530_create_permissions_and_role_permission_tables.php:58-97
private function insertDefaultPermissions()
{
    $modules = [
        'ventas' => 'Ventas',
        'productos' => 'Productos',
        'clientes' => 'Clientes',
        'proveedores' => 'Proveedores',
        'compras' => 'Compras',
        'cotizaciones' => 'Cotizaciones',
        'empresas' => 'Empresas',
        'usuarios' => 'Usuarios',
        'reportes' => 'Reportes',
    ];

    $actions = [
        'view' => 'Ver',
        'create' => 'Crear',
        'edit' => 'Editar',
        'delete' => 'Eliminar',
    ];

    $permissions = [];
    $now = now();

    foreach ($modules as $moduleKey => $moduleName) {
        foreach ($actions as $actionKey => $actionName) {
            $permissions[] = [
                'name' => "{$moduleKey}.{$actionKey}",
                'display_name' => "{$actionName} {$moduleName}",
                'module' => $moduleKey,
                'action' => $actionKey,
                'description' => "Permiso para {$actionName} en el módulo de {$moduleName}",
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }
    }

    DB::table('permissions')->insert($permissions);
}

Agregar Nuevos Permisos

Para agregar permisos a un nuevo módulo:
use App\Models\Permission;

$module = 'inventario';
$moduleName = 'Inventario';
$actions = [
    'view' => 'Ver',
    'create' => 'Crear',
    'edit' => 'Editar',
    'delete' => 'Eliminar',
];

foreach ($actions as $actionKey => $actionName) {
    Permission::create([
        'name' => "{$module}.{$actionKey}",
        'display_name' => "{$actionName} {$moduleName}",
        'module' => $module,
        'action' => $actionKey,
        'description' => "Permiso para {$actionName} en el módulo de {$moduleName}",
    ]);
}

Verificación en Frontend

Obtener Permisos al Login

Al hacer login, el sistema retorna todos los permisos del usuario:
const response = await fetch('/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ user: 'admin', password: 'secret' })
});

const data = await response.json();
const permissions = data.permissions; // ['ventas.view', 'ventas.create', ...]

// Guardar en localStorage o estado global
localStorage.setItem('permissions', JSON.stringify(permissions));

Verificar Permisos en React

// Hook personalizado
function usePermission(permission) {
  const permissions = JSON.parse(localStorage.getItem('permissions') || '[]');
  return permissions.includes(permission);
}

// En un componente
function VentasPage() {
  const canCreate = usePermission('ventas.create');
  const canDelete = usePermission('ventas.delete');

  return (
    <div>
      {canCreate && <button>Crear Venta</button>}
      {canDelete && <button>Eliminar</button>}
    </div>
  );
}

Store de Permisos con Zustand

// stores/authStore.js
import { create } from 'zustand';

const useAuthStore = create((set) => ({
  permissions: [],
  setPermissions: (permissions) => set({ permissions }),
  hasPermission: (permission) => {
    const state = useAuthStore.getState();
    return state.permissions.includes(permission);
  },
}));

export default useAuthStore;

Mejores Prácticas

1. Verificación en Backend

Siempre verifique permisos en el backend, incluso si ya los verificó en frontend:
public function store(Request $request)
{
    // El middleware ya verificó el permiso
    // pero puede hacer verificaciones adicionales
    
    if (!$request->user()->hasPermission('ventas.create')) {
        abort(403);
    }
    
    // Proceder con la creación
}

2. Permisos Compuestos

Para operaciones que requieren múltiples permisos:
public function transferStock(Request $request)
{
    $user = $request->user();
    
    if (!$user->hasAllPermissions(['productos.view', 'productos.edit'])) {
        return response()->json([
            'success' => false,
            'message' => 'Necesitas permisos de ver y editar productos'
        ], 403);
    }
    
    // Proceder con la transferencia
}

3. Permisos Alternativos

Para operaciones que aceptan permisos alternativos:
public function viewFinancial(Request $request)
{
    $user = $request->user();
    
    if (!$user->hasAnyPermission(['reportes.view', 'ventas.view'])) {
        return response()->json([
            'success' => false,
            'message' => 'No tienes acceso a reportes financieros'
        ], 403);
    }
    
    // Mostrar reporte
}

4. Mensajes Descriptivos

Use mensajes de error claros:
if (!$user->hasPermission('clientes.delete')) {
    return response()->json([
        'success' => false,
        'message' => 'No tienes permiso para eliminar clientes. Contacta a tu administrador.'
    ], 403);
}

5. Logging de Intentos Denegados

Registre intentos de acceso no autorizado para auditoría:
use Illuminate\Support\Facades\Log;

if (!$user->hasPermission($permission)) {
    Log::warning('Intento de acceso denegado', [
        'user_id' => $user->id,
        'permission' => $permission,
        'ip' => $request->ip(),
        'url' => $request->fullUrl()
    ]);
    
    abort(403);
}

Troubleshooting

Permiso no funciona después de asignarlo

Causa: Cache de relaciones o sesión antigua. Solución:
  1. Refrescar la relación: $rol->load('permissions')
  2. Hacer logout y login nuevamente
  3. Verificar en base de datos: SELECT * FROM role_permission WHERE rol_id = X

Admin todavía pasa por verificación de permisos

Causa: El bypass no está antes de la verificación. Solución: Verificar que el check de rol_id == 1 esté antes de hasPermission().

403 en todas las rutas

Causa: Usuario sin rol o rol sin permisos. Solución:
  1. Verificar que el usuario tiene rol_id asignado
  2. Verificar que el rol existe: SELECT * FROM roles WHERE rol_id = X
  3. Verificar que el rol tiene permisos: SELECT * FROM role_permission WHERE rol_id = X

Permisos no se retornan en login

Causa: Relación no cargada correctamente. Solución: Verificar implementación en AuthController.php:68-76.

Extensiones Futuras

Permisos a Nivel de Registro

Para restringir acceso a registros específicos:
if (!$user->hasPermission('ventas.edit')) {
    abort(403);
}

// Verificar que la venta pertenece a la empresa del usuario
$venta = Venta::where('id', $id)
    ->where('id_empresa', $user->id_empresa)
    ->firstOrFail();

Permisos Temporales

Agregar campos de fecha a role_permission:
$rol->permissions()->attach($permissionId, [
    'expires_at' => now()->addDays(7)
]);

Permisos de Solo Lectura

Agregar campo read_only a role_permission para permitir ver pero no editar ciertos datos.

Build docs developers (and LLMs) love