From 88650009196a3bb5a6ffcddcedc1c1fd0b0527a5 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Wed, 10 Dec 2025 13:26:19 -0600 Subject: [PATCH] Fix: paquetes --- .../Repuve/InscriptionController.php | 1 + .../Controllers/Repuve/PackageController.php | 298 ++++++++++++++++-- .../Controllers/Repuve/TagsController.php | 94 +++++- .../Requests/Repuve/PackageUpdateRequest.php | 8 +- app/Models/Package.php | 6 + ...0_000001_add_user_id_to_packages_table.php | 35 ++ routes/api.php | 1 + 7 files changed, 408 insertions(+), 35 deletions(-) create mode 100644 database/migrations/2025_12_10_000001_add_user_id_to_packages_table.php diff --git a/app/Http/Controllers/Repuve/InscriptionController.php b/app/Http/Controllers/Repuve/InscriptionController.php index 5b8274e..f70d370 100644 --- a/app/Http/Controllers/Repuve/InscriptionController.php +++ b/app/Http/Controllers/Repuve/InscriptionController.php @@ -441,6 +441,7 @@ public function searchRecord(Request $request) 'files' => $record->files->map(function ($file) { return [ 'id' => $file->id, + 'catalog_id' => $file->name_id, 'name' => $file->catalogName?->name, 'path' => $file->path, 'url' => $file->url, diff --git a/app/Http/Controllers/Repuve/PackageController.php b/app/Http/Controllers/Repuve/PackageController.php index 5b4b490..b2b51b6 100644 --- a/app/Http/Controllers/Repuve/PackageController.php +++ b/app/Http/Controllers/Repuve/PackageController.php @@ -8,7 +8,7 @@ use App\Http\Requests\Repuve\PackageUpdateRequest; use App\Models\CatalogTagStatus; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Auth; use Illuminate\Http\Request; use Illuminate\Database\QueryException; use App\Models\Package; @@ -20,48 +20,87 @@ class PackageController extends Controller public function index(Request $request) { try { - $packages = Package::with(['tags'])->withCount('tags')->orderBy('id', 'ASC'); + $packages = Package::with([ + 'tags.status:id,code,name', + 'tags.vehicle:id,placa,niv', + 'tags.module:id,name', + 'user:id,name,email' + ]) + ->withCount('tags') + ->orderBy('id', 'ASC'); - if ($request->filled('lote')) { - Log::info('PackageController@index - Filtro lote aplicado', ['lote' => $request->lote]); - $packages->where('lot', 'LIKE', '%' . $request->lote . '%'); + if ($request->filled('lote') || $request->filled('lot')) { + $loteValue = $request->input('lote') ?? $request->input('lot'); + $packages->where('lot', 'LIKE', '%' . trim($loteValue) . '%'); } - if ($request->filled('caja')) { - Log::info('PackageController@index - Filtro caja aplicado', ['caja' => $request->caja]); - $packages->where('box_number', 'LIKE', '%' . $request->caja . '%'); + if ($request->filled('caja') || $request->filled('box_number')) { + $cajaValue = $request->input('caja') ?? $request->input('box_number'); + $packages->where('box_number', 'LIKE', '%' . trim($cajaValue) . '%'); } - Log::info('PackageController@index - Ejecutando query de paginación'); $paginatedPackages = $packages->paginate(config('app.pagination')); - Log::info('PackageController@index - Query ejecutado exitosamente', [ - 'total' => $paginatedPackages->total(), - 'per_page' => $paginatedPackages->perPage(), - 'current_page' => $paginatedPackages->currentPage(), - ]); - // Validación si no hay resultados if ($paginatedPackages->isEmpty()) { - Log::info('PackageController@index - No se encontraron resultados'); return ApiResponse::NOT_FOUND->response([ - 'message' => 'No se encontraron tags con los criterios de búsqueda proporcionados.', - 'filters_applied' => array_filter($request->only(['lot', 'box_number'])) + 'message' => 'No se encontraron paquetes con los criterios de búsqueda proporcionados.', + 'filters_applied' => [ + 'lote' => $request->input('lote') ?? $request->input('lot'), + 'caja' => $request->input('caja') ?? $request->input('box_number'), + ] ]); } - Log::info('PackageController@index - Respuesta exitosa'); + // Si hay filtro de caja, incluir estadísticas de tags + if ($request->filled('caja') || $request->filled('box_number')) { + $paginatedPackages->getCollection()->transform(function ($package) { + return [ + 'id' => $package->id, + 'lot' => $package->lot, + 'box_number' => $package->box_number, + 'starting_page' => $package->starting_page, + 'ending_page' => $package->ending_page, + 'created_by' => $package->user ? [ + 'id' => $package->user->id, + 'name' => $package->user->name, + 'email' => $package->user->email, + ] : null, + 'estadisticas' => [ + 'total' => $package->tags->count(), + 'available' => $package->tags->where('status.code', 'available')->count(), + 'assigned' => $package->tags->where('status.code', 'assigned')->count(), + 'cancelled' => $package->tags->where('status.code', 'cancelled')->count(), + ], + 'tags' => $package->tags->map(function ($tag) { + return [ + 'id' => $tag->id, + 'folio' => $tag->folio, + 'tag_number' => $tag->tag_number, + 'status' => [ + 'id' => $tag->status->id, + 'code' => $tag->status->code, + 'name' => $tag->status->name, + ], + 'vehicle' => $tag->vehicle ? [ + 'id' => $tag->vehicle->id, + 'placa' => $tag->vehicle->placa, + 'niv' => $tag->vehicle->niv, + ] : null, + 'module' => $tag->module ? [ + 'id' => $tag->module->id, + 'name' => $tag->module->name, + ] : null, + ]; + }), + ]; + }); + } + return ApiResponse::OK->response([ 'Paquetes' => $paginatedPackages, ]); } catch (\Exception $e) { - Log::error('PackageController@index - Error capturado', [ - 'message' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'trace' => $e->getTraceAsString(), - ]); - return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al obtener los paquetes', 'error' => $e->getMessage(), @@ -69,6 +108,7 @@ public function index(Request $request) } } + public function store(PackageStoreRequest $request) { try { @@ -79,6 +119,7 @@ public function store(PackageStoreRequest $request) 'box_number' => $request->box_number, 'starting_page' => $request->starting_page, 'ending_page' => $request->ending_page, + 'user_id' => Auth::id(), ]); // Obtener el status "available" para los tags @@ -164,11 +205,83 @@ public function show($id) public function update(PackageUpdateRequest $request, $id) { try { - $package = Package::findOrFail($id); + $package = Package::with('tags')->findOrFail($id); + $validated = $request->validated(); + + // Validar si el paquete tiene tags asignados + $hasTags = $package->tags()->count() > 0; + + // Si tiene tags, validar que no se cambien los rangos de páginas + if ($hasTags) { + if (isset($validated['starting_page']) && $validated['starting_page'] != $package->starting_page) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'No se puede cambiar el rango inicial porque el paquete ya tiene tags asignados.', + 'current_starting_page' => $package->starting_page, + 'requested_starting_page' => $validated['starting_page'], + 'tags_count' => $package->tags()->count(), + ]); + } + + if (isset($validated['ending_page']) && $validated['ending_page'] != $package->ending_page) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => 'No se puede cambiar el rango final porque el paquete ya tiene tags asignados.', + 'current_ending_page' => $package->ending_page, + 'requested_ending_page' => $validated['ending_page'], + 'tags_count' => $package->tags()->count(), + ]); + } + } + + // Validar que la combinación de lote + caja sea única + if (isset($validated['lot']) || isset($validated['box_number'])) { + $newLot = $validated['lot'] ?? $package->lot; + $newBoxNumber = $validated['box_number'] ?? $package->box_number; + + $existingPackage = Package::where('lot', $newLot) + ->where('box_number', $newBoxNumber) + ->where('id', '!=', $package->id) + ->first(); + + if ($existingPackage) { + return ApiResponse::BAD_REQUEST->response([ + 'message' => "Ya existe otro paquete con el lote '{$newLot}' y caja '{$newBoxNumber}'.", + 'existing_package_id' => $existingPackage->id, + 'current_lot' => $package->lot, + 'current_box_number' => $package->box_number, + 'requested_lot' => $newLot, + 'requested_box_number' => $newBoxNumber, + ]); + } + } DB::beginTransaction(); - $package->update($request->validated()); + // Guardar valores anteriores para el log + $changes = []; + foreach ($validated as $key => $value) { + if ($package->$key != $value) { + $changes[$key] = [ + 'old' => $package->$key, + 'new' => $value, + ]; + } + } + + // Si no hay cambios + if (empty($changes)) { + return ApiResponse::OK->response([ + 'message' => 'No se realizaron cambios en el paquete.', + 'package' => [ + 'id' => $package->id, + 'lot' => $package->lot, + 'box_number' => $package->box_number, + 'starting_page' => $package->starting_page, + 'ending_page' => $package->ending_page, + ], + ]); + } + + $package->update($validated); DB::commit(); @@ -182,6 +295,21 @@ public function update(PackageUpdateRequest $request, $id) 'ending_page' => $package->ending_page, 'updated_at' => $package->updated_at->format('Y-m-d H:i:s'), ], + 'changes' => $changes, + ]); + } 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 esa combinación de lote y caja.', + 'error' => $e->getMessage(), + ]); + } + + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error de base de datos al actualizar el paquete', + 'error' => $e->getMessage(), ]); } catch (\Exception $e) { DB::rollBack(); @@ -221,4 +349,118 @@ public function destroy($id) ]); } } + + /** + * Obtener tags de una caja específica con paginación + */ + public function getBoxTags(Request $request) + { + try { + // Si no se envían parámetros, obtener el primer paquete + if (!$request->has('lot') && !$request->has('box_number')) { + $package = Package::with('user')->orderBy('id', 'DESC')->first(); + + if (!$package) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No se encontraron paquetes en el sistema.', + ]); + } + } else { + // Validar parámetros si se proporcionan + $validated = $request->validate([ + 'lot' => 'required|string', + 'box_number' => 'required|string|max:255', + ]); + + $lot = $validated['lot']; + $boxNumber = $validated['box_number']; + + // Buscar el paquete + $package = Package::with('user')->where('lot', $lot) + ->where('box_number', $boxNumber) + ->first(); + + if (!$package) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'No se encontró un paquete con el lote y caja especificados.', + 'lot' => $lot, + 'box_number' => $boxNumber, + ]); + } + } + + // Obtener tags con paginación + $tags = Tag::with(['status:id,code,name', 'vehicle:id,placa,niv', 'module:id,name']) + ->where('package_id', $package->id) + ->orderBy('folio', 'ASC'); + + // Filtro adicional por status si se proporciona + if ($request->filled('status')) { + $tags->whereHas('status', function ($q) use ($request) { + $q->where('code', $request->input('status')); + }); + } + + $paginatedTags = $tags->paginate($request->input('per_page', 25)); + + // Estadísticas generales + $estadisticas = [ + 'total' => Tag::where('package_id', $package->id)->count(), + 'available' => Tag::where('package_id', $package->id) + ->whereHas('status', fn($q) => $q->where('code', 'available')) + ->count(), + 'assigned' => Tag::where('package_id', $package->id) + ->whereHas('status', fn($q) => $q->where('code', 'assigned')) + ->count(), + 'cancelled' => Tag::where('package_id', $package->id) + ->whereHas('status', fn($q) => $q->where('code', 'cancelled')) + ->count(), + ]; + + // Transformar tags + $paginatedTags->getCollection()->transform(function ($tag) { + return [ + 'id' => $tag->id, + 'folio' => $tag->folio, + 'tag_number' => $tag->tag_number, + 'status' => [ + 'id' => $tag->status->id, + 'code' => $tag->status->code, + 'name' => $tag->status->name, + ], + 'vehicle' => $tag->vehicle ? [ + 'id' => $tag->vehicle->id, + 'placa' => $tag->vehicle->placa, + 'niv' => $tag->vehicle->niv, + ] : null, + 'module' => $tag->module ? [ + 'id' => $tag->module->id, + 'name' => $tag->module->name, + ] : null, + ]; + }); + + return ApiResponse::OK->response([ + 'package' => [ + 'id' => $package->id, + 'lot' => $package->lot, + 'box_number' => $package->box_number, + 'starting_page' => $package->starting_page, + 'ending_page' => $package->ending_page, + 'created_by' => $package->user ? [ + 'id' => $package->user->id, + 'name' => $package->user->name, + 'email' => $package->user->email, + ] : null, + ], + 'estadisticas' => $estadisticas, + 'tags' => $paginatedTags, + ]); + } catch (\Exception $e) { + return ApiResponse::INTERNAL_ERROR->response([ + 'message' => 'Error al obtener los tags de la caja.', + 'error' => $e->getMessage(), + ]); + } + } } diff --git a/app/Http/Controllers/Repuve/TagsController.php b/app/Http/Controllers/Repuve/TagsController.php index 19a24ee..b99ccb0 100644 --- a/app/Http/Controllers/Repuve/TagsController.php +++ b/app/Http/Controllers/Repuve/TagsController.php @@ -125,7 +125,77 @@ public function store(Request $request) DB::beginTransaction(); - // Crear el tag con status disponible por defecto + // Obtener el paquete + $package = Package::findOrFail($validated['package_id']); + $folioNumerico = (int) $validated['folio']; + + // Verificar si el folio está fuera del rango actual del paquete + $packageUpdated = false; + $rangeChanges = []; + $missingTags = []; + + // Caso 1: El folio es MENOR que el starting_page (crear tags intermedios) + if ($folioNumerico < $package->starting_page) { + $rangeChanges['starting_page'] = [ + 'old' => $package->starting_page, + 'new' => $folioNumerico, + ]; + + // Crear tags intermedios (desde el nuevo folio hasta el starting_page - 1) + for ($i = $folioNumerico + 1; $i < $package->starting_page; $i++) { + // Verificar que el tag no exista + $existingTag = Tag::where('folio', $i)->where('package_id', $package->id)->first(); + if (!$existingTag) { + Tag::create([ + 'folio' => $i, + 'tag_number' => null, + 'package_id' => $package->id, + 'module_id' => null, + 'status_id' => $statusAvailable->id, + 'vehicle_id' => null, + ]); + $missingTags[] = $i; + } + } + + $package->starting_page = $folioNumerico; + $packageUpdated = true; + } + + // Caso 2: El folio es MAYOR que el ending_page (crear tags intermedios) + if ($folioNumerico > $package->ending_page) { + $rangeChanges['ending_page'] = [ + 'old' => $package->ending_page, + 'new' => $folioNumerico, + ]; + + // Crear tags intermedios (desde ending_page + 1 hasta el nuevo folio - 1) + for ($i = $package->ending_page + 1; $i < $folioNumerico; $i++) { + // Verificar que el tag no exista + $existingTag = Tag::where('folio', $i)->where('package_id', $package->id)->first(); + if (!$existingTag) { + Tag::create([ + 'folio' => $i, + 'tag_number' => null, + 'package_id' => $package->id, + 'module_id' => null, + 'status_id' => $statusAvailable->id, + 'vehicle_id' => null, + ]); + $missingTags[] = $i; + } + } + + $package->ending_page = $folioNumerico; + $packageUpdated = true; + } + + // Guardar cambios en el paquete si es necesario + if ($packageUpdated) { + $package->save(); + } + + // Crear el tag principal solicitado $tag = Tag::create([ 'folio' => $validated['folio'], 'tag_number' => $validated['tag_number'] ?? null, @@ -140,10 +210,28 @@ public function store(Request $request) // Cargar relaciones $tag->load(['package', 'module', 'status']); - return ApiResponse::CREATED->response([ + $response = [ 'message' => 'Tag creado correctamente.', 'tag' => $tag, - ]); + ]; + + // Agregar información de actualización del paquete si hubo cambios + if ($packageUpdated) { + $response['package_updated'] = true; + $response['package_range_changes'] = $rangeChanges; + $response['package_current_range'] = [ + 'starting_page' => $package->starting_page, + 'ending_page' => $package->ending_page, + ]; + } + + // Agregar información de tags intermedios creados + if (!empty($missingTags)) { + $response['missing_tags_created'] = $missingTags; + $response['missing_tags_count'] = count($missingTags); + } + + return ApiResponse::CREATED->response($response); } catch (\Illuminate\Validation\ValidationException $e) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'No se pudo crear el tag: Datos de validación incorrectos.', diff --git a/app/Http/Requests/Repuve/PackageUpdateRequest.php b/app/Http/Requests/Repuve/PackageUpdateRequest.php index f0f8d90..b6b323a 100644 --- a/app/Http/Requests/Repuve/PackageUpdateRequest.php +++ b/app/Http/Requests/Repuve/PackageUpdateRequest.php @@ -14,10 +14,10 @@ public function authorize(): bool public function rules(): array { return [ - 'lot' => ['required', 'string'], - 'box_number' => ['required', 'integer'], - 'starting_page' => ['required', 'integer', 'min:1'], - 'ending_page' => ['required', 'integer', 'min:1', 'gte:starting_page'], + 'lot' => ['sometimes', 'string'], + 'box_number' => ['sometimes', 'integer'], + 'starting_page' => ['sometimes', 'integer', 'min:1'], + 'ending_page' => ['sometimes', 'integer', 'min:1', 'gte:starting_page'], ]; } diff --git a/app/Models/Package.php b/app/Models/Package.php index 2253105..4f83311 100644 --- a/app/Models/Package.php +++ b/app/Models/Package.php @@ -14,6 +14,7 @@ class Package extends Model 'box_number', 'starting_page', 'ending_page', + 'user_id', ]; protected $casts = [ @@ -25,4 +26,9 @@ public function tags() { return $this->hasMany(Tag::class); } + + public function user() + { + return $this->belongsTo(User::class); + } } diff --git a/database/migrations/2025_12_10_000001_add_user_id_to_packages_table.php b/database/migrations/2025_12_10_000001_add_user_id_to_packages_table.php new file mode 100644 index 0000000..d118264 --- /dev/null +++ b/database/migrations/2025_12_10_000001_add_user_id_to_packages_table.php @@ -0,0 +1,35 @@ +unsignedBigInteger('user_id')->nullable()->after('ending_page'); + + // Foreign key constraint + $table->foreign('user_id') + ->references('id') + ->on('users') + ->onDelete('set null'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('packages', function (Blueprint $table) { + $table->dropForeign(['user_id']); + $table->dropColumn('user_id'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 488bfd5..d5ac4eb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -73,6 +73,7 @@ Route::resource('devices', DeviceController::class); //Ruta de paquetes + Route::get('packages/boxes', [PackageController::class, 'getBoxTags']); Route::resource('packages', PackageController::class); //Ruta Tags