diff --git a/app/Http/Controllers/Repuve/DeviceController.php b/app/Http/Controllers/Repuve/DeviceController.php index 8ee97ff..03dba37 100644 --- a/app/Http/Controllers/Repuve/DeviceController.php +++ b/app/Http/Controllers/Repuve/DeviceController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use Notsoweb\ApiResponse\Enums\ApiResponse; use App\Http\Requests\Repuve\DeviceStoreRequest; +use App\Http\Requests\Repuve\DeviceUpdateRequest; use App\Models\Device; use App\Models\DeviceModule; use Illuminate\Support\Facades\DB; @@ -17,12 +18,6 @@ public function index(Request $request) try { $query = Device::query(); - if (!$request->filled('serie') && !$request->filled('brand')) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'Debe proporcionar al menos uno de los siguientes parámetros: serie o marca.' - ]); - } - if ($request->filled('serie')) { $query->where('serie', 'LIKE', '%' . $request->input('serie') . '%'); } @@ -86,7 +81,7 @@ public function store(DeviceStoreRequest $request) try { DB::beginTransaction(); - // 1. Crear el dispositivo + // Crear el dispositivo $device = Device::create([ 'brand' => $request->input('brand'), 'serie' => $request->input('serie'), @@ -94,7 +89,7 @@ public function store(DeviceStoreRequest $request) 'status' => $request->input('status', true), ]); - // 2. Asignar módulo y usuarios usando el modelo DeviceModule + // Asignar módulo y usuarios usando el modelo DeviceModule $userIds = $request->input('user_id'); foreach ($userIds as $userId) { @@ -127,4 +122,80 @@ public function store(DeviceStoreRequest $request) ]); } } + + public function update(DeviceUpdateRequest $request, $id) + { + try { + + DB::beginTransaction(); + + $device = Device::findOrFail($id); + + $device->update($request->only(['brand', 'serie', 'mac_address', 'status'])); + + DeviceModule::where('device_id', $device->id)->delete(); + + $userIds = $request->input('user_id'); + foreach ($userIds as $userId) { + DeviceModule::create([ + 'device_id' => $device->id, + 'module_id' => $request->module_id, + 'user_id' => $userId, + 'status' => true, + ]); + } + + DB::commit(); + + $device->load(['deviceModules.module', 'deviceModules.user']); + + return ApiResponse::OK->response([ + 'message' => 'Dispositivo actualizado exitosamente.', + 'device' => [ + 'id' => $device->id, + 'brand' => $device->brand, + 'serie' => $device->serie, + 'mac_address' => $device->mac_address, + 'status' => $device->status, + 'module' => $device->deviceModules->first()?->module, + 'authorized_users' => $device->deviceModules + ->filter(fn($dm) => $dm->user !== null) + ->map(fn($dm) => [ + 'id' => $dm->user->id, + 'name' => $dm->user->full_name, + 'email' => $dm->user->email, + ]) + ->unique('id') + ->values(), + ], + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al actualizar el dispositivo.', + 'error' => $e->getMessage(), + ]); + } + } + + public function destroy($id) + { + try { + DB::beginTransaction(); + + $device = Device::findOrFail($id); + $device->delete(); + + DB::commit(); + + return ApiResponse::OK->response([ + 'message' => 'Dispositivo eliminado exitosamente.', + ]); + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al eliminar el dispositivo.', + 'error' => $e->getMessage(), + ]); + } + } } diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index 0d05515..a93ad4c 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -15,6 +15,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Auth; +use Illuminate\Validation\ValidationException; class InscriptionController extends Controller { @@ -86,7 +87,7 @@ public function vehicleInscription(VehicleStoreRequest $request) 'placa' => $vehicleData['PLACA'], 'numero_serie' => $vehicleData['NO_SERIE'], 'rfc' => $vehicleData['RFC'], - 'folio' => $folio, // Folio del request + 'folio' => $folio, 'vigencia' => $vehicleData['VIGENCIA'], 'fecha_impresion' => $vehicleData['FECHA_IMPRESION'], 'qr_hash' => $vehicleData['QR_HASH'], @@ -245,12 +246,6 @@ public function vehicleInscription(VehicleStoreRequest $request) } catch (\Exception $e) { DB::rollBack(); - Log::error('Error en inscripcionVehiculo: ' . $e->getMessage(), [ - 'folio' => $folio ?? null, - 'tag_id' => $tagId ?? null, - 'trace' => $e->getTraceAsString() - ]); - return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al procesar la inscripción del vehículo', 'error' => $e->getMessage(), @@ -554,7 +549,7 @@ public function listTags(Request $request) ], ]); - } catch (\Illuminate\Validation\ValidationException $e) { + } catch (ValidationException $e) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error de validación', 'errors' => $e->errors(), diff --git a/app/Http/Controllers/Repuve/ModuleController.php b/app/Http/Controllers/Repuve/ModuleController.php index 79a1898..b5a9075 100644 --- a/app/Http/Controllers/Repuve/ModuleController.php +++ b/app/Http/Controllers/Repuve/ModuleController.php @@ -7,7 +7,6 @@ use App\Http\Requests\Repuve\ModuleUpdateRequest; use App\Models\Module; use App\Models\User; -use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -24,13 +23,6 @@ public function index(Request $request) try { $query = Module::query('responsible')->withCount(['packages']); - // Validar que se envíe al menos un parámetro de búsqueda - if (!$request->filled('name') && !$request->filled('municipality')) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'Debe proporcionar al menos uno de los siguientes parámetros: nombre o municipio.' - ]); - } - // Filtro por nombre if ($request->filled('name')) { $query->where('name', 'like', '%' . $request->input('name') . '%'); @@ -45,9 +37,8 @@ public function index(Request $request) $query->withCount(['packages']); // Ordenar - $sortBy = $request->input('sort_by', 'created_at'); - $sortOrder = $request->input('sort_order', 'desc'); - $query->orderBy($sortBy, $sortOrder); + $sortOrder = $request->input('sort_order', 'ASC'); + $query->orderBy('id', $sortOrder); // Paginación $perPage = $request->input('per_page', 20); @@ -120,17 +111,15 @@ public function store(ModuleStoreRequest $request) return ApiResponse::CREATED->response([ 'message' => 'Módulo creado exitosamente', 'module' => [ - 'id' => $module->id, 'name' => $module->name, - 'municipality' => $module->municipality, 'responsible_id' => $module->responsible_id, + 'municipality' => $module->municipality, 'address' => $module->address, 'colony' => $module->colony, 'cp' => $module->cp, 'longitude' => $module->longitude, 'latitude' => $module->latitude, - 'status' => $module->status, - 'status_text' => $module->status ? 'Activo' : 'Inactivo', + 'status' => $module->status ? 'Activo' : 'Inactivo', 'created_at' => $module->created_at->format('Y-m-d H:i:s'), ], ]); @@ -155,24 +144,13 @@ public function update(ModuleUpdateRequest $request, int $id) DB::beginTransaction(); // Actualizar solo los campos que vienen en el request - $module->update($request->only([ - 'name', - 'responsible_id', - 'municipality', - 'address', - 'colony', - 'cp', - 'longitude', - 'latitude', - 'status', - ])); + $module->update($request->validated()); DB::commit(); return ApiResponse::OK->response([ 'message' => 'Módulo actualizado exitosamente', 'module' => [ - 'id' => $module->id, 'name' => $module->name, 'responsible_id' => $module->responsible_id, 'municipality' => $module->municipality, @@ -181,24 +159,17 @@ public function update(ModuleUpdateRequest $request, int $id) 'cp' => $module->cp, 'longitude' => $module->longitude, 'latitude' => $module->latitude, - 'status' => $module->status, - 'status_text' => $module->status ? 'Activo' : 'Inactivo', + 'status' => $module->status ? 'Activo' : 'Inactivo', 'updated_at' => $module->updated_at->format('Y-m-d H:i:s'), ], ]); - } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { + } catch (ModelNotFoundException $e) { return ApiResponse::NOT_FOUND->response([ 'message' => 'Módulo no encontrado', ]); } catch (\Exception $e) { DB::rollBack(); - Log::error('Error al actualizar módulo: ' . $e->getMessage(), [ - 'module_id' => $id, - 'request' => $request->all(), - 'trace' => $e->getTraceAsString() - ]); - return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al actualizar módulo', 'error' => $e->getMessage(), @@ -280,30 +251,4 @@ public function getAvailableUsers() ]); } } - - /** - * Obtener lista simplificada de módulos para dropdown - */ - public function listForDropdown() - { - try { - $modules = Module::where('status', true) // Solo activos - ->orderBy('name') - ->get(['id', 'name', 'municipality']); - - return ApiResponse::OK->response([ - 'modules' => $modules->map(fn($m) => [ - 'id' => $m->id, - 'name' => $m->name, - 'municipality' => $m->municipality, - 'label' => "{$m->name} - {$m->municipality}", // Para mostrar en dropdown - ]), - ]); - - } catch (\Exception $e) { - return ApiResponse::INTERNAL_ERROR->response([ - 'message' => 'Error al obtener módulos', - ]); - } - } } diff --git a/app/Http/Controllers/Repuve/PackageController.php b/app/Http/Controllers/Repuve/PackageController.php new file mode 100644 index 0000000..48258d2 --- /dev/null +++ b/app/Http/Controllers/Repuve/PackageController.php @@ -0,0 +1,163 @@ +filled('lote')) { + $query->where('lot', 'LIKE', '%' . $request->lote . '%'); + } + + if ($request->filled('caja')) { + $query->where('box_number', 'LIKE', '%' . $request->caja . '%'); + } + + + $query->with('tags')->orderBy('id', 'ASC'); + $perPage = $request->input('per_page', 20); + $packages = $query->paginate($perPage); + + return ApiResponse::OK->response([ + 'packages' => $packages->map(fn($item) => [ + 'id' => $item->id, + 'lot' => $item->lot, + 'box_number' => $item->box_number, + 'starting_page' => $item->starting_page, + 'ending_page' => $item->ending_page, + 'module' => $item->module ? [ + 'id' => $item->module->id, + 'name' => $item->module->name, + ] : null, + 'tags' => $item->tags->map(fn($tag) => [ + 'id' => $tag->id, + 'folio' => $tag->folio, + 'vehicle_id' => $tag->vehicle_id, + 'status' => $tag->status ?? null, + ]), + 'tags_count' => $item->tags->count(), + 'created_at' => $item->created_at->format('Y-m-d H:i:s'), + ]), + 'pagination' => [ + 'total' => $packages->total(), + 'per_page' => $packages->perPage(), + 'current_page' => $packages->currentPage(), + 'last_page' => $packages->lastPage(), + 'from' => $packages->firstItem(), + 'to' => $packages->lastItem(), + ], + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al obtener los paquetes', + 'error' => $e->getMessage(), + ]); + } + } + + public function store(PackageStoreRequest $request) + { + try { + DB::beginTransaction(); + + $package = Package::create([ + 'lot' => $request->input('lot'), + 'box_number' => $request->input('box_number'), + 'starting_page' => $request->input('starting_page'), + 'ending_page' => $request->input('ending_page'), + 'module_id' => $request->input('module_id'), + + ]); + + DB::commit(); + + return ApiResponse::CREATED->response([ + 'message' => 'Paquete creado exitosamente', + 'package' => [ + 'id' => $package->id, + 'lot' => $package->lot, + 'box_number' => $package->box_number, + 'starting_page' => $package->starting_page, + 'ending_page' => $package->ending_page, + 'module_id' => $package->module_id, + 'created_at' => $package->created_at->format('Y-m-d H:i:s'), + ], + ]); + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al crear el paquete', + 'error' => $e->getMessage(), + ]); + } + } + + public function update(PackageUpdateRequest $request, $id) + { + try { + $package = Package::findOrFail($id); + + DB::beginTransaction(); + + $package->update($request->validated()); + + DB::commit(); + + return ApiResponse::OK->response([ + 'message' => 'Paquete actualizado exitosamente', + 'package' => [ + 'id' => $package->id, + 'lot' => $package->lot, + 'box_number' => $package->box_number, + 'starting_page' => $package->starting_page, + 'ending_page' => $package->ending_page, + 'module_id' => $package->module_id, + 'updated_at' => $package->updated_at->format('Y-m-d H:i:s'), + ], + ]); + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al actualizar el paquete', + 'error' => $e->getMessage(), + ]); + } + } + + public function destroy($id) + { + try { + DB::beginTransaction(); + + $package = Package::findOrFail($id); + $package->delete(); + + DB::commit(); + + return ApiResponse::OK->response([ + 'message' => 'Paquete eliminado exitosamente.', + ]); + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al eliminar el paquete.', + 'error' => $e->getMessage(), + ]); + } + } +} diff --git a/app/Http/Controllers/Repuve/UpdateController.php b/app/Http/Controllers/Repuve/UpdateController.php index 537fd1a..9790f6f 100644 --- a/app/Http/Controllers/Repuve/UpdateController.php +++ b/app/Http/Controllers/Repuve/UpdateController.php @@ -156,7 +156,7 @@ public function vehicleUpdate(VehicleUpdateRequest $request) 'id' => $record->id, 'folio' => $record->folio, ], - 'vehicle' => $vehicleData, // Retorna datos para edición + 'vehicle' => $vehicleData, 'owner' => $ownerData, ]); } @@ -227,13 +227,6 @@ public function vehicleUpdate(VehicleUpdateRequest $request) } catch (\Exception $e) { DB::rollBack(); - - Log::error('Error en actualizarVehiculo: ' . $e->getMessage(), [ - 'folio' => $folio ?? null, - 'tag_id' => $tagId ?? null, - 'trace' => $e->getTraceAsString() - ]); - return ApiResponse::BAD_REQUEST->response([ 'message' => 'Error al actualizar el vehículo', 'error' => $e->getMessage(), diff --git a/app/Http/Requests/Repuve/DeviceUpdateRequest.php b/app/Http/Requests/Repuve/DeviceUpdateRequest.php index 9d99571..6149a2f 100644 --- a/app/Http/Requests/Repuve/DeviceUpdateRequest.php +++ b/app/Http/Requests/Repuve/DeviceUpdateRequest.php @@ -13,14 +13,16 @@ public function authorize(): bool public function rules(): array { + $deviceId = $this->route('id'); + return [ - 'brand' => ['required', 'string', 'max:255'], - 'serie' => ['required', 'string', 'unique:devices,serie', 'max:255'], - 'mac_address' => ['required', 'string', 'unique:devices,mac_address', 'max:15'], - 'module_id' => ['required', 'exists:modules,id'], - 'user_id' => ['required', 'array', 'min:1'], + 'brand' => ['sometimes', 'string', 'max:255'], + 'serie' => ['sometimes', 'string', 'unique:devices,serie,' . $deviceId, 'max:255'], + 'mac_address' => ['sometimes', 'string', 'unique:devices,mac_address,' . $deviceId, 'regex:/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/'], + 'module_id' => ['sometimes', 'exists:modules,id'], + 'user_id' => ['sometimes', 'array', 'min:1'], 'user_id.*' => ['exists:users,id'], - 'status' => ['nullable', 'boolean'], + 'status' => ['sometimes', 'boolean'], ]; } diff --git a/app/Http/Requests/Repuve/ModuleStoreRequest.php b/app/Http/Requests/Repuve/ModuleStoreRequest.php index 6558f6f..f85e6ee 100644 --- a/app/Http/Requests/Repuve/ModuleStoreRequest.php +++ b/app/Http/Requests/Repuve/ModuleStoreRequest.php @@ -15,6 +15,7 @@ public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], + 'responsible_id' => ['required', 'exists:users,id'], 'municipality' => ['required', 'string', 'max:100'], 'address' => ['required', 'string', 'max:255'], 'colony' => ['required', 'string', 'max:100'], diff --git a/app/Http/Requests/Repuve/PackageStoreRequest.php b/app/Http/Requests/Repuve/PackageStoreRequest.php new file mode 100644 index 0000000..3807391 --- /dev/null +++ b/app/Http/Requests/Repuve/PackageStoreRequest.php @@ -0,0 +1,35 @@ + ['required', 'string', 'max:50', 'unique:packages,lot'], + 'box_number' => ['required', 'string', 'max:100'], + 'starting_page' => ['required', 'max:255'], + 'ending_page' => ['required', 'max:255'], + 'module_id' => ['required', 'exists:modules,id'], + ]; + } + + public function messages(): array + { + return [ + 'lot.required' => 'El lote es requerido', + 'lot.unique' => 'El lote ya existe', + 'box_number.required' => 'El número de caja es requerido', + 'starting_page.required' => 'Tag de inicio es requerido', + 'ending_page.required' => 'Tag de fin es requerido', + 'module_id.required' => 'El módulo es requerido', + 'module_id.exists' => 'El módulo no existe', + ]; + } +} diff --git a/app/Http/Requests/Repuve/PackageUpdateRequest.php b/app/Http/Requests/Repuve/PackageUpdateRequest.php new file mode 100644 index 0000000..9060766 --- /dev/null +++ b/app/Http/Requests/Repuve/PackageUpdateRequest.php @@ -0,0 +1,34 @@ + ['required', 'string', 'max:100'], + 'starting_page' => ['required', 'max:255'], + 'ending_page' => ['required', 'max:255'], + 'module_id' => ['required', 'exists:modules,id'], + ]; + } + + public function messages(): array + { + return [ + 'box_number.required' => 'El número de caja es requerido', + 'starting_page.required' => 'Tag de inicio es requerido', + 'ending_page.required' => 'Tag de fin es requerido', + 'module_id.required' => 'El módulo es requerido', + 'module_id.exists' => 'El módulo no existe', + ]; + } +} diff --git a/app/Models/Tag.php b/app/Models/Tag.php index f9c5465..02b5cca 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -13,7 +13,6 @@ class Tag extends Model 'folio', 'vehicle_id', 'package_id', - 'status', ]; public function vehicle() diff --git a/database/migrations/2025_10_18_140300_create_errors_table.php b/database/migrations/2025_10_18_140300_create_errors_table.php index 7c83127..543d828 100644 --- a/database/migrations/2025_10_18_140300_create_errors_table.php +++ b/database/migrations/2025_10_18_140300_create_errors_table.php @@ -14,7 +14,9 @@ public function up(): void Schema::create('errors', function (Blueprint $table) { $table->id(); $table->string('code')->unique(); + $table->text('name')->nullable(); $table->text('description')->nullable(); + $table->text('type')->nullable(); $table->timestamps(); }); } diff --git a/database/seeders/DevSeeder.php b/database/seeders/DevSeeder.php index 46e13bd..5b00da6 100644 --- a/database/seeders/DevSeeder.php +++ b/database/seeders/DevSeeder.php @@ -26,7 +26,7 @@ public function run(): void // Nivel 1 - Sin dependencias $this->call(ModuleSeeder::class); $this->call(OwnerSeeder::class); - $this->call(ErrorSeeder::class); + //$this->call(ErrorSeeder::class); // Nivel 2 - Dependen de Nivel 1 $this->call(PackageSeeder::class); diff --git a/routes/api.php b/routes/api.php index f936ed5..996062b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,6 +7,7 @@ use App\Http\Controllers\Repuve\UpdateController; use App\Http\Controllers\Admin\RoleController; use App\Http\Controllers\Repuve\DeviceController; +use App\Http\Controllers\Repuve\PackageController; /** * Rutas del núcleo de la aplicación. @@ -41,21 +42,27 @@ Route::post('cancelacion/cancelar', [CancellationController::class, 'cancelarConstancia']); //Rutas de Modulos - Route::get('/moduleList', [ModuleController::class, 'index']); + Route::get('/module', [ModuleController::class, 'index']); Route::post('/moduleCreate', [ModuleController::class, 'store']); Route::put('/moduleUpdate/{id}', [ModuleController::class, 'update']); Route::patch('/moduleStatus/{id}', [ModuleController::class, 'toggleStatus']); Route::get('/moduleUsers', [ModuleController::class, 'getAvailableUsers']); - //Ruta para listar modulos para usuarios - Route::get('/modulesDropdown', [ModuleController::class, 'listForDropdown']); - // Rutas de roles, mostrar roles para asignar a usuarios Route::get('/admin/roles-all', [RoleController::class, 'listAll']); //Rutas de dispositivos Route::get('/devices', [DeviceController::class, 'index']); Route::post('/devices-create', [DeviceController::class, 'store']); + Route::put('/devices/{id}', [DeviceController::class, 'update']); + Route::delete('/devices/{id}', [DeviceController::class, 'destroy']); + + //Ruta de tags + Route::get('/packages', [PackageController::class, 'index']); + Route::post('/packages-create', [PackageController::class, 'store']); + Route::put('/packages-update/{id}', [PackageController::class, 'update']); + Route::delete('/packages/{id}', [PackageController::class, 'destroy']); + }); /** Rutas públicas */