$request->all() ]); // Si NO hay filtro de caja, no cargar las relaciones de tags para optimizar $shouldLoadTags = $request->filled('caja') || $request->filled('box_number'); $packages = Package::query(); if ($shouldLoadTags) { $packages->with([ 'tags:id,folio,tag_number,package_id,status_id,vehicle_id,module_id', 'tags.status:id,code,name', 'tags.vehicle:id,placa,niv', 'tags.module:id,name', 'user:id,name,email' ]); } else { $packages->with('user:id,name,email'); } $packages->withCount('tags')->orderBy('id', 'ASC'); if ($request->filled('lote') || $request->filled('lot')) { $loteValue = $request->input('lote') ?? $request->input('lot'); $packages->where('lot', 'LIKE', '%' . trim($loteValue) . '%'); } 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 paginación'); $paginatedPackages = $packages->paginate(config('app.pagination')); Log::info('PackageController@index - Paginación completada', [ 'total' => $paginatedPackages->total(), 'count' => $paginatedPackages->count() ]); // Validación si no hay resultados if ($paginatedPackages->isEmpty()) { return ApiResponse::NOT_FOUND->response([ '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'), ] ]); } // 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->filter(function($tag) { return $tag->status && $tag->status->code === 'available'; })->count(), 'assigned' => $package->tags->filter(function($tag) { return $tag->status && $tag->status->code === 'assigned'; })->count(), 'cancelled' => $package->tags->filter(function($tag) { return $tag->status && $tag->status->code === 'cancelled'; })->count(), ], 'tags' => $package->tags->map(function ($tag) { return [ 'id' => $tag->id, 'folio' => $tag->folio, 'tag_number' => $tag->tag_number, 'status' => $tag->status ? [ 'id' => $tag->status->id, 'code' => $tag->status->code, 'name' => $tag->status->name, ] : null, '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, ]; }), ]; }); } Log::info('PackageController@index - Retornando respuesta exitosa'); 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(), ]); } } 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(), ]); // Obtener el status "available" para los tags $statusAvailable = CatalogTagStatus::where('code', 'available')->first(); 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()) { 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(), ]); } // Crear los tags según el rango de páginas for ($page = $request->starting_page; $page <= $request->ending_page; $page++) { Tag::create([ 'folio' => $page, 'tag_number' => null, 'package_id' => $package->id, 'status_id' => $statusAvailable->id, ]); } DB::commit(); return ApiResponse::CREATED->response([ '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(), ]); } } public function show($id) { try { $package = Package::with(['tags'])->findOrFail($id); return ApiResponse::OK->response([ 'package' => $package, ]); } catch (\Exception $e) { return ApiResponse::INTERNAL_ERROR->response([ 'message' => 'Error al obtener el paquete', 'error' => $e->getMessage(), ]); } } public function update(PackageUpdateRequest $request, $id) { try { $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(); // 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(); 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, '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(); 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); if ($package->tags()->count() > 0) { return ApiResponse::BAD_REQUEST->response([ 'message' => 'No se puede eliminar el paquete porque tiene tags asociados.', 'tags_count' => $package->tags()->count(), ]); } $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(), ]); } } /** * 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(), ]); } } }