From 505729ba193b0c179fbc750ac5a79287d8e0dca6 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Tue, 6 Jan 2026 12:45:18 -0600 Subject: [PATCH] =?UTF-8?q?ADD:=20Implementa=20controlador=20y=20modelo=20?= =?UTF-8?q?para=20gesti=C3=B3n=20de=20arcos=20RFID,=20incluyendo=20rutas?= =?UTF-8?q?=20y=20migraciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/ArcoController.php | 156 ++++++++++++++++++ .../Controllers/Api/VehicleController.php | 29 ++-- app/Models/Arco.php | 59 +++++++ app/Services/ConsultaEstatalService.php | 19 --- app/Services/ReporteRoboService.php | 9 +- app/Services/VehicleService.php | 76 +++++---- .../2026_01_06_113001_create_arcos_table.php | 34 ++++ ...113139_add_arco_id_to_detections_table.php | 30 ++++ routes/api.php | 8 +- 9 files changed, 352 insertions(+), 68 deletions(-) create mode 100644 app/Http/Controllers/Api/ArcoController.php create mode 100644 app/Models/Arco.php create mode 100644 database/migrations/2026_01_06_113001_create_arcos_table.php create mode 100644 database/migrations/2026_01_06_113139_add_arco_id_to_detections_table.php diff --git a/app/Http/Controllers/Api/ArcoController.php b/app/Http/Controllers/Api/ArcoController.php new file mode 100644 index 0000000..c80f8ee --- /dev/null +++ b/app/Http/Controllers/Api/ArcoController.php @@ -0,0 +1,156 @@ +has('activo')) { + $query->where('activo', $request->boolean('activo')); + } + + // Filtro por IP + if ($request->has('ip')) { + $query->where('ip_address', 'like', '%' . $request->ip . '%'); + } + + $arcos = $query->orderBy('created_at', 'desc')->get(); + + return ApiResponse::OK->response([ + 'arcos' => $arcos + ]); + } + + /** + * Crear un nuevo arco + * POST /api/arcos + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'nombre' => 'required|string|max:255', + 'ip_address' => 'required|ip|unique:arcos,ip_address', + 'ubicacion' => 'nullable|string|max:255', + 'descripcion' => 'nullable|string', + 'activo' => 'boolean' + ]); + + $arco = Arco::create($validated); + + return ApiResponse::CREATED->response([ + 'message' => 'Arco creado exitosamente', + 'arco' => $arco + ]); + } + + /** + * Mostrar un arco específico + * GET /api/arcos/{id} + */ + public function show(int $id) + { + $arco = Arco::with(['detecciones' => function ($query) { + $query->latest()->limit(10); + }])->find($id); + + if (!$arco) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Arco no encontrado' + ]); + } + + return ApiResponse::OK->response([ + 'arco' => $arco + ]); + } + + /** + * Actualizar un arco + * PUT/PATCH /api/arcos/{id} + */ + public function update(Request $request, int $id) + { + $arco = Arco::find($id); + + if (!$arco) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Arco no encontrado' + ]); + } + + $validated = $request->validate([ + 'nombre' => 'sometimes|required|string|max:255', + 'ip_address' => 'sometimes|required|ip|unique:arcos,ip_address,' . $id, + 'ubicacion' => 'nullable|string|max:255', + 'descripcion' => 'nullable|string', + 'activo' => 'boolean' + ]); + + $arco->update($validated); + + return ApiResponse::OK->response([ + 'message' => 'Arco actualizado exitosamente', + 'arco' => $arco + ]); + } + + /** + * Eliminar un arco + * DELETE /api/arcos/{id} + */ + public function destroy(int $id) + { + $arco = Arco::find($id); + + if (!$arco) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Arco no encontrado' + ]); + } + + $arco->delete(); + + return ApiResponse::OK->response([ + 'message' => 'Arco eliminado exitosamente' + ]); + } + + /** + * Activar/Desactivar un arco + * PATCH /api/arcos/{id}/toggle-estado + */ + public function toggleEstado(int $id) + { + $arco = Arco::find($id); + + if (!$arco) { + return ApiResponse::NOT_FOUND->response([ + 'message' => 'Arco no encontrado' + ]); + } + + $arco->activo = !$arco->activo; + $arco->save(); + + return ApiResponse::OK->response([ + 'message' => $arco->activo ? 'Arco activado' : 'Arco desactivado', + 'arco' => $arco + ]); + } +} diff --git a/app/Http/Controllers/Api/VehicleController.php b/app/Http/Controllers/Api/VehicleController.php index b05d891..c67405d 100644 --- a/app/Http/Controllers/Api/VehicleController.php +++ b/app/Http/Controllers/Api/VehicleController.php @@ -377,30 +377,35 @@ public function buscarVehiculoRobado(Request $request) } /** - * Buscar vehículo por tag_id (tag_number) - * GET /api/vehicles/buscar + * Procesar detección de tag RFID + * POST /api/vehicles/buscar */ public function buscarPorTag(Request $request) { $validated = $request->validate([ - 'tag_id' => 'required|string', + 'fast_id' => 'required|string', + 'epc' => 'nullable|string', + 'arco_ip' => 'nullable|ip', + 'timestamp' => 'nullable|date' ]); - $resultado = $this->vehicleService->consultarVehiculoPorTag( - epc: null, - tagId: $validated['tag_id'] + // Si no se proporciona EPC, usar el FastID como identificador + $epc = $validated['epc'] ?? $validated['fast_id']; + + $resultado = $this->vehicleService->procesarDeteccion( + $epc, + $validated['fast_id'], + $validated['arco_ip'], + $validated['timestamp'] ?? null ); - if (!$resultado) { + if (!$resultado['success']) { return ApiResponse::NOT_FOUND->response([ 'success' => false, - 'message' => 'No se encontró el vehículo' + 'message' => $resultado['message'] ?? 'No se encontró el vehículo' ]); } - return ApiResponse::OK->response([ - 'success' => true, - 'vehiculo' => $resultado - ]); + return ApiResponse::OK->response($resultado); } } diff --git a/app/Models/Arco.php b/app/Models/Arco.php new file mode 100644 index 0000000..f5305eb --- /dev/null +++ b/app/Models/Arco.php @@ -0,0 +1,59 @@ + + * + * @version 1.0.0 + */ +class Arco extends Model +{ + protected $table = 'arcos'; + + protected $fillable = [ + 'nombre', + 'ip_address', + 'ubicacion', + 'activo' + ]; + + protected $casts = [ + 'activo' => 'boolean', + 'created_at' => 'datetime', + 'updated_at' => 'datetime' + ]; + + /** + * Relación con detecciones + */ + public function detecciones(): HasMany + { + return $this->hasMany(Detection::class, 'arco_id'); + } + + /** + * Scope para obtener solo arcos activos + */ + public function scopeActivos($query) + { + return $query->where('activo', true); + } + + /** + * Buscar arco por IP + */ + public static function buscarPorIp(string $ip): ?self + { + return self::where('ip_address', $ip)->first(); + } +} diff --git a/app/Services/ConsultaEstatalService.php b/app/Services/ConsultaEstatalService.php index c2a43db..c1997a2 100644 --- a/app/Services/ConsultaEstatalService.php +++ b/app/Services/ConsultaEstatalService.php @@ -14,25 +14,6 @@ public function __construct() $this->soapUrl = config('services.padron_estatal.url'); } - /** - * Consulta vehículo - */ - public function consultarPorEpc(string $epc): ?array - { - try { - $datos = $this->consultarPadron('niv', $epc); - - return [ - 'placa' => $datos['placa'] ?? null, - 'vin' => $datos['niv'] ?? null, - ]; - - } catch (Exception $e) { - Log::error("Error consultando padrón por EPC: {$epc} - " . $e->getMessage()); - return null; - } - } - /** * Consulta el padrón vehicular estatal vía SOAP */ diff --git a/app/Services/ReporteRoboService.php b/app/Services/ReporteRoboService.php index c489ff0..6c5b4ab 100644 --- a/app/Services/ReporteRoboService.php +++ b/app/Services/ReporteRoboService.php @@ -20,7 +20,7 @@ public function __construct() $this->password = config('services.repuve_federal.password'); } - public function consultarPorVin(?string $vin = null, ?string $placa = null) + public function consultarRobado(?string $vin = null, ?string $placa = null) { try { if (empty($vin) && empty($placa)) { @@ -43,11 +43,6 @@ public function consultarPorVin(?string $vin = null, ?string $placa = null) $arg2 = '||' . $placa . str_repeat('|', 5); } - Log::info('ReporteRoboService: Consultando REPUVE', [ - 'vin' => $vin, - 'placa' => $placa - ]); - $soapBody = << @@ -125,7 +120,7 @@ private function parseRoboResponse(string $soapResponse, string $valor) return [ 'tiene_reporte' => false, 'datos' => [], - 'error' => 'Respuesta SOAP inválida' + 'error' => 'Respuesta inválida' ]; } diff --git a/app/Services/VehicleService.php b/app/Services/VehicleService.php index 90b6afa..0a24520 100644 --- a/app/Services/VehicleService.php +++ b/app/Services/VehicleService.php @@ -4,94 +4,111 @@ use App\Models\Vehicle; use App\Models\Detection; +use App\Models\Arco; use Illuminate\Support\Facades\Redis; use Illuminate\Support\Facades\Log; class VehicleService { public function __construct( - private ConsultaEstatalService $consultaEstatal, private ReporteRoboService $reporteRobo, private ConsultaRepuveConstancia $consultaRepuveCons ) {} - public function procesarDeteccion(string $epc, ?int $arcoId = null): array + public function procesarDeteccion(string $epc, string $fastId, string $arcoIp, ?string $timestamp = null): array { $key = "vehiculo:robado:{$epc}"; + // Buscar o crear arco por IP + $arco = Arco::buscarPorIp($arcoIp); + $arcoId = $arco?->id; + // Verificar si está en Redis $enRedis = Redis::get($key); if ($enRedis) { // Ya está marcado como robado, verificar si sigue así - $resultado = $this->verificarVehiculoRobado($epc, $arcoId, json_decode($enRedis, true)); - $this->registrarDeteccion($epc, $resultado); + $resultado = $this->verificarVehiculoRobado($epc, json_decode($enRedis, true)); + $this->registrarDeteccion($epc, $resultado, $arcoId); return $resultado; } // No está en Redis, consultar servicios - $resultado = $this->consultarNuevoVehiculo($epc, $arcoId); - $this->registrarDeteccion($epc, $resultado); + $resultado = $this->consultarNuevoVehiculo($epc, $fastId); + $this->registrarDeteccion($epc, $resultado, $arcoId); return $resultado; } - private function consultarNuevoVehiculo(string $epc, ?int $arcoId): array + private function consultarNuevoVehiculo(string $epc, string $fastId): array { - // Consultar padrón estatal - $datosEstatal = $this->consultaEstatal->consultarPorEpc($epc); + // Consultar con FastID (tag_number) + $datosVehiculo = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId); + + if (!$datosVehiculo || !$datosVehiculo['vin']) { + Log::warning('Vehículo NO encontrado en API externa', [ + 'epc' => $epc, + 'fast_id' => $fastId + ]); - if (!$datosEstatal || !$datosEstatal['vin']) { return [ 'success' => false, - 'message' => 'No se encontró información del vehículo' + 'message' => 'No se encontró información del vehículo en la API externa' ]; } - // Consultar REPUVE - $reporteRobo = $this->reporteRobo->consultarPorVin( - $datosEstatal['vin'], - $datosEstatal['placa'] + // Consultar REPUVE para verificar si está robado + $reporteRobo = $this->reporteRobo->consultarRobado( + $datosVehiculo['vin'], + $datosVehiculo['placa'] ); if ($reporteRobo['tiene_reporte']) { // Está robado → Guardar en Redis - $this->guardarEnRedis($epc, $datosEstatal, $reporteRobo['datos']); + $this->guardarEnRedis($epc, $datosVehiculo, $reporteRobo['datos']); Log::warning('¡VEHÍCULO ROBADO DETECTADO!', [ 'epc' => $epc, - 'vin' => $datosEstatal['vin'], - 'placa' => $datosEstatal['placa'] + 'fast_id' => $fastId, + 'vin' => $datosVehiculo['vin'], + 'placa' => $datosVehiculo['placa'] ]); return [ 'success' => true, 'tiene_reporte_robo' => true, 'estado' => 'ROBADO', - 'accion' => 'GUARDADO_EN_REDIS', - 'vehiculo' => array_merge($datosEstatal, $reporteRobo['datos']) + 'accion' => 'GUARDADO EN BD DE ROBADOS', + 'vehiculo' => array_merge($datosVehiculo, $reporteRobo['datos']) ]; } - // No está robado, no hacer nada + // No está robado - vehículo LIBRE + Log::info('Vehículo detectado - LIBRE (sin reporte de robo)', [ + 'epc' => $epc, + 'fast_id' => $fastId, + 'vin' => $datosVehiculo['vin'], + 'placa' => $datosVehiculo['placa'] + ]); + return [ 'success' => true, 'tiene_reporte_robo' => false, 'estado' => 'LIBRE', - 'vehiculo' => $datosEstatal + 'vehiculo' => $datosVehiculo ]; } - private function verificarVehiculoRobado(string $epc, ?int $arcoId, array $datosRedis): array + private function verificarVehiculoRobado(string $epc, array $datosRedis): array { // Consultar REPUVE para verificar estado actual - $reporteRobo = $this->reporteRobo->consultarPorVin( + $reporteRobo = $this->reporteRobo->consultarRobado( $datosRedis['vin'], $datosRedis['placa'] ); if (!$reporteRobo['tiene_reporte']) { - // No tiene reporte robo - $this->registrarRecuperacion($epc, $arcoId, $datosRedis); + // No tiene reporte robo - RECUPERADO + $this->registrarRecuperacion($epc, $datosRedis); Log::info('¡VEHÍCULO RECUPERADO!', [ 'epc' => $epc, @@ -103,7 +120,7 @@ private function verificarVehiculoRobado(string $epc, ?int $arcoId, array $datos 'success' => true, 'tiene_reporte_robo' => false, 'estado' => 'RECUPERADO', - 'accion' => 'GUARDADO_EN_MYSQL_Y_ELIMINADO_DE_REDIS', + 'accion' => 'GUARDADO EN RECUPERADOS Y ELIMINADO DE ROBADOS', 'vehiculo' => $datosRedis ]; } @@ -156,7 +173,7 @@ private function actualizarDeteccionRedis(string $epc, array $datosActuales) Redis::set($key, json_encode($datosActuales)); } - private function registrarRecuperacion(string $epc, ?int $arcoId, array $datosRedis) + private function registrarRecuperacion(string $epc, array $datosRedis) { // Guardar en MySQL Vehicle::create([ @@ -191,7 +208,7 @@ public function listarVehiculosRobados(): array return $vehiculos; } - private function registrarDeteccion(string $epc, array $resultado) + private function registrarDeteccion(string $epc, array $resultado, ?int $arcoId = null) { if (!$resultado['success'] || !isset($resultado['vehiculo'])) { return; @@ -200,6 +217,7 @@ private function registrarDeteccion(string $epc, array $resultado) $vehiculo = $resultado['vehiculo']; Detection::create([ + 'arco_id' => $arcoId, 'epc' => $epc, 'vin' => $vehiculo['vin'] ?? null, 'placa' => $vehiculo['placa'] ?? null, diff --git a/database/migrations/2026_01_06_113001_create_arcos_table.php b/database/migrations/2026_01_06_113001_create_arcos_table.php new file mode 100644 index 0000000..cdaab82 --- /dev/null +++ b/database/migrations/2026_01_06_113001_create_arcos_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('nombre'); + $table->string('ip_address')->unique(); + $table->string('ubicacion')->nullable(); + $table->boolean('activo'); + $table->timestamps(); + + $table->index('ip_address'); + $table->index('activo'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('arcos'); + } +}; diff --git a/database/migrations/2026_01_06_113139_add_arco_id_to_detections_table.php b/database/migrations/2026_01_06_113139_add_arco_id_to_detections_table.php new file mode 100644 index 0000000..df9deec --- /dev/null +++ b/database/migrations/2026_01_06_113139_add_arco_id_to_detections_table.php @@ -0,0 +1,30 @@ +foreignId('arco_id')->nullable()->after('id')->constrained('arcos')->onDelete('set null'); + $table->index('arco_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('detections', function (Blueprint $table) { + $table->dropForeign(['arco_id']); + $table->dropColumn('arco_id'); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 6f67a39..805036a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,6 +1,7 @@ group(function() { + // Rutas de Vehículos Route::post('/vehicles/consultar', [VehicleController::class, 'consultarVehiculo']); Route::post('/vehicles/recuperar', [VehicleController::class, 'recuperarVehiculo']); + Route::post('/vehicles/buscar', [VehicleController::class, 'buscarPorTag']); Route::get('/vehicles/detectar', [VehicleController::class, 'buscarVehiculo']); Route::get('/vehicles/robados', [VehicleController::class, 'listarRobados']); Route::get('/vehicles', [VehicleController::class, 'listarRecuperados']); Route::get('/vehicles/detecciones', [VehicleController::class, 'listarDetecciones']); Route::get('/vehicles/robado', [VehicleController::class, 'buscarVehiculoRobado']); - Route::get('/vehicles/buscar', [VehicleController::class, 'buscarPorTag']); + + // Rutas de Arcos RFID + Route::resource('/arcos', ArcoController::class); + Route::patch('/arcos/{id}/toggle-estado', [ArcoController::class, 'toggleEstado']); }); /** Rutas públicas */