From de9d801d5096d43fba0d300ab56ec4a9653277ca Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Tue, 17 Feb 2026 14:35:34 -0600 Subject: [PATCH] fix: mejorar manejo de excepciones y validaciones en controladores de dispositivos, modulos y paquetes --- .../Controllers/Repuve/DeviceController.php | 24 +++++- .../Controllers/Repuve/ModuleController.php | 18 +++-- .../Controllers/Repuve/PackageController.php | 79 +++++++------------ .../Requests/Repuve/PackageStoreRequest.php | 4 +- lang/es/validation.php | 12 ++- 5 files changed, 72 insertions(+), 65 deletions(-) diff --git a/app/Http/Controllers/Repuve/DeviceController.php b/app/Http/Controllers/Repuve/DeviceController.php index f69493f..700793f 100644 --- a/app/Http/Controllers/Repuve/DeviceController.php +++ b/app/Http/Controllers/Repuve/DeviceController.php @@ -106,6 +106,10 @@ public function show($id) return ApiResponse::OK->response([ 'device' => $device, ]); + } catch (ModelNotFoundException $e) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Dispositivo no encontrado.', + ]); } catch (\Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al obtener el dispositivo.', @@ -125,16 +129,18 @@ public function update(DeviceUpdateRequest $request, $id) // Validar unicidad solo si los valores cambiaron if ($request->filled('serie') && $request->serie !== $device->serie) { if (Device::where('serie', $request->serie)->exists()) { - return ApiResponse::INTERNAL_ERROR->response([ - 'message' => 'El número de serie ya está en uso.', + DB::rollBack(); + return ApiResponse::UNPROCESSABLE_CONTENT->response([ + 'serie' => ['El número de serie ya está en uso.'], ]); } } if ($request->filled('mac_address') && $request->mac_address !== $device->mac_address) { if (Device::where('mac_address', $request->mac_address)->exists()) { - return ApiResponse::INTERNAL_ERROR->response([ - 'message' => 'La dirección MAC ya está registrada.', + DB::rollBack(); + return ApiResponse::UNPROCESSABLE_CONTENT->response([ + 'mac_address' => ['La dirección MAC ya está registrada.'], ]); } } @@ -187,7 +193,12 @@ public function update(DeviceUpdateRequest $request, $id) ->values(), ], ]); + } catch (ModelNotFoundException $e) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Dispositivo no encontrado.', + ]); } catch (\Exception $e) { + DB::rollBack(); return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al actualizar el dispositivo.', 'error' => $e->getMessage(), @@ -208,6 +219,11 @@ public function destroy($id) return ApiResponse::OK->response([ 'message' => 'Dispositivo eliminado exitosamente.', ]); + } catch (ModelNotFoundException $e) { + DB::rollBack(); + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Dispositivo no encontrado.', + ]); } catch (\Exception $e) { DB::rollBack(); return ApiResponse::INTERNAL_ERROR->response([ diff --git a/app/Http/Controllers/Repuve/ModuleController.php b/app/Http/Controllers/Repuve/ModuleController.php index ebfecb4..dbfa64e 100644 --- a/app/Http/Controllers/Repuve/ModuleController.php +++ b/app/Http/Controllers/Repuve/ModuleController.php @@ -106,15 +106,19 @@ public function store(ModuleStoreRequest $request) public function show($id) { try { - $modules = Module::with([ - 'responsible:id,name,username', - 'municipality:id,code,name', - 'users:id,name,paternal,maternal,username,module_id', - 'users.roles:id,name,description' - ])->find($id); + $module = Module::with([ + 'responsible:id,name,username', + 'municipality:id,code,name', + 'users:id,name,paternal,maternal,username,module_id', + 'users.roles:id,name,description' + ])->findOrFail($id); return ApiResponse::OK->response([ - 'module' => $modules, + 'module' => $module, + ]); + } catch (ModelNotFoundException $e) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Módulo no encontrado.', ]); } catch (\Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ diff --git a/app/Http/Controllers/Repuve/PackageController.php b/app/Http/Controllers/Repuve/PackageController.php index 65d1d73..694b0eb 100644 --- a/app/Http/Controllers/Repuve/PackageController.php +++ b/app/Http/Controllers/Repuve/PackageController.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\QueryException; use App\Models\Package; use App\Models\Tag; @@ -129,74 +130,50 @@ public function store(PackageStoreRequest $request) try { DB::beginTransaction(); - $package = Package::create([ - 'lot' => $request->lot, - 'box_number' => $request->box_number, - 'starting_page' => $request->starting_page, - 'ending_page' => $request->ending_page, - 'user_id' => Auth::id(), - ]); + // Verificar folios en el mismo lote antes de crear + $packageIds = Package::where('lot', $request->lot)->pluck('id'); - // Obtener el status "available" para los tags - $statusAvailable = CatalogTagStatus::where('code', 'available')->first(); + $conflicting = Tag::whereIn('package_id', $packageIds) + ->whereRaw('CAST(folio AS UNSIGNED) BETWEEN ? AND ?', [$request->starting_page, $request->ending_page]) + ->pluck('folio'); - if (!$statusAvailable) { - throw new \Exception('No se encontró el status "Disponible" para los tags'); - } - - $existingTags = Tag::whereHas('package', function ($query) use ($request) { - $query->where('box_number', $request->box_number); - }) - ->whereBetween('folio', [$request->starting_page, $request->ending_page]) - ->get(['folio', 'package_id']); - - if ($existingTags->isNotEmpty()) { + if ($conflicting->isNotEmpty()) { DB::rollBack(); - return ApiResponse::BAD_REQUEST->response([ - 'message' => 'Ya existen tags en esta caja con folios en el rango especificado.', - 'box_number' => $request->box_number, - 'starting_page' => $request->starting_page, - 'ending_page' => $request->ending_page, - 'folios_conflictivos' => $existingTags->pluck('folio')->toArray(), - 'total_folios_conflictivos' => $existingTags->count(), + return ApiResponse::UNPROCESSABLE_CONTENT->response([ + 'starting_page' => [ + 'Los folios ' . $conflicting->join(', ') . ' ya están registrados en el lote "' . $request->lot . '".', + ], ]); } - // Crear los tags según el rango de páginas + $form = $request->validated(); + $form['user_id'] = Auth::id(); + + $package = Package::create($form); + + $statusAvailable = CatalogTagStatus::where('code', 'available')->firstOrFail(); + for ($page = $request->starting_page; $page <= $request->ending_page; $page++) { Tag::create([ - 'folio' => $page, + 'folio' => $page, 'tag_number' => null, 'package_id' => $package->id, - 'status_id' => $statusAvailable->id, + 'status_id' => $statusAvailable->id, ]); } DB::commit(); return ApiResponse::CREATED->response([ - 'message' => 'Paquete registrado exitosamente con sus tags', - 'package' => $package->load('tags'), + 'message' => 'Paquete registrado exitosamente con sus tags', + 'package' => $package->load('tags'), 'tags_created' => $package->tags()->count(), ]); - } catch (QueryException $e) { - DB::rollBack(); - - if ($e->getCode() == 23000 && str_contains($e->getMessage(), 'packages_lot_box_unique')) { - return ApiResponse::BAD_REQUEST->response([ - 'message' => "Ya existe un paquete con el lote '{$request->lot}' y caja número '{$request->box_number}'.", - ]); - } - - return ApiResponse::INTERNAL_ERROR->response([ - 'message' => 'Error al crear el paquete', - 'error' => $e->getMessage(), - ]); } catch (\Exception $e) { DB::rollBack(); return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al crear el paquete', - 'error' => $e->getMessage(), + 'error' => $e->getMessage(), ]); } } @@ -209,6 +186,10 @@ public function show($id) return ApiResponse::OK->response([ 'package' => $package, ]); + } catch (ModelNotFoundException $e) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Paquete no encontrado.', + ]); } catch (\Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al obtener el paquete', @@ -322,13 +303,13 @@ public function update(PackageUpdateRequest $request, $id) ]); } - return ApiResponse::INTERNAL_ERROR->response([ + return ApiResponse::NOT_FOUND->response([ 'message' => 'Error de base de datos al actualizar el paquete', 'error' => $e->getMessage(), ]); } catch (\Exception $e) { DB::rollBack(); - return ApiResponse::INTERNAL_ERROR->response([ + return ApiResponse::NOT_FOUND->response([ 'message' => 'Error al actualizar el paquete', 'error' => $e->getMessage(), ]); @@ -407,7 +388,7 @@ public function getBoxTags(Request $request) // Obtener tags con paginación $tags = Tag::with(['status:id,code,name', 'vehicle:id,placa,niv', 'module:id,name', 'cancellationLogs.cancellationReason']) ->where('package_id', $package->id) - ->orderBy('folio', 'ASC'); + ->orderBy('id', 'ASC'); // Filtro adicional por status si se proporciona if ($request->filled('status')) { diff --git a/app/Http/Requests/Repuve/PackageStoreRequest.php b/app/Http/Requests/Repuve/PackageStoreRequest.php index 2360972..435b3bc 100644 --- a/app/Http/Requests/Repuve/PackageStoreRequest.php +++ b/app/Http/Requests/Repuve/PackageStoreRequest.php @@ -1,6 +1,7 @@ ['required', 'string'], + 'lot' => ['required', 'string', Rule::unique('packages', 'lot')->where(fn($q) => $q->where('box_number', $this->input('box_number')))], 'box_number' => ['required', 'integer'], 'starting_page' => ['required', 'integer', 'min:1'], 'ending_page' => ['required', 'integer', 'min:1', 'gte:starting_page'], @@ -23,6 +24,7 @@ public function messages(): array { return [ 'lot.required' => 'El lote es requerido', + 'lot.unique' => 'Ya existe un paquete con ese lote y número de caja.', 'box_number.required' => 'El número de caja es requerido', diff --git a/lang/es/validation.php b/lang/es/validation.php index bdd968a..40f04a3 100644 --- a/lang/es/validation.php +++ b/lang/es/validation.php @@ -151,14 +151,18 @@ 'attributes' => [ 'barcode' => 'código de barras', 'section.number' => 'sección', - 'name' => 'nombre', - 'paternal' => 'apellido paterno', + 'name' => 'nombre', + 'paternal' => 'apellido paterno', 'maternal' => 'apellido materno', 'email' => 'correo', 'phone' => 'teléfono', + 'box_number' => 'número de caja', + 'lot' => 'lote', + 'starting_page' => 'página inicial', + 'ending_page' => 'página final', 'type_ck' => 'tipo', - '*.name' => 'nombre', - '*.paternal' => 'apellido paterno', + '*.name' => 'nombre', + '*.paternal' => 'apellido paterno', '*.maternal' => 'apellido materno', '*.email' => 'correo', '*.phone' => 'teléfono',