Fix: paquetes

This commit is contained in:
Juan Felipe Zapata Moreno 2025-12-10 13:26:19 -06:00
parent 20a7e3beda
commit 8865000919
7 changed files with 408 additions and 35 deletions

View File

@ -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,

View File

@ -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(),
]);
}
}
}

View File

@ -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.',

View File

@ -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'],
];
}

View File

@ -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);
}
}

View File

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('packages', function (Blueprint $table) {
$table->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');
});
}
};

View File

@ -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