ADD: Implementa controlador y modelo para gestión de arcos RFID, incluyendo rutas y migraciones
This commit is contained in:
parent
19e9491e04
commit
505729ba19
156
app/Http/Controllers/Api/ArcoController.php
Normal file
156
app/Http/Controllers/Api/ArcoController.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Arco;
|
||||
use Illuminate\Http\Request;
|
||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
||||
|
||||
/**
|
||||
* Controlador para gestión de Arcos
|
||||
*/
|
||||
class ArcoController extends Controller
|
||||
{
|
||||
/**
|
||||
* Listar todos los arcos
|
||||
* GET /api/arcos
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Arco::query();
|
||||
|
||||
// Filtro por estado activo
|
||||
if ($request->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
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
59
app/Models/Arco.php
Normal file
59
app/Models/Arco.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php namespace App\Models;
|
||||
/**
|
||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
||||
*/
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* Modelo Arco RFID
|
||||
*
|
||||
* Representa un arco lector RFID físico identificado por su dirección IP
|
||||
*
|
||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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 = <<<XML
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
|
||||
<soapenv:Header/>
|
||||
@ -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'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
34
database/migrations/2026_01_06_113001_create_arcos_table.php
Normal file
34
database/migrations/2026_01_06_113001_create_arcos_table.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?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::create('arcos', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
<?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('detections', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\VehicleController;
|
||||
use App\Http\Controllers\Api\ArcoController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/**
|
||||
@ -20,14 +21,19 @@
|
||||
/** Rutas protegidas (requieren autenticación) */
|
||||
Route::middleware('auth:api')->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 */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user