feat: Agrega validaciones de autorización y nuevas clases Request

- Se agregó autorización basada en permisos en múltiples Requests.
- Nuevos Requests para motivos de cancelación y tags con validación y autorización.
- Se añadieron métodos de roles al modelo User (isDeveloper, isAdmin, isPrimary).
- Se actualizó el acceso a Telescope usando validación por roles.
- Mejora en el manejo de excepciones de autorización.
- Actualización de RoleSeeder con nuevas convenciones de permisos.
- Actualización de dependencias (composer.lock).
This commit is contained in:
Juan Felipe Zapata Moreno 2026-02-23 13:05:53 -06:00
parent 69371a0088
commit 31746867b8
38 changed files with 566 additions and 222 deletions

View File

@ -1,4 +1,7 @@
<?php namespace App\Http\Controllers\Admin;
<?php
namespace App\Http\Controllers\Admin;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
@ -24,23 +27,19 @@ public function index(UserActivityRequest $request)
{
$filters = $request->all();
$model = UserEvent::with('user:id,name,paternal,maternal,profile_photo_path,deleted_at');
if(isset($filters['user']) && !empty($filters['user'])){
$model->where('user_id', $filters['user']);
}
if(isset($filters['search']) && !empty($filters['search'])){
$model->where('event', 'like', '%'.$filters['search'].'%');
}
if(isset($filters['start_date']) && !empty($filters['start_date'])){
$model->where('created_at', '>=', "{$filters['start_date']} 00:00:00");
}
if(isset($filters['end_date']) && !empty($filters['end_date'])){
$model->where('created_at', '<=', "{$filters['end_date']} 23:59:59");
}
$model = UserEvent::with('user:id,name,paternal,maternal,profile_photo_path,deleted_at')
->when(isset($filters['user']) && !empty($filters['user']), function ($query) use ($filters) {
$query->where('user_id', $filters['user']);
})
->when(isset($filters['search']) && !empty($filters['search']), function ($query) use ($filters) {
$query->where('event', 'like', '%' . $filters['search'] . '%');
})
->when(isset($filters['start_date']) && !empty($filters['start_date']), function ($query) use ($filters) {
$query->where('created_at', '>=', "{$filters['start_date']} 00:00:00");
})
->when(isset($filters['end_date']) && !empty($filters['end_date']), function ($query) use ($filters) {
$query->where('created_at', '<=', "{$filters['end_date']} 23:59:59");
});
return ApiResponse::OK->response([
'models' =>

View File

@ -7,6 +7,7 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Roles\RoleStoreRequest;
use App\Http\Requests\Roles\RoleUpdateRequest;
use Illuminate\Routing\Controllers\HasMiddleware;
use App\Models\Role;
use App\Supports\QuerySupport;
use Illuminate\Http\Request;
@ -19,8 +20,21 @@
*
* @version 1.0.0
*/
class RoleController extends Controller
class RoleController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('roles.index', ['index']),
self::can('roles.show', ['show']),
self::can('roles.destroy', ['destroy']),
self::can('roles.permissions', ['permissions', 'updatePermissions']),
];
}
/**
* Listar
*/
@ -75,7 +89,7 @@ public function update(RoleUpdateRequest $request, Role $role)
*/
public function destroy(Role $role)
{
if (in_array($role->id, ['2'])) {
if (in_array($role->id, [1, 2])) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'No se puede eliminar este rol'
]);

View File

@ -1,7 +1,6 @@
<?php namespace App\Http\Controllers;
/**
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
*/
use Illuminate\Routing\Controllers\Middleware;
/**
* Controlador base
@ -12,5 +11,12 @@
*/
abstract class Controller
{
//
/**
* Evaluar permisos de un usuario
*/
public static function can(string $permission, array $methods): Middleware
{
return new Middleware("permission:{$permission}", only: $methods);
}
}

View File

@ -16,9 +16,20 @@
use Illuminate\Support\Facades\Log;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Routing\Controllers\HasMiddleware;
class CancellationController extends Controller
class CancellationController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('cancellations.cancel_constancia', ['cancelarConstancia']),
self::can('cancellations.cancel_tag_no_asignado', ['cancelarTagNoAsignado']),
];
}
/**
* Cancelar la constancia/tag

View File

@ -7,16 +7,30 @@
*/
use App\Http\Controllers\Controller;
use App\Http\Requests\Repuve\CancelConstanciaRequest;
use App\Http\Requests\Repuve\CatalogCancellationReasonStoreRequest;
use App\Http\Requests\Repuve\CatalogCancellationReasonUpdateRequest;
use App\Models\CatalogCancellationReason;
use Illuminate\Http\Request;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Illuminate\Routing\Controllers\HasMiddleware;
/**
* Descripción
*/
class CatalogController extends Controller
class CatalogController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('catalogs.cancellation_reasons.index', ['index']),
self::can('catalogs.cancellation_reasons.show', ['show']),
self::can('catalogs.cancellation_reasons.destroy', ['destroy']),
];
}
public function index(Request $request)
{
$type = $request->query('type');
@ -55,14 +69,9 @@ public function show($id)
]);
}
public function store(Request $request)
public function store(CatalogCancellationReasonStoreRequest $request)
{
$validated = $request->validate([
'code' => 'required|string|unique:catalog_cancellation_reasons,code',
'name' => 'required|string',
'description' => 'nullable|string',
'applies_to' => 'required|in:cancelacion,sustitucion,ambos',
]);
$validated = $request->validated();
$reason = CatalogCancellationReason::create($validated);
@ -72,7 +81,7 @@ public function store(Request $request)
]);
}
public function update(Request $request, $id)
public function update(CatalogCancellationReasonUpdateRequest $request, $id)
{
$reason = CatalogCancellationReason::find($id);
@ -82,11 +91,7 @@ public function update(Request $request, $id)
]);
}
$validated = $request->validate([
'name' => 'required|string',
'description' => 'nullable|string',
'applies_to' => 'required|in:cancelacion,sustitucion,ambos',
]);
$validated = $request->validated();
$reason->update($validated);

View File

@ -4,12 +4,24 @@
use App\Http\Requests\Repuve\CatalogNameImgStoreRequest;
use App\Http\Requests\Repuve\CatalogNameImgUpdateRequest;
use Notsoweb\LaravelCore\Controllers\VueController;
use App\Http\Controllers\Controller;
use App\Models\CatalogNameImg;
use Illuminate\Routing\Controllers\HasMiddleware;
use Notsoweb\ApiResponse\Enums\ApiResponse;
class CatalogNameImgController extends VueController
class CatalogNameImgController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('catalogs.name_img.index', ['index']),
self::can('catalogs.name_img.destroy', ['destroy']),
];
}
/**
* Listar
*/

View File

@ -12,9 +12,21 @@
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log;
use Illuminate\Routing\Controllers\HasMiddleware;
class DeviceController extends Controller
class DeviceController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
self::can('devices.index', ['index']),
self::can('devices.show', ['show']),
self::can('devices.destroy', ['destroy']),
self::can('devices.toggle_status', ['toggleStatus']),
];
}
public function index(Request $request)
{
try {

View File

@ -21,13 +21,28 @@
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use Illuminate\Support\Facades\Auth;
use Illuminate\Routing\Controllers\HasMiddleware;
/**
* Descripción
*/
class ExcelController extends Controller
class ExcelController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('reports.vehicle_updates.index', ['vehicleActualizaciones']),
self::can('reports.substitutions.index', ['constanciasSustituidas']),
self::can('reports.cancellations.index', ['constanciasCanceladas']),
self::can('reports.general.index', ['excelGeneral']),
self::can('reports.search_records.index', ['exportSearchRecords']),
];
}
public function vehicleActualizaciones(Request $request)
{

View File

@ -19,9 +19,21 @@
use App\Services\RepuveService;
use App\Services\PadronEstatalService;
use App\Jobs\ProcessRepuveResponse;
use Illuminate\Routing\Controllers\HasMiddleware;
class InscriptionController extends Controller
class InscriptionController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('repuve.search_records', ['searchRecord']),
self::can('repuve.check_stolen', ['stolen']),
];
}
private RepuveService $repuveService;
private PadronEstatalService $padronEstatalService;

View File

@ -6,15 +6,28 @@
use App\Http\Requests\Repuve\ModuleStoreRequest;
use App\Http\Requests\Repuve\ModuleUpdateRequest;
use App\Models\Module;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Illuminate\Routing\Controllers\HasMiddleware;
class ModuleController extends Controller
class ModuleController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('modules.index', ['index']),
self::can('modules.show', ['show']),
self::can('modules.destroy', ['destroy']),
self::can('modules.toggle_status', ['toggleStatus']),
];
}
/**
* Listar módulos existentes
*/

View File

@ -14,9 +14,23 @@
use Illuminate\Database\QueryException;
use App\Models\Package;
use App\Models\Tag;
use Illuminate\Routing\Controllers\HasMiddleware;
class PackageController extends Controller
class PackageController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('packages.index', ['index']),
self::can('packages.show', ['show']),
self::can('packages.destroy', ['destroy']),
self::can('packages.box_tags', ['getBoxTags']),
];
}
public function index(Request $request)
{

View File

@ -11,9 +11,22 @@
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Codedge\Fpdf\Fpdf\Fpdf;
use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
class RecordController extends Controller
class RecordController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('records.index', ['index']),
self::can('records.show', ['show']),
self::can('records.generate_pdf', ['generatePdf', 'generatePdfVerification', 'generatePdfConstancia', 'generatePdfImages', 'generatePdfForm']),
];
}
public function generatePdf($id)
{
$record = Record::with('vehicle.owner', 'user', 'module')->findOrFail($id);

View File

@ -3,6 +3,8 @@
namespace App\Http\Controllers\Repuve;
use App\Http\Controllers\Controller;
use App\Http\Requests\Repuve\TagStoreRequest;
use App\Http\Requests\Repuve\TagUpdateRequest;
use App\Models\CatalogTagStatus;
use App\Models\Module;
use App\Models\Package;
@ -15,9 +17,21 @@
use Carbon\Carbon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
use Illuminate\Routing\Controllers\HasMiddleware;
class TagsController extends Controller
class TagsController extends Controller implements HasMiddleware
{
public static function middleware(): array
{
return [
self::can('tags.index', ['index']),
self::can('tags.create', ['tagStore']),
self::can('tags.assign_to_module', ['assignToModule']),
self::can('tags.show', ['show']),
self::can('tags.destroy', ['destroy']),
];
}
public function index(Request $request)
{
try {
@ -69,15 +83,10 @@ public function index(Request $request)
}
}
public function store(Request $request)
public function store(TagStoreRequest $request)
{
try {
$validated = $request->validate([
'folio' => 'required|string|max:8',
'package_id' => 'required|integer|exists:packages,id',
'tag_number' => 'nullable|string|min:32|max:32',
'module_id' => 'nullable|integer|exists:modules,id',
]);
$validated = $request->validated();
// Verificar si ya existe un tag con el mismo folio
$existingTagByFolio = Tag::where('folio', $validated['folio'])->first();
@ -232,12 +241,6 @@ public function store(Request $request)
}
return ApiResponse::CREATED->response($response);
} catch (ValidationException $e) {
return ApiResponse::BAD_REQUEST->response([
'message' => 'No se pudo crear el tag: Datos de validación incorrectos.',
'error' => 'validation_error',
'errors' => $e->errors(),
]);
} catch (\Exception $e) {
DB::rollBack();
@ -292,7 +295,7 @@ public function show(Tag $tag)
]);
}
public function update(Request $request, Tag $tag)
public function update(TagUpdateRequest $request, Tag $tag)
{
try {
// Validar que el tag solo pueda actualizarse si está disponible o cancelado
@ -304,14 +307,7 @@ public function update(Request $request, Tag $tag)
]);
}
// Validar los campos de entrada
$validated = $request->validate([
'folio' => 'sometimes|string|max:8',
'tag_number' => 'nullable|string|min:32|max:32',
'package_id' => 'sometimes|integer|exists:packages,id',
'module_id' => 'nullable|integer|exists:modules,id',
'status_id' => 'sometimes|integer|exists:catalog_tag_status,id',
]);
$validated = $request->validated();
// Si se va a cambiar el status, validar que solo sea a disponible o cancelado
if (isset($validated['status_id'])) {

View File

@ -22,9 +22,22 @@
use Illuminate\Support\Facades\Auth;
use App\Jobs\ProcessRepuveResponse;
use App\Models\CatalogTagStatus;
use Illuminate\Routing\Controllers\HasMiddleware;
class UpdateController extends Controller
class UpdateController extends Controller implements HasMiddleware
{
/**
* Middleware
*/
public static function middleware(): array
{
return [
self::can('records.update', ['updateData']),
self::can('records.update', ['tagSubstitution']),
];
}
private RepuveService $repuveService;
private PadronEstatalService $padronEstatalService;

View File

@ -11,7 +11,7 @@ class CancelConstanciaRequest extends FormRequest
*/
public function authorize(): bool
{
return true;
return auth()->user()->hasPermissionTo('cancellations.cancel_constancia');
}
/**

View File

@ -0,0 +1,43 @@
<?php namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class CatalogCancellationReasonStoreRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->user()->can('catalogs.cancellation_reasons.create');
}
public function rules(): array
{
return [
'code' => ['required', 'string', 'unique:catalog_cancellation_reasons,code'],
'name' => ['required', 'string'],
'description' => ['nullable', 'string'],
'applies_to' => ['required', 'in:cancelacion,sustitucion,ambos'],
];
}
public function messages(): array
{
return [
'code.required' => 'El código es obligatorio.',
'code.unique' => 'El código ya existe.',
'name.required' => 'El nombre es obligatorio.',
'applies_to.required' => 'El tipo de aplicación es obligatorio.',
'applies_to.in' => 'El tipo de aplicación debe ser: cancelacion, sustitucion o ambos.',
];
}
public function attributes(): array
{
return [
'code' => 'código',
'name' => 'nombre',
'description' => 'descripción',
'applies_to' => 'aplica a',
];
}
}

View File

@ -0,0 +1,39 @@
<?php namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class CatalogCancellationReasonUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->user()->can('catalogs.cancellation_reasons.edit');
}
public function rules(): array
{
return [
'name' => ['required', 'string'],
'description' => ['nullable', 'string'],
'applies_to' => ['required', 'in:cancelacion,sustitucion,ambos'],
];
}
public function messages(): array
{
return [
'name.required' => 'El nombre es obligatorio.',
'applies_to.required' => 'El tipo de aplicación es obligatorio.',
'applies_to.in' => 'El tipo de aplicación debe ser: cancelacion, sustitucion o ambos.',
];
}
public function attributes(): array
{
return [
'name' => 'nombre',
'description' => 'descripción',
'applies_to' => 'aplica a',
];
}
}

View File

@ -7,7 +7,7 @@ class CatalogNameImgStoreRequest extends FormRequest
public function authorize(): bool
{
return true;
return auth()->user()->can('catalogs.name_img.create');
}
public function rules(): array

View File

@ -7,7 +7,7 @@ class CatalogNameImgUpdateRequest extends FormRequest
public function authorize(): bool
{
return true;
return auth()->user()->can('catalogs.name_img.edit');
}
public function rules(): array

View File

@ -6,7 +6,7 @@ class DeviceStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('devices.create');
}
public function rules(): array

View File

@ -8,7 +8,7 @@ class DeviceUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('devices.edit');
}
public function rules(): array

View File

@ -9,7 +9,7 @@ class ModuleStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('modules.create');
}
public function rules(): array

View File

@ -9,7 +9,7 @@ class ModuleUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('modules.edit');
}
public function rules(): array

View File

@ -7,7 +7,7 @@ class PackageStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('packages.create');
}
public function rules(): array

View File

@ -8,7 +8,7 @@ class PackageUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('packages.edit');
}
public function rules(): array

View File

@ -6,7 +6,7 @@ class RecordSearchRequest extends FormRequest
{
public function authorize(): bool
{
return true;
return auth()->user()->can('repuve.records.search');
}
public function rules(): array

View File

@ -0,0 +1,44 @@
<?php namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class TagStoreRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->user()->can('tags.create');
}
public function rules(): array
{
return [
'folio' => ['required', 'string', 'max:8'],
'package_id' => ['required', 'integer', 'exists:packages,id'],
'tag_number' => ['nullable', 'string', 'min:32', 'max:32'],
'module_id' => ['nullable', 'integer', 'exists:modules,id'],
];
}
public function messages(): array
{
return [
'folio.required' => 'El folio es obligatorio.',
'folio.max' => 'El folio no puede tener más de 8 caracteres.',
'package_id.required' => 'La caja es obligatoria.',
'package_id.exists' => 'La caja seleccionada no existe.',
'tag_number.min' => 'El número de constancia debe tener exactamente 32 caracteres.',
'tag_number.max' => 'El número de constancia debe tener exactamente 32 caracteres.',
'module_id.exists' => 'El módulo seleccionado no existe.',
];
}
public function attributes(): array
{
return [
'folio' => 'folio',
'package_id' => 'caja',
'tag_number' => 'número de constancia',
'module_id' => 'módulo',
];
}
}

View File

@ -0,0 +1,45 @@
<?php namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class TagUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return auth()->user()->can('tags.edit');
}
public function rules(): array
{
return [
'folio' => ['sometimes', 'string', 'max:8'],
'tag_number' => ['nullable', 'string', 'min:32', 'max:32'],
'package_id' => ['sometimes', 'integer', 'exists:packages,id'],
'module_id' => ['nullable', 'integer', 'exists:modules,id'],
'status_id' => ['sometimes', 'integer', 'exists:catalog_tag_status,id'],
];
}
public function messages(): array
{
return [
'folio.max' => 'El folio no puede tener más de 8 caracteres.',
'tag_number.min' => 'El número de constancia debe tener exactamente 32 caracteres.',
'tag_number.max' => 'El número de constancia debe tener exactamente 32 caracteres.',
'package_id.exists' => 'La caja seleccionada no existe.',
'module_id.exists' => 'El módulo seleccionado no existe.',
'status_id.exists' => 'El estado seleccionado no existe.',
];
}
public function attributes(): array
{
return [
'folio' => 'folio',
'tag_number' => 'número de constancia',
'package_id' => 'caja',
'module_id' => 'módulo',
'status_id' => 'estado',
];
}
}

View File

@ -7,7 +7,7 @@ class VehicleStoreRequest extends FormRequest
public function authorize(): bool
{
return true;
return auth()->user()->can('vehicles.create');
}
public function rules(): array

View File

@ -9,7 +9,7 @@ class VehicleUpdateRequest extends FormRequest
public function authorize(): bool
{
return true;
return auth()->user()->can('vehicles.edit');
}
public function rules(): array

View File

@ -21,7 +21,7 @@ class RoleStoreRequest extends FormRequest
*/
public function authorize(): bool
{
return auth()->user()->hasPermissionTo('roles.create');
return auth()->user()->can('roles.create');
}
/**

View File

@ -21,7 +21,7 @@ class RoleUpdateRequest extends FormRequest
*/
public function authorize(): bool
{
return auth()->user()->hasPermissionTo('roles.edit');
return auth()->user()->can('roles.edit');
}
/**
@ -39,8 +39,10 @@ public function rules(): array
*/
protected function passedValidation()
{
if(!in_array($this->route('role')->id, [1, 2])) {
$this->merge([
'name' => Str::slug($this->description),
]);
}
}
}

View File

@ -19,7 +19,7 @@ class UserActivityRequest extends FormRequest
*/
public function authorize(): bool
{
return true;
return auth()->user()->can('activities.index');
}
/**

View File

@ -48,7 +48,6 @@ class User extends Authenticatable
'username',
'phone',
'password',
//'profile_photo_path',
'module_id'
];
@ -58,6 +57,7 @@ class User extends Authenticatable
protected $hidden = [
'password',
'remember_token',
'profile_photo_path'
];
/**
@ -136,6 +136,30 @@ public function module()
return $this->belongsTo(Module::class);
}
/**
* Preguntar si el usuario es desarrollador
*/
public function isDeveloper(): bool
{
return $this->hasRole(Role::find(1));
}
/**
* Preguntar si el usuario es administrador
*/
public function isAdmin(): bool
{
return $this->hasRole(Role::find(2));
}
/**
* Preguntar si el usuario es primario (privilegios elevados)
*/
public function isPrimary(): bool
{
return $this->hasRole(Role::find(1), Role::find(2));
}
/**
* Módulo del cual el usuario es responsable
*/

View File

@ -65,7 +65,7 @@ protected function hideSensitiveRequestDetails(): void
protected function gate(): void
{
Gate::define('viewTelescope', function (User $user) {
return $user->hasRole('developer');
return $user->isDeveloper();
});
}
}

View File

@ -8,6 +8,8 @@
use Illuminate\Session\Middleware\StartSession;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use Notsoweb\LaravelCore\Http\APIException;
use Spatie\Permission\Exceptions\UnauthorizedException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
return Application::configure(basePath: dirname(__DIR__))
@ -44,6 +46,20 @@
return ApiResponse::SERVICE_UNAVAILABLE->response();
}
});
$exceptions->render(function (UnauthorizedException $e, Request $request) {
if ($request->is('api/*')) {
return ApiResponse::UNPROCESSABLE_CONTENT->response([
'message' => $e->getMessage()
]);
}
});
$exceptions->render(function (AccessDeniedHttpException $e, Request $request) {
if ($request->is('api/*')) {
return ApiResponse::UNPROCESSABLE_CONTENT->response([
'message' => __($e->getMessage())
]);
}
});
$exceptions->render(APIException::notFound(...));
$exceptions->render(APIException::unauthorized(...));
$exceptions->render(APIException::unprocessableContent(...));

184
composer.lock generated
View File

@ -1782,16 +1782,16 @@
},
{
"name": "laravel/framework",
"version": "v12.51.0",
"version": "v12.52.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "ce4de3feb211e47c4f959d309ccf8a2733b1bc16"
"reference": "d5511fa74f4608dbb99864198b1954042aa8d5a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/ce4de3feb211e47c4f959d309ccf8a2733b1bc16",
"reference": "ce4de3feb211e47c4f959d309ccf8a2733b1bc16",
"url": "https://api.github.com/repos/laravel/framework/zipball/d5511fa74f4608dbb99864198b1954042aa8d5a7",
"reference": "d5511fa74f4608dbb99864198b1954042aa8d5a7",
"shasum": ""
},
"require": {
@ -2000,7 +2000,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2026-02-10T18:20:19+00:00"
"time": "2026-02-17T17:07:04+00:00"
},
{
"name": "laravel/passport",
@ -3881,16 +3881,16 @@
},
{
"name": "nette/schema",
"version": "v1.3.4",
"version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7"
"reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7",
"reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7",
"url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002",
"reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002",
"shasum": ""
},
"require": {
@ -3898,8 +3898,10 @@
"php": "8.1 - 8.5"
},
"require-dev": {
"nette/phpstan-rules": "^1.0",
"nette/tester": "^2.6",
"phpstan/phpstan": "^2.0@stable",
"phpstan/extension-installer": "^1.4@stable",
"phpstan/phpstan": "^2.1.39@stable",
"tracy/tracy": "^2.8"
},
"type": "library",
@ -3940,9 +3942,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.3.4"
"source": "https://github.com/nette/schema/tree/v1.3.5"
},
"time": "2026-02-08T02:54:00+00:00"
"time": "2026-02-23T03:47:12+00:00"
},
{
"name": "nette/utils",
@ -4180,31 +4182,31 @@
},
{
"name": "nunomaduro/termwind",
"version": "v2.3.3",
"version": "v2.4.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/termwind.git",
"reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017"
"reference": "712a31b768f5daea284c2169a7d227031001b9a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/6fb2a640ff502caace8e05fd7be3b503a7e1c017",
"reference": "6fb2a640ff502caace8e05fd7be3b503a7e1c017",
"url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8",
"reference": "712a31b768f5daea284c2169a7d227031001b9a8",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^8.2",
"symfony/console": "^7.3.6"
"symfony/console": "^7.4.4 || ^8.0.4"
},
"require-dev": {
"illuminate/console": "^11.46.1",
"laravel/pint": "^1.25.1",
"illuminate/console": "^11.47.0",
"laravel/pint": "^1.27.1",
"mockery/mockery": "^1.6.12",
"pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.1.3",
"pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2",
"phpstan/phpstan": "^1.12.32",
"phpstan/phpstan-strict-rules": "^1.6.2",
"symfony/var-dumper": "^7.3.5",
"symfony/var-dumper": "^7.3.5 || ^8.0.4",
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@ -4236,7 +4238,7 @@
"email": "enunomaduro@gmail.com"
}
],
"description": "Its like Tailwind CSS, but for the console.",
"description": "It's like Tailwind CSS, but for the console.",
"keywords": [
"cli",
"console",
@ -4247,7 +4249,7 @@
],
"support": {
"issues": "https://github.com/nunomaduro/termwind/issues",
"source": "https://github.com/nunomaduro/termwind/tree/v2.3.3"
"source": "https://github.com/nunomaduro/termwind/tree/v2.4.0"
},
"funding": [
{
@ -4263,7 +4265,7 @@
"type": "github"
}
],
"time": "2025-11-20T02:34:59+00:00"
"time": "2026-02-16T23:10:27+00:00"
},
{
"name": "nyholm/psr7",
@ -6286,33 +6288,35 @@
},
{
"name": "sabberworm/php-css-parser",
"version": "v9.1.0",
"version": "v9.2.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb"
"reference": "59373045e11ad47b5c18fc615feee0219e42f6d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb",
"reference": "1b363fdbdc6dd0ca0f4bf98d3a4d7f388133f1fb",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/59373045e11ad47b5c18fc615feee0219e42f6d3",
"reference": "59373045e11ad47b5c18fc615feee0219e42f6d3",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.3"
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "1.12.28 || 2.1.25",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.7",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.6",
"phpunit/phpunit": "8.5.46",
"phpstan/phpstan": "1.12.32 || 2.1.32",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.8",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7",
"phpunit/phpunit": "8.5.52",
"rawr/phpunit-data-provider": "3.3.1",
"rector/rector": "1.2.10 || 2.1.7",
"rector/type-perfect": "1.0.0 || 2.1.0"
"rector/rector": "1.2.10 || 2.2.8",
"rector/type-perfect": "1.0.0 || 2.1.0",
"squizlabs/php_codesniffer": "4.0.1",
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
@ -6320,10 +6324,14 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.2.x-dev"
"dev-main": "9.3.x-dev"
}
},
"autoload": {
"files": [
"src/Rule/Rule.php",
"src/RuleSet/RuleContainer.php"
],
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
@ -6354,9 +6362,9 @@
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.1.0"
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.2.0"
},
"time": "2025-09-14T07:37:21+00:00"
"time": "2026-02-21T17:12:03+00:00"
},
{
"name": "setasign/fpdf",
@ -6489,25 +6497,24 @@
},
{
"name": "spatie/simple-excel",
"version": "3.8.1",
"version": "3.9.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/simple-excel.git",
"reference": "80c2fd16090d28e1d0036bfac1afc6bfd8452ea3"
"reference": "67095053ff6037284fd213abd84259ea4faca7aa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/simple-excel/zipball/80c2fd16090d28e1d0036bfac1afc6bfd8452ea3",
"reference": "80c2fd16090d28e1d0036bfac1afc6bfd8452ea3",
"url": "https://api.github.com/repos/spatie/simple-excel/zipball/67095053ff6037284fd213abd84259ea4faca7aa",
"reference": "67095053ff6037284fd213abd84259ea4faca7aa",
"shasum": ""
},
"require": {
"illuminate/support": "^9.0|^10.0|^11.0|^12.0",
"illuminate/support": "^9.0|^10.0|^11.0|^12.0|^13.0",
"openspout/openspout": "^4.30",
"php": "^8.3"
},
"require-dev": {
"pestphp/pest-plugin-laravel": "^1.3|^2.3|^3.0",
"phpunit/phpunit": "^9.4|^10.5|^11.0|^12.0",
"spatie/pest-plugin-snapshots": "^1.1|^2.1",
"spatie/phpunit-snapshot-assertions": "^4.0|^5.1",
@ -6538,7 +6545,7 @@
"spatie"
],
"support": {
"source": "https://github.com/spatie/simple-excel/tree/3.8.1"
"source": "https://github.com/spatie/simple-excel/tree/3.9.0"
},
"funding": [
{
@ -6546,7 +6553,7 @@
"type": "github"
}
],
"time": "2025-09-24T06:40:28+00:00"
"time": "2026-02-22T08:49:24+00:00"
},
{
"name": "symfony/clock",
@ -9139,16 +9146,16 @@
},
{
"name": "thecodingmachine/safe",
"version": "v3.3.0",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236"
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/2cdd579eeaa2e78e51c7509b50cc9fb89a956236",
"reference": "2cdd579eeaa2e78e51c7509b50cc9fb89a956236",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
"shasum": ""
},
"require": {
@ -9258,7 +9265,7 @@
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v3.3.0"
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
},
"funding": [
{
@ -9269,25 +9276,29 @@
"url": "https://github.com/shish",
"type": "github"
},
{
"url": "https://github.com/silasjoisten",
"type": "github"
},
{
"url": "https://github.com/staabm",
"type": "github"
}
],
"time": "2025-05-14T06:15:44+00:00"
"time": "2026-02-04T18:08:13+00:00"
},
{
"name": "tightenco/ziggy",
"version": "v2.6.0",
"version": "v2.6.1",
"source": {
"type": "git",
"url": "https://github.com/tighten/ziggy.git",
"reference": "cccc6035c109daab03a33926b3a8499bedbed01f"
"reference": "6b9d38415b684b798ba25ae73324f6652ff15314"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tighten/ziggy/zipball/cccc6035c109daab03a33926b3a8499bedbed01f",
"reference": "cccc6035c109daab03a33926b3a8499bedbed01f",
"url": "https://api.github.com/repos/tighten/ziggy/zipball/6b9d38415b684b798ba25ae73324f6652ff15314",
"reference": "6b9d38415b684b798ba25ae73324f6652ff15314",
"shasum": ""
},
"require": {
@ -9297,9 +9308,9 @@
},
"require-dev": {
"laravel/folio": "^1.1",
"orchestra/testbench": "^7.0 || ^8.0 || ^9.0 || ^10.0",
"pestphp/pest": "^2.26|^3.0",
"pestphp/pest-plugin-laravel": "^2.4|^3.0"
"orchestra/testbench": "^8.0 || ^9.0 || ^10.0",
"pestphp/pest": "^2.0 || ^3.0 || ^4.0",
"pestphp/pest-plugin-laravel": "^2.0 || ^3.0 || ^4.0"
},
"type": "library",
"extra": {
@ -9342,9 +9353,9 @@
],
"support": {
"issues": "https://github.com/tighten/ziggy/issues",
"source": "https://github.com/tighten/ziggy/tree/v2.6.0"
"source": "https://github.com/tighten/ziggy/tree/v2.6.1"
},
"time": "2025-09-15T00:00:26+00:00"
"time": "2026-02-16T20:20:46+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@ -10169,39 +10180,36 @@
},
{
"name": "nunomaduro/collision",
"version": "v8.8.3",
"version": "v8.9.1",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
"reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4"
"reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/1dc9e88d105699d0fee8bb18890f41b274f6b4c4",
"reference": "1dc9e88d105699d0fee8bb18890f41b274f6b4c4",
"url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935",
"reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935",
"shasum": ""
},
"require": {
"filp/whoops": "^2.18.1",
"nunomaduro/termwind": "^2.3.1",
"filp/whoops": "^2.18.4",
"nunomaduro/termwind": "^2.4.0",
"php": "^8.2.0",
"symfony/console": "^7.3.0"
"symfony/console": "^7.4.4 || ^8.0.4"
},
"conflict": {
"laravel/framework": "<11.44.2 || >=13.0.0",
"phpunit/phpunit": "<11.5.15 || >=13.0.0"
"laravel/framework": "<11.48.0 || >=14.0.0",
"phpunit/phpunit": "<11.5.50 || >=14.0.0"
},
"require-dev": {
"brianium/paratest": "^7.8.3",
"larastan/larastan": "^3.4.2",
"laravel/framework": "^11.44.2 || ^12.18",
"laravel/pint": "^1.22.1",
"laravel/sail": "^1.43.1",
"laravel/sanctum": "^4.1.1",
"laravel/tinker": "^2.10.1",
"orchestra/testbench-core": "^9.12.0 || ^10.4",
"pestphp/pest": "^3.8.2 || ^4.0.0",
"sebastian/environment": "^7.2.1 || ^8.0"
"brianium/paratest": "^7.8.5",
"larastan/larastan": "^3.9.2",
"laravel/framework": "^11.48.0 || ^12.52.0",
"laravel/pint": "^1.27.1",
"orchestra/testbench-core": "^9.12.0 || ^10.9.0",
"pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0",
"sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0"
},
"type": "library",
"extra": {
@ -10264,7 +10272,7 @@
"type": "patreon"
}
],
"time": "2025-11-20T02:55:25+00:00"
"time": "2026-02-17T17:33:08+00:00"
},
{
"name": "phar-io/manifest",
@ -10733,16 +10741,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.53",
"version": "11.5.55",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607"
"reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a997a653a82845f1240d73ee73a8a4e97e4b0607",
"reference": "a997a653a82845f1240d73ee73a8a4e97e4b0607",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00",
"reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00",
"shasum": ""
},
"require": {
@ -10815,7 +10823,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.53"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55"
},
"funding": [
{
@ -10839,7 +10847,7 @@
"type": "tidelift"
}
],
"time": "2026-02-10T12:28:25+00:00"
"time": "2026-02-18T12:37:06+00:00"
},
{
"name": "sebastian/cli-parser",
@ -12017,5 +12025,5 @@
"php": "^8.3"
},
"platform-dev": {},
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.6.0"
}

View File

@ -61,10 +61,10 @@ public function run(): void
$moduleCreate,
$moduleEdit,
$moduleDestroy
] = $this->onCRUD('module', $modules, 'api');
] = $this->onCRUD('modules', $modules, 'api');
$moduleToggleStatus = $this->onPermission(
'module.toggle-status',
'modules.toggle_status',
'Cambiar estado del módulo',
$modules,
'api'
@ -106,20 +106,14 @@ public function run(): void
'api'
);
$inscriptionStolen = $this->onPermission(
'inscription.stolen',
'Marcar como robado',
$inscriptions,
'api'
);
$cancellations = PermissionType::firstOrCreate([
'name' => 'Cancelaciones'
]);
$cancellationCancel = $this->onPermission(
'cancellations.cancel',
'Cancelar constancia',
$cancellationTagNoAsignado = $this->onPermission(
'cancellations.tag_no_asignado',
'Cancelar constancia no asignada',
$cancellations,
'api'
);
@ -147,35 +141,35 @@ public function run(): void
]);
$recordGeneratePdf = $this->onPermission(
'records.generate-pdf',
'Generar PDF de expediente',
'records.generate_pdf',
'Generar Hoja de recepción',
$records,
'api'
);
$recordGeneratePdfVerification = $this->onPermission(
'records.generate-pdf-verification',
'Generar PDF de verificación',
'records.generate_pdf_verification',
'Generar Hoja de verificación',
$records,
'api'
);
$recordGeneratePdfConstancia = $this->onPermission(
'records.generate-pdf-constancia',
'Generar PDF de constancia',
'records.generate_pdf_constancia',
'Generar Impresión en la constancia',
$records,
'api'
);
$recordGeneratePdfSubtitution = $this->onPermission(
'records.generate-pdf-substitution',
'Generar PDF constancia sustitución',
'records.generate_pdf_substitution',
'Generar Solicitud de sustitución',
$records,
'api'
);
$recordGeneratePdfDamaged = $this->onPermission(
'records.generate-pdf-damaged',
'records.generate_pdf_damaged',
'Generar PDF constancia dañada',
$records,
'api'
@ -204,7 +198,7 @@ public function run(): void
] = $this->onCRUD('tags', $tags, 'api');
$tagAssignToModule = $this->onPermission(
'tags.assign-to-module',
'tags.assign_to_module',
'Asignar etiquetas a módulo',
$tags,
'api'
@ -233,7 +227,6 @@ public function run(): void
$roleCreate,
$roleEdit,
$roleDestroy,
$moduleIndex, //Módulos
$moduleCreate,
$moduleEdit,
@ -246,8 +239,7 @@ public function run(): void
$inscriptionVehicle, //Inscripcion de vehículos
$inscriptionSearch,
$inscriptionBusqueda,
$inscriptionStolen,
$cancellationCancel, //Cancelacion de constancia
$cancellationTagNoAsignado, //Cancelacion de constancia no asignada
$updateVehicleData, //Actualizaciones de vehículo
$updateVehicleUpdate,
$recordGeneratePdf, //Expedientes
@ -281,8 +273,6 @@ public function run(): void
$deviceDestroy,
$inscriptionVehicle, //Inscripcion de vehículos
$inscriptionSearch,
$inscriptionStolen,
$cancellationCancel, //Cancelacion de constancia
$updateVehicleData, //Actualizaciones de vehículo
$updateVehicleUpdate,
$recordGeneratePdf, //Expedientes
@ -310,7 +300,6 @@ public function run(): void
$userIndex,
$inscriptionVehicle, //Inscripcion de vehículos
$inscriptionSearch,
$inscriptionStolen,
$updateVehicleData, //Actualizaciones de vehículo
$updateVehicleUpdate,
);
@ -324,7 +313,6 @@ public function run(): void
$userIndex,
$inscriptionVehicle, //Inscripcion de vehículos
$inscriptionSearch,
$inscriptionStolen,
$updateVehicleData, //Actualizaciones de vehículo
$updateVehicleUpdate,
);