ADD: Controller paquetes de tags

This commit is contained in:
Juan Felipe Zapata Moreno 2025-10-24 16:50:07 -06:00
parent 2fc21be5a2
commit e97d124967
13 changed files with 345 additions and 98 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,163 @@
<?php
namespace App\Http\Controllers\Repuve;
use Notsoweb\ApiResponse\Enums\ApiResponse;
use App\Http\Controllers\Controller;
use App\Http\Requests\Repuve\PackageStoreRequest;
use App\Http\Requests\Repuve\PackageUpdateRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use App\Models\Package;
use App\Models\Tag;
use Dompdf\FrameDecorator\Page;
class PackageController extends Controller
{
public function index(Request $request)
{
try {
$query = Package::query();
if ($request->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(),
]);
}
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,35 @@
<?php namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class PackageStoreRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'lot' => ['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',
];
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Requests\Repuve;
use Illuminate\Foundation\Http\FormRequest;
class PackageUpdateRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'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 [
'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',
];
}
}

View File

@ -13,7 +13,6 @@ class Tag extends Model
'folio',
'vehicle_id',
'package_id',
'status',
];
public function vehicle()

View File

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

View File

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

View File

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