Compare commits
No commits in common. "develop" and "main" have entirely different histories.
14
.env.example
14
.env.example
@ -1,9 +1,6 @@
|
|||||||
APP_NAME="Arcos"
|
APP_NAME="Arcos"
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=base64:2qBv3agj3nPac2/LlDEAiQBE3vV7ycffh4lhc0ksfGM=
|
APP_KEY=base64:2qBv3agj3nPac2/LlDEAiQBE3vV7ycffh4lhc0ksfGM=
|
||||||
# Clave dedicada para tokens de arcos RFID (NO CAMBIAR DESPUÉS DE GENERARLA)
|
|
||||||
# Generar con: php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
|
|
||||||
ARCO_TOKEN_ENCRYPTION_KEY=
|
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_TIMEZONE=America/Mexico_City
|
APP_TIMEZONE=America/Mexico_City
|
||||||
APP_URL=http://localhost:8080
|
APP_URL=http://localhost:8080
|
||||||
@ -58,7 +55,7 @@ CACHE_PREFIX=
|
|||||||
MEMCACHED_HOST=127.0.0.1
|
MEMCACHED_HOST=127.0.0.1
|
||||||
|
|
||||||
REDIS_CLIENT=phpredis
|
REDIS_CLIENT=phpredis
|
||||||
REDIS_HOST=redis
|
REDIS_HOST=127.0.0.1
|
||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
@ -78,7 +75,6 @@ AWS_DEFAULT_REGION=us-east-1
|
|||||||
AWS_BUCKET=
|
AWS_BUCKET=
|
||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
# API REPUVE FEDERAL
|
|
||||||
REPUVE_FED_BASE_URL=
|
REPUVE_FED_BASE_URL=
|
||||||
REPUVE_FED_USERNAME=
|
REPUVE_FED_USERNAME=
|
||||||
REPUVE_FED_PASSWORD=
|
REPUVE_FED_PASSWORD=
|
||||||
@ -86,14 +82,6 @@ REPUVE_FED_PASSWORD=
|
|||||||
# REPUVE ESTATAL
|
# REPUVE ESTATAL
|
||||||
REPUVE_EST_URL=
|
REPUVE_EST_URL=
|
||||||
|
|
||||||
# API REPUVE CONSTANCIAS
|
|
||||||
REPUVE_CONSTANCIA_BASE_URL=
|
|
||||||
REPUVE_CONSTANCIA_LOGIN_ENDPOINT=
|
|
||||||
REPUVE_CONSTANCIA_CONSULTA_ENDPOINT=
|
|
||||||
REPUVE_CONSTANCIA_EMAIL=
|
|
||||||
REPUVE_CONSTANCIA_PASSWORD=
|
|
||||||
REPUVE_CONSTANCIA_TOKEN_TTL=3600
|
|
||||||
|
|
||||||
REVERB_APP_ID=
|
REVERB_APP_ID=
|
||||||
REVERB_APP_KEY=
|
REVERB_APP_KEY=
|
||||||
REVERB_APP_SECRET=
|
REVERB_APP_SECRET=
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,7 +14,6 @@
|
|||||||
.env.production
|
.env.production
|
||||||
.phpactor.json
|
.phpactor.json
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
CLAUDE.md
|
|
||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
auth.json
|
auth.json
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
server {
|
server {
|
||||||
listen 7003;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
root /var/www/arcos-backend/public;
|
root /var/www/arcos-backend/public;
|
||||||
index index.php index.html;
|
index index.php index.html;
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Models\DailyDetection;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class LimpiarDeteccionesDiarias extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'detecciones:limpiar {--dias=1 : Eliminar registros más antiguos de X días}';
|
|
||||||
protected $description = 'Elimina las detecciones diarias antiguas de la tabla daily_detections';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$dias = (int) $this->option('dias');
|
|
||||||
$fecha = now()->subDays($dias)->format('Y-m-d');
|
|
||||||
|
|
||||||
$this->info("Eliminando detecciones anteriores a: {$fecha}");
|
|
||||||
|
|
||||||
$eliminados = DailyDetection::whereDate('fecha_deteccion', '<', $fecha)->delete();
|
|
||||||
|
|
||||||
$this->info("{$eliminados} registros eliminados de daily_detections");
|
|
||||||
|
|
||||||
Log::info("Limpieza automática de detecciones diarias", [
|
|
||||||
'registros_eliminados' => $eliminados,
|
|
||||||
'fecha_limite' => $fecha
|
|
||||||
]);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
<?php namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Services\ArcoSimuladorService;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class SimularArcoReforma extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'arco:simular
|
|
||||||
{--min=5 : Tiempo mínimo de espera entre vehículos (segundos)}
|
|
||||||
{--max=30 : Tiempo máximo de espera entre vehículos (segundos)}
|
|
||||||
{--single : Simular solo un vehículo}';
|
|
||||||
|
|
||||||
protected $description = 'Simula vehículos pasando por el ARCO RFID de Reforma';
|
|
||||||
|
|
||||||
public function handle(ArcoSimuladorService $simulador): int
|
|
||||||
{
|
|
||||||
$min = (int) $this->option('min');
|
|
||||||
$max = (int) $this->option('max');
|
|
||||||
$single = $this->option('single');
|
|
||||||
|
|
||||||
if ($single) {
|
|
||||||
$this->info('Simulando paso de un vehículo...');
|
|
||||||
$resultado = $simulador->simularPasoVehiculo();
|
|
||||||
|
|
||||||
if ($resultado['success']) {
|
|
||||||
$this->info("Vehículo detectado: {$resultado['vehiculo']['placa']} - {$resultado['vehiculo']['marca']} {$resultado['vehiculo']['modelo']}");
|
|
||||||
$this->line(json_encode($resultado['deteccion'], JSON_PRETTY_PRINT));
|
|
||||||
} else {
|
|
||||||
$this->error($resultado['message']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->info("Iniciando simulación continua del ARCO Reforma...");
|
|
||||||
$this->info("Tiempo de espera entre vehículos: {$min}-{$max} segundos");
|
|
||||||
$this->warn("Presiona Ctrl+C para detener la simulación");
|
|
||||||
|
|
||||||
$simulador->iniciarSimulacionContinua($min, $max);
|
|
||||||
|
|
||||||
return Command::SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Events;
|
|
||||||
|
|
||||||
use App\Models\AlertaRobo;
|
|
||||||
use Illuminate\Broadcasting\Channel;
|
|
||||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
|
||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class VehiculoRobadoDetectado implements ShouldBroadcast
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
|
||||||
|
|
||||||
public AlertaRobo $alerta;
|
|
||||||
|
|
||||||
public function __construct(AlertaRobo $alerta)
|
|
||||||
{
|
|
||||||
$this->alerta = $alerta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Canal público para todos los usuarios
|
|
||||||
*/
|
|
||||||
public function broadcastOn(): Channel
|
|
||||||
{
|
|
||||||
return new Channel('alertas-robos');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Nombre del evento en el frontend
|
|
||||||
*/
|
|
||||||
public function broadcastAs(): string
|
|
||||||
{
|
|
||||||
return 'vehiculo.robado.detectado';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Datos que se envían al frontend
|
|
||||||
*/
|
|
||||||
public function broadcastWith(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'alerta_id' => $this->alerta->id,
|
|
||||||
'fast_id' => $this->alerta->fast_id,
|
|
||||||
'vin' => $this->alerta->vin,
|
|
||||||
'placa' => $this->alerta->placa,
|
|
||||||
'marca' => $this->alerta->marca,
|
|
||||||
'modelo' => $this->alerta->modelo,
|
|
||||||
'color' => $this->alerta->color,
|
|
||||||
'arco_id' => $this->alerta->arco_id,
|
|
||||||
'arco_nombre' => $this->alerta->arco_nombre,
|
|
||||||
'antena' => $this->alerta->antena,
|
|
||||||
'fecha_deteccion' => $this->alerta->fecha_deteccion->toIso8601String(),
|
|
||||||
'mensaje' => "🚨 VEHÍCULO ROBADO DETECTADO: {$this->alerta->placa} en {$this->alerta->arco_nombre}",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
<?php
|
|
||||||
namespace App\Helpers;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class EncryptionHelper
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt using a custom key (independent of APP_KEY)
|
|
||||||
* Useful for tokens that should survive APP_KEY rotation
|
|
||||||
*/
|
|
||||||
public static function encryptWithCustomKey(string $string, string $key): string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$cipher = 'AES-256-CBC';
|
|
||||||
$ivLength = openssl_cipher_iv_length($cipher);
|
|
||||||
$iv = openssl_random_pseudo_bytes($ivLength);
|
|
||||||
|
|
||||||
$encrypted = openssl_encrypt($string, $cipher, $key, 0, $iv);
|
|
||||||
|
|
||||||
if ($encrypted === false) {
|
|
||||||
throw new \RuntimeException('Encryption failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combinar IV + encrypted data y codificar en base64
|
|
||||||
return base64_encode($iv . $encrypted);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \RuntimeException("Error al encriptar con clave personalizada: " . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt using a custom key (independent of APP_KEY)
|
|
||||||
*/
|
|
||||||
public static function decryptWithCustomKey(string $encryptedString, string $key): ?string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$cipher = 'AES-256-CBC';
|
|
||||||
$ivLength = openssl_cipher_iv_length($cipher);
|
|
||||||
|
|
||||||
// Decodificar y separar IV + encrypted data
|
|
||||||
$data = base64_decode($encryptedString);
|
|
||||||
if ($data === false) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$iv = substr($data, 0, $ivLength);
|
|
||||||
$encrypted = substr($data, $ivLength);
|
|
||||||
|
|
||||||
$decrypted = openssl_decrypt($encrypted, $cipher, $key, 0, $iv);
|
|
||||||
|
|
||||||
if ($decrypted === false) {
|
|
||||||
Log::error('Error al desencriptar con clave personalizada');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $decrypted;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('Error inesperado al desencriptar con clave personalizada: ' . $e->getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify if a plain value matches a value encrypted with custom key
|
|
||||||
*/
|
|
||||||
public static function verifyWithCustomKey(string $plainValue, string $encryptedValue, string $key): bool
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$decrypted = self::decryptWithCustomKey($encryptedValue, $key);
|
|
||||||
return $decrypted === $plainValue;
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -26,7 +26,7 @@ class RoleController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$model = Role::orderBy('description')->where('id', '!=', '1');
|
$model = Role::orderBy('description');
|
||||||
|
|
||||||
QuerySupport::queryByKey($model, request(), 'name');
|
QuerySupport::queryByKey($model, request(), 'name');
|
||||||
|
|
||||||
@ -61,12 +61,6 @@ public function show(Role $role)
|
|||||||
*/
|
*/
|
||||||
public function update(RoleUpdateRequest $request, Role $role)
|
public function update(RoleUpdateRequest $request, Role $role)
|
||||||
{
|
{
|
||||||
if(in_array($role->id, [1, 2])) {
|
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'message' => 'No se puede modificar este rol'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$role->update($request->all());
|
$role->update($request->all());
|
||||||
|
|
||||||
return ApiResponse::OK->response();
|
return ApiResponse::OK->response();
|
||||||
@ -77,11 +71,6 @@ public function update(RoleUpdateRequest $request, Role $role)
|
|||||||
*/
|
*/
|
||||||
public function destroy(Role $role)
|
public function destroy(Role $role)
|
||||||
{
|
{
|
||||||
if(in_array($role->id, [1, 2])) {
|
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'message' => 'No se puede eliminar este rol'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$role->delete();
|
$role->delete();
|
||||||
|
|
||||||
return ApiResponse::OK->response();
|
return ApiResponse::OK->response();
|
||||||
@ -92,12 +81,6 @@ public function destroy(Role $role)
|
|||||||
*/
|
*/
|
||||||
public function permissions(Role $role)
|
public function permissions(Role $role)
|
||||||
{
|
{
|
||||||
if(in_array($role->id, [1, 2])) {
|
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'message' => 'No se puede eliminar este rol'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
return ApiResponse::OK->response([
|
||||||
'permissions' => $role->permissions
|
'permissions' => $role->permissions
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class UserController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$users = User::orderBy('id', 'DESC')->where('id', '!=', 1);
|
$users = User::orderBy('name');
|
||||||
|
|
||||||
QuerySupport::queryByKeys($users, ['name', 'email']);
|
QuerySupport::queryByKeys($users, ['name', 'email']);
|
||||||
|
|
||||||
|
|||||||
@ -1,133 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\AlertaRobo;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
||||||
|
|
||||||
class AlertaRoboController extends Controller
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Listar alertas pendientes (no vistas)
|
|
||||||
* GET /api/alertas/pendientes
|
|
||||||
*/
|
|
||||||
public function pendientes(Request $request)
|
|
||||||
{
|
|
||||||
$alertas = AlertaRobo::pendientes()
|
|
||||||
->recientes()
|
|
||||||
->with(['arco', 'usuario'])
|
|
||||||
->get();
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'total' => $alertas->count(),
|
|
||||||
'alertas' => $alertas
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listar todas las alertas con filtros
|
|
||||||
* GET /api/alertas
|
|
||||||
*/
|
|
||||||
public function index(Request $request)
|
|
||||||
{
|
|
||||||
$query = AlertaRobo::query()->with(['arco', 'usuario']);
|
|
||||||
|
|
||||||
// Filtro por estado (visto/no visto)
|
|
||||||
if ($request->has('visto')) {
|
|
||||||
$query->where('visto', $request->boolean('visto'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtro por arco
|
|
||||||
if ($request->has('arco_id')) {
|
|
||||||
$query->where('arco_id', $request->arco_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtro por placa
|
|
||||||
if ($request->has('placa') && !empty($request->placa)) {
|
|
||||||
$query->where('placa', 'like', '%' . $request->placa . '%');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtro por VIN
|
|
||||||
if ($request->has('vin') && !empty($request->vin)) {
|
|
||||||
$query->where('vin', 'like', '%' . $request->vin . '%');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtro por rango de fechas
|
|
||||||
if ($request->has('fecha_desde')) {
|
|
||||||
$query->where('fecha_deteccion', '>=', $request->fecha_desde);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->has('fecha_hasta')) {
|
|
||||||
$query->where('fecha_deteccion', '<=', $request->fecha_hasta);
|
|
||||||
}
|
|
||||||
|
|
||||||
$alertas = $query->orderBy('fecha_deteccion', 'desc')
|
|
||||||
->paginate($request->input('per_page', 15));
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'alertas' => $alertas
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ver una alerta específica
|
|
||||||
* GET /api/alertas/{id}
|
|
||||||
*/
|
|
||||||
public function show(string $id)
|
|
||||||
{
|
|
||||||
$alerta = AlertaRobo::with(['arco', 'usuario'])->find($id);
|
|
||||||
|
|
||||||
if (!$alerta) {
|
|
||||||
return ApiResponse::NOT_FOUND->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Alerta no encontrada'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'alerta' => $alerta
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirmar/marcar alerta como vista
|
|
||||||
* PUT /api/alertas/{id}/confirmar
|
|
||||||
*/
|
|
||||||
public function confirmar(Request $request, string $id)
|
|
||||||
{
|
|
||||||
$alerta = AlertaRobo::find($id);
|
|
||||||
|
|
||||||
if (!$alerta) {
|
|
||||||
return ApiResponse::NOT_FOUND->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Alerta no encontrada'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($alerta->visto) {
|
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Esta alerta ya fue confirmada anteriormente',
|
|
||||||
'confirmada_por' => $alerta->usuario?->name,
|
|
||||||
'fecha_confirmacion' => $alerta->fecha_confirmacion
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marcar como vista
|
|
||||||
$alerta->visto = true;
|
|
||||||
$alerta->usuario_id = auth()->id(); // Usuario autenticado que confirmó
|
|
||||||
$alerta->fecha_confirmacion = now();
|
|
||||||
$alerta->save();
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Alerta confirmada exitosamente',
|
|
||||||
'alerta' => $alerta
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,225 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
|
||||||
use App\Models\Arco;
|
|
||||||
use App\Services\VehicleService;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controlador para gestión de Arcos
|
|
||||||
*/
|
|
||||||
class ArcoController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private VehicleService $vehicleService
|
|
||||||
) {}
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
|
|
||||||
// Agregar token desencriptado a cada arco
|
|
||||||
$arcos->each(function($arco) {
|
|
||||||
$arco->api_token_plain = $arco->obtenerTokenDesencriptado();
|
|
||||||
});
|
|
||||||
|
|
||||||
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',
|
|
||||||
'antena_1' => 'nullable|string',
|
|
||||||
'antena_2' => 'nullable|string',
|
|
||||||
'antena_3' => 'nullable|string',
|
|
||||||
'antena_4' => 'nullable|string',
|
|
||||||
'activo' => 'boolean'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$arco = Arco::create($validated);
|
|
||||||
|
|
||||||
// Obtener el token en texto plano para mostrárselo al usuario
|
|
||||||
$plainToken = $arco->obtenerTokenDesencriptado();
|
|
||||||
|
|
||||||
return ApiResponse::CREATED->response([
|
|
||||||
'message' => 'Arco creado exitosamente',
|
|
||||||
'arco' => $arco,
|
|
||||||
'api_token' => $plainToken // Token en texto plano para que el arco lo use
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mostrar un arco específico
|
|
||||||
* GET /api/arcos/{id}
|
|
||||||
*/
|
|
||||||
public function show(Arco $arco)
|
|
||||||
{
|
|
||||||
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',
|
|
||||||
'antena_1' => 'nullable|string',
|
|
||||||
'antena_2' => 'nullable|string',
|
|
||||||
'antena_3' => 'nullable|string',
|
|
||||||
'antena_4' => 'nullable|string'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$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
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listar detecciones del día de un arco específico
|
|
||||||
* GET /api/arcos/{id}/detecciones/dia
|
|
||||||
*/
|
|
||||||
public function deteccionesDelDia(Request $request, int $id)
|
|
||||||
{
|
|
||||||
$arco = Arco::find($id);
|
|
||||||
|
|
||||||
if (!$arco) {
|
|
||||||
return ApiResponse::NOT_FOUND->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Arco no encontrado'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$fecha = $request->input('fecha'); // Formato: Y-m-d (opcional)
|
|
||||||
|
|
||||||
if ($fecha && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) {
|
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Formato de fecha inválido. Use YYYY-MM-DD'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$detecciones = $this->vehicleService->listarDeteccionesDelDiaPorArco($id, $fecha);
|
|
||||||
|
|
||||||
// Filtrar solo VIN, placa, antena y si es robado
|
|
||||||
$deteccionesFiltradas = array_map(function($deteccion) {
|
|
||||||
return [
|
|
||||||
'tag_id' => $deteccion['fast_id'] ?? null,
|
|
||||||
'vin' => $deteccion['vin'] ?? null,
|
|
||||||
'placa' => $deteccion['placa'] ?? null,
|
|
||||||
'antena' => $deteccion['antena'] ?? null,
|
|
||||||
'robado' => $deteccion['tiene_reporte_robo'] ?? false,
|
|
||||||
'timestamp' => $deteccion['fecha_deteccion'] ?? null,
|
|
||||||
];
|
|
||||||
}, $detecciones);
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'arco' => [
|
|
||||||
'id' => $arco->id,
|
|
||||||
'nombre' => $arco->nombre,
|
|
||||||
'ubicacion' => $arco->ubicacion,
|
|
||||||
],
|
|
||||||
'fecha' => $fecha ?? now()->format('Y-m-d'),
|
|
||||||
'total' => count($deteccionesFiltradas),
|
|
||||||
'detecciones' => $deteccionesFiltradas
|
|
||||||
]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return ApiResponse::INTERNAL_ERROR->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al obtener detecciones del arco',
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,11 @@
|
|||||||
<?php
|
<?php namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\ProcesarDeteccionVehiculo;
|
|
||||||
use App\Models\Vehicle;
|
use App\Models\Vehicle;
|
||||||
use App\Models\Detection;
|
|
||||||
use App\Services\VehicleService;
|
use App\Services\VehicleService;
|
||||||
|
use App\Services\ConsultaEstatalService;
|
||||||
|
use App\Services\ReporteRoboService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
@ -17,14 +14,15 @@
|
|||||||
class VehicleController extends Controller
|
class VehicleController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private VehicleService $vehicleService
|
private VehicleService $vehicleService,
|
||||||
|
private ConsultaEstatalService $consultaEstatal,
|
||||||
|
private ReporteRoboService $reporteRobo
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consultar vehículo por placa/VIN y darlo de alta como robado
|
* Consultar y registrar vehículo por PLACA o VIN
|
||||||
* POST /api/vehicles/consultar
|
|
||||||
*/
|
*/
|
||||||
public function vehiculoRobadoAlta(Request $request): JsonResponse
|
public function consultarVehiculo(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'placa' => 'required_without:vin|string|nullable',
|
'placa' => 'required_without:vin|string|nullable',
|
||||||
@ -35,70 +33,127 @@ public function vehiculoRobadoAlta(Request $request): JsonResponse
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Datos inválidos',
|
'message' => 'Datos inválidos',
|
||||||
'errors' => $validator->errors()
|
'errors' => $validator->errors()
|
||||||
]);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
$datosVehiculo = null;
|
try {
|
||||||
|
$placa = $request->placa;
|
||||||
|
$vin = $request->vin;
|
||||||
|
|
||||||
if ($request->vin) {
|
// Consultar padrón estatal (obtener datos completos del vehículo)
|
||||||
$datosVehiculo = $this->vehicleService->consultarVehiculoPorTag($request->vin);
|
Log::info('Consultando padrón estatal', ['placa' => $placa, 'vin' => $vin]);
|
||||||
}
|
|
||||||
|
|
||||||
if (!$datosVehiculo && $request->placa) {
|
// Si viene VIN, usar VIN, si no usar placa
|
||||||
$datosVehiculo = $this->vehicleService->consultarVehiculoPorTag($request->placa);
|
$criterio = $vin ?: $placa;
|
||||||
}
|
$datosEstatal = $this->consultaEstatal->consultarPorEpc($criterio);
|
||||||
|
|
||||||
if (!$datosVehiculo || !isset($datosVehiculo['tag_number'])) {
|
if (!$datosEstatal || (!$datosEstatal['vin'] && !$datosEstatal['placa'])) {
|
||||||
return ApiResponse::NOT_FOUND->response([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'No se encontró el vehículo en el sistema'
|
'message' => 'No se encontró información del vehículo en el padrón estatal'
|
||||||
]);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fastId = $datosVehiculo['tag_number'];
|
// Obtener VIN y Placa completos
|
||||||
|
$vinCompleto = $datosEstatal['vin'] ?: $vin;
|
||||||
|
$placaCompleta = $datosEstatal['placa'] ?: $placa;
|
||||||
|
|
||||||
// Verificar si ya está en Redis
|
Log::info('Datos del padrón estatal obtenidos', [
|
||||||
$key = "vehiculo:robado:{$fastId}";
|
'vin' => $vinCompleto,
|
||||||
if (Redis::exists($key)) {
|
'placa' => $placaCompleta
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'El vehículo ya está registrado como robado'
|
|
||||||
]);
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
// Guardar en Redis usando la estructura del VehicleService
|
// Consultar REPUVE
|
||||||
$datos = [
|
$reporteRobo = $this->reporteRobo->consultarPorVin($vinCompleto, $placaCompleta);
|
||||||
'fast_id' => $fastId,
|
|
||||||
'vin' => $datosVehiculo['vin'] ?? null,
|
|
||||||
'placa' => $datosVehiculo['placa'] ?? null,
|
|
||||||
'marca' => $datosVehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $datosVehiculo['modelo'] ?? null,
|
|
||||||
'color' => $datosVehiculo['color'] ?? null,
|
|
||||||
'fecha_robo' => $request->fecha_robo,
|
|
||||||
'autoridad' => $request->autoridad,
|
|
||||||
'acta' => $request->acta,
|
|
||||||
'denunciante' => $request->denunciante,
|
|
||||||
'fecha_acta' => $request->fecha_acta,
|
|
||||||
'primera_deteccion' => now()->toIso8601String(),
|
|
||||||
'ultima_deteccion' => now()->toIso8601String(),
|
|
||||||
'detecciones' => 0
|
|
||||||
];
|
|
||||||
|
|
||||||
Redis::set($key, json_encode($datos));
|
$tieneReporte = $reporteRobo['tiene_reporte'] ?? false;
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
// Si tiene reporte de robo → Guardar en Redis
|
||||||
|
if ($tieneReporte) {
|
||||||
|
$epc = 'MANUAL_' . ($vinCompleto ?: $placaCompleta);
|
||||||
|
|
||||||
|
$this->guardarEnRedis($epc, $datosEstatal, $reporteRobo['datos']);
|
||||||
|
|
||||||
|
Log::warning('¡VEHÍCULO CON REPORTE DE ROBO REGISTRADO!', [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta,
|
||||||
|
'autoridad' => $reporteRobo['datos']['autoridad'] ?? null
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Vehículo consultado y registrado como robado exitosamente',
|
'tiene_reporte_robo' => true,
|
||||||
'vehiculo' => $datos
|
'message' => '¡ALERTA! El vehículo tiene reporte de robo',
|
||||||
|
'vehiculo' => [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta,
|
||||||
|
],
|
||||||
|
'reporte_robo' => $reporteRobo['datos']
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No tiene reporte de robo
|
||||||
|
Log::info('Vehículo consultado sin reporte de robo', [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => false,
|
||||||
|
'message' => 'Vehículo sin reporte de robo',
|
||||||
|
'vehiculo' => [
|
||||||
|
'vin' => $vinCompleto,
|
||||||
|
'placa' => $placaCompleta,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error al consultar vehículo', [
|
||||||
|
'placa' => $request->placa,
|
||||||
|
'vin' => $request->vin,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Error al consultar vehículo',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dar de baja (registrar como recuperado)
|
* Guardar vehículo robado en Redis
|
||||||
|
*/
|
||||||
|
private function guardarEnRedis(string $epc, array $datosEstatal, array $datosRobo): void
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
$datos = [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosEstatal['vin'],
|
||||||
|
'placa' => $datosEstatal['placa'],
|
||||||
|
'fecha_robo' => $datosRobo['fecha_robo'] ?? null,
|
||||||
|
'autoridad' => $datosRobo['autoridad'] ?? null,
|
||||||
|
'acta' => $datosRobo['acta'] ?? null,
|
||||||
|
'denunciante' => $datosRobo['denunciante'] ?? null,
|
||||||
|
'fecha_acta' => $datosRobo['fecha_acta'] ?? null,
|
||||||
|
'primera_deteccion' => now()->toIso8601String(),
|
||||||
|
'ultima_deteccion' => now()->toIso8601String(),
|
||||||
|
'detecciones' => 0,
|
||||||
|
'origen' => 'CONSULTA_MANUAL'
|
||||||
|
];
|
||||||
|
|
||||||
|
Redis::set($key, json_encode($datos));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dar de baja (registrar como recuperado) un vehículo por PLACA o VIN
|
||||||
*/
|
*/
|
||||||
public function recuperarVehiculo(Request $request): JsonResponse
|
public function recuperarVehiculo(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
@ -111,59 +166,76 @@ public function recuperarVehiculo(Request $request): JsonResponse
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Datos inválidos',
|
'message' => 'Datos inválidos',
|
||||||
'errors' => $validator->errors()
|
'errors' => $validator->errors()
|
||||||
]);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$placa = $request->placa;
|
$placa = $request->placa;
|
||||||
$vin = $request->vin;
|
$vin = $request->vin;
|
||||||
|
|
||||||
|
Log::info('Buscando vehículo robado para recuperar', ['placa' => $placa, 'vin' => $vin]);
|
||||||
|
|
||||||
// 1. Buscar en Redis (vehículos robados activos)
|
// 1. Buscar en Redis (vehículos robados activos)
|
||||||
$vehiculoEncontrado = $this->buscarEnRedis($vin, $placa);
|
$vehiculoEncontrado = $this->buscarEnRedis($vin, $placa);
|
||||||
|
|
||||||
if (!$vehiculoEncontrado) {
|
if (!$vehiculoEncontrado) {
|
||||||
return ApiResponse::NOT_FOUND->response([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'El vehículo no se encuentra en la lista de robados activos'
|
'message' => 'El vehículo no se encuentra en la lista de robados activos'
|
||||||
]);
|
], 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$datosRedis = $vehiculoEncontrado['datos'];
|
$datosRedis = $vehiculoEncontrado['datos'];
|
||||||
|
$epc = $vehiculoEncontrado['epc'];
|
||||||
|
|
||||||
|
Log::info('Vehículo encontrado en Redis', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa']
|
||||||
|
]);
|
||||||
|
|
||||||
// 2. Guardar en MySQL como recuperado
|
// 2. Guardar en MySQL como recuperado
|
||||||
$vehiculoRecuperado = Vehicle::create([
|
$vehiculoRecuperado = Vehicle::create([
|
||||||
'fast_id' => $datosRedis['fast_id'] ?? null,
|
'epc' => $epc,
|
||||||
'vin' => $datosRedis['vin'] ?? null,
|
'vin' => $datosRedis['vin'],
|
||||||
'placa' => $datosRedis['placa'] ?? null,
|
'placa' => $datosRedis['placa'],
|
||||||
'fecha_robo' => $datosRedis['fecha_robo'] ?? null,
|
'fecha_robo' => $datosRedis['fecha_robo'],
|
||||||
'acta_robo' => $datosRedis['acta'] ?? null,
|
'autoridad_robo' => $datosRedis['autoridad'],
|
||||||
'denunciante' => $datosRedis['denunciante'] ?? null,
|
'acta_robo' => $datosRedis['acta'],
|
||||||
|
'denunciante' => $datosRedis['denunciante'],
|
||||||
'fecha_acta' => $datosRedis['fecha_acta'] ?? null,
|
'fecha_acta' => $datosRedis['fecha_acta'] ?? null,
|
||||||
'fecha_recuperacion' => now(),
|
'fecha_recuperacion' => now(),
|
||||||
'datos_robo_original' => $datosRedis
|
'datos_robo_original' => $datosRedis
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 3. Eliminar de Redis
|
// 3. Eliminar de Redis
|
||||||
$fastId = $vehiculoEncontrado['fast_id'];
|
Redis::del("vehiculo:robado:{$epc}");
|
||||||
Redis::del("vehiculo:robado:{$fastId}");
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
Log::info('Vehículo registrado como recuperado', [
|
||||||
|
'id' => $vehiculoRecuperado->id,
|
||||||
|
'vin' => $vehiculoRecuperado->vin,
|
||||||
|
'placa' => $vehiculoRecuperado->placa
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'message' => 'Vehículo registrado como recuperado exitosamente',
|
'message' => 'Vehículo registrado como recuperado exitosamente',
|
||||||
'vehiculo' => [
|
'vehiculo' => [
|
||||||
'id' => $vehiculoRecuperado->id,
|
'id' => $vehiculoRecuperado->id,
|
||||||
'fast_id' => $vehiculoRecuperado->fast_id,
|
'epc' => $vehiculoRecuperado->epc,
|
||||||
'vin' => $vehiculoRecuperado->vin,
|
'vin' => $vehiculoRecuperado->vin,
|
||||||
'placa' => $vehiculoRecuperado->placa,
|
'placa' => $vehiculoRecuperado->placa,
|
||||||
'fecha_robo' => $vehiculoRecuperado->fecha_robo,
|
'fecha_robo' => $vehiculoRecuperado->fecha_robo,
|
||||||
'fecha_recuperacion' => $vehiculoRecuperado->fecha_recuperacion,
|
'fecha_recuperacion' => $vehiculoRecuperado->fecha_recuperacion,
|
||||||
|
'autoridad_robo' => $vehiculoRecuperado->autoridad_robo,
|
||||||
'acta_robo' => $vehiculoRecuperado->acta_robo,
|
'acta_robo' => $vehiculoRecuperado->acta_robo,
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Error al recuperar vehículo', [
|
Log::error('Error al recuperar vehículo', [
|
||||||
'placa' => $request->placa,
|
'placa' => $request->placa,
|
||||||
@ -171,16 +243,16 @@ public function recuperarVehiculo(Request $request): JsonResponse
|
|||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return ApiResponse::INTERNAL_ERROR->response([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Error al registrar vehículo como recuperado',
|
'message' => 'Error al registrar vehículo como recuperado',
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage()
|
||||||
]);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buscar vehículo en Redis
|
* Buscar vehículo en Redis por VIN o Placa
|
||||||
*/
|
*/
|
||||||
private function buscarEnRedis(?string $vin, ?string $placa): ?array
|
private function buscarEnRedis(?string $vin, ?string $placa): ?array
|
||||||
{
|
{
|
||||||
@ -196,11 +268,11 @@ private function buscarEnRedis(?string $vin, ?string $placa): ?array
|
|||||||
|
|
||||||
// Buscar por VIN o Placa
|
// Buscar por VIN o Placa
|
||||||
if (($vin && $vehiculo['vin'] === $vin) || ($placa && $vehiculo['placa'] === $placa)) {
|
if (($vin && $vehiculo['vin'] === $vin) || ($placa && $vehiculo['placa'] === $placa)) {
|
||||||
// Extraer el fast_id del key
|
// Extraer el EPC del key
|
||||||
$fastId = str_replace('vehiculo:robado:', '', $key);
|
$epc = str_replace('vehiculo:robado:', '', $key);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'fast_id' => $fastId,
|
'epc' => $epc,
|
||||||
'datos' => $vehiculo
|
'datos' => $vehiculo
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -213,220 +285,24 @@ private function buscarEnRedis(?string $vin, ?string $placa): ?array
|
|||||||
* Listar todos los vehículos robados activos
|
* Listar todos los vehículos robados activos
|
||||||
* GET /api/vehicles/robados
|
* GET /api/vehicles/robados
|
||||||
*/
|
*/
|
||||||
public function listarRobados(Request $request)
|
public function listarRobados(): JsonResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$vehiculos = $this->vehicleService->listarVehiculosRobados();
|
$vehiculos = $this->vehicleService->listarVehiculosRobados();
|
||||||
|
|
||||||
$collection = collect($vehiculos);
|
return response()->json([
|
||||||
|
|
||||||
// Filtro por placa
|
|
||||||
if ($request->has('placa') && !empty($request->placa)) {
|
|
||||||
$collection = $collection->filter(function ($vehiculo) use ($request) {
|
|
||||||
return isset($vehiculo['placa']) &&
|
|
||||||
stripos($vehiculo['placa'], $request->placa) !== false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtro por VIN
|
|
||||||
if ($request->has('vin') && !empty($request->vin)) {
|
|
||||||
$collection = $collection->filter(function ($vehiculo) use ($request) {
|
|
||||||
return isset($vehiculo['vin']) &&
|
|
||||||
stripos($vehiculo['vin'], $request->vin) !== false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$perPage = $request->input('per_page', config('app.pagination', 15));
|
|
||||||
$page = $request->input('page', 1);
|
|
||||||
|
|
||||||
$paginatedVehiculos = new \Illuminate\Pagination\LengthAwarePaginator(
|
|
||||||
$collection->forPage($page, $perPage),
|
|
||||||
$collection->count(),
|
|
||||||
$perPage,
|
|
||||||
$page,
|
|
||||||
['path' => $request->url(), 'query' => $request->query()]
|
|
||||||
);
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'total' => $collection->count(),
|
'total' => count($vehiculos),
|
||||||
'vehiculos' => $paginatedVehiculos
|
'vehiculos' => $vehiculos
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'Error al listar vehículos robados',
|
'message' => 'Error al listar vehículos robados',
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage()
|
||||||
]);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listarRecuperados()
|
|
||||||
{
|
|
||||||
$vehiculos = Vehicle::orderBy('fecha_recuperacion', 'desc')
|
|
||||||
->paginate(config('app.pagination'));
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'vehiculos' => $vehiculos
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function listarDetecciones(Request $request)
|
|
||||||
{
|
|
||||||
$query = Detection::query();
|
|
||||||
|
|
||||||
// Filtro por placa
|
|
||||||
if ($request->has('placa') && !empty($request->placa)) {
|
|
||||||
$query->where('placa', 'like', '%' . $request->placa . '%');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($request->has('vin') && !empty($request->vin)) {
|
|
||||||
$query->where('vin', 'like', '%' . $request->vin . '%');
|
|
||||||
}
|
|
||||||
|
|
||||||
$detecciones = $query->orderBy('fecha_deteccion', 'desc')
|
|
||||||
->paginate(config('app.pagination'));
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'detecciones' => $detecciones
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listar detecciones del día desde Redis
|
|
||||||
* GET /api/vehicles/detecciones/dia
|
|
||||||
*/
|
|
||||||
public function listarDeteccionesDelDia(Request $request)
|
|
||||||
{
|
|
||||||
$fecha = $request->input('fecha'); // Formato: Y-m-d (opcional)
|
|
||||||
|
|
||||||
if ($fecha && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) {
|
|
||||||
return ApiResponse::BAD_REQUEST->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Formato de fecha inválido. Use YYYY-MM-DD'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$detecciones = $this->vehicleService->listarDeteccionesDelDia($fecha);
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'fecha' => $fecha ?? now()->format('Y-m-d'),
|
|
||||||
'total' => count($detecciones),
|
|
||||||
'detecciones' => $detecciones
|
|
||||||
]);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return ApiResponse::INTERNAL_ERROR->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'Error al obtener detecciones del día',
|
|
||||||
'error' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Buscar vehículo por placa o VIN en el servicio externo
|
|
||||||
* GET /api/vehicles/detectar
|
|
||||||
* Consulta datos sin dar de alta como robado
|
|
||||||
*/
|
|
||||||
public function buscarVehiculo(Request $request)
|
|
||||||
{
|
|
||||||
$validated = $request->validate([
|
|
||||||
'placa' => 'required_without:vin|string|nullable',
|
|
||||||
'vin' => 'required_without:placa|string|nullable',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$criterio = $validated['vin'] ?? $validated['placa'];
|
|
||||||
|
|
||||||
// Consultar en el servicio externo
|
|
||||||
$datosVehiculo = $this->vehicleService->consultarVehiculoPorTag($criterio);
|
|
||||||
|
|
||||||
if (!$datosVehiculo) {
|
|
||||||
return ApiResponse::NOT_FOUND->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No se encontró el vehículo en el sistema'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'vehiculo' => [
|
|
||||||
'fast_id' => $datosVehiculo['tag_number'] ?? null,
|
|
||||||
'folio_tag' => $datosVehiculo['folio_tag'] ?? null,
|
|
||||||
'vin' => $datosVehiculo['vin'] ?? null,
|
|
||||||
'placa' => $datosVehiculo['placa'] ?? null,
|
|
||||||
'marca' => $datosVehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $datosVehiculo['modelo'] ?? null,
|
|
||||||
'linea' => $datosVehiculo['linea'] ?? null,
|
|
||||||
'sublinea' => $datosVehiculo['sublinea'] ?? null,
|
|
||||||
'color' => $datosVehiculo['color'] ?? null,
|
|
||||||
'clase_veh' => $datosVehiculo['clase_veh'] ?? null,
|
|
||||||
'tipo_servicio' => $datosVehiculo['tipo_servicio'] ?? null,
|
|
||||||
'propietario' => $datosVehiculo['propietario'] ?? null,
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buscarVehiculoRobado(Request $request)
|
|
||||||
{
|
|
||||||
$validated = $request->validate([
|
|
||||||
'placa' => 'required_without:vin|string|nullable',
|
|
||||||
'vin' => 'required_without:placa|string|nullable',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$placa = $validated['placa'] ?? null;
|
|
||||||
$vin = $validated['vin'] ?? null;
|
|
||||||
|
|
||||||
// Buscar en Redis
|
|
||||||
$vehiculoEncontrado = $this->buscarEnRedis($vin, $placa);
|
|
||||||
|
|
||||||
if (!$vehiculoEncontrado) {
|
|
||||||
return ApiResponse::NOT_FOUND->response([
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'El vehículo no está registrado como robado'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Vehículo encontrado en lista de robados',
|
|
||||||
'vehiculo' => $vehiculoEncontrado['datos']
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Procesar detección de tag RFID
|
|
||||||
* POST /api/vehicles/buscar
|
|
||||||
* Requiere autenticación mediante token de arco
|
|
||||||
*/
|
|
||||||
public function buscarPorTag(Request $request)
|
|
||||||
{
|
|
||||||
$validated = $request->validate([
|
|
||||||
'fast_id' => 'required|string',
|
|
||||||
'antena' => 'nullable|string',
|
|
||||||
'timestamp' => 'nullable|date'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Obtener el arco autenticado del middleware
|
|
||||||
$arco = $request->get('arco_autenticado');
|
|
||||||
|
|
||||||
ProcesarDeteccionVehiculo::dispatch(
|
|
||||||
$validated['fast_id'],
|
|
||||||
$arco->id,
|
|
||||||
$validated['antena'] ?? null
|
|
||||||
);
|
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
|
||||||
'success' => true,
|
|
||||||
'message' => 'Detección recibida y en proceso',
|
|
||||||
'fast_id' => $validated['fast_id'],
|
|
||||||
'arco' => [
|
|
||||||
'id' => $arco->id,
|
|
||||||
'nombre' => $arco->nombre
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Middleware;
|
|
||||||
|
|
||||||
use App\Models\Arco;
|
|
||||||
use Closure;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Notsoweb\ApiResponse\Enums\ApiResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class ArcoTokenMiddleware
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Handle an incoming request.
|
|
||||||
*/
|
|
||||||
public function handle(Request $request, Closure $next): Response
|
|
||||||
{
|
|
||||||
$tokenPlano = $request->bearerToken();
|
|
||||||
|
|
||||||
if (!$tokenPlano) {
|
|
||||||
return ApiResponse::UNAUTHORIZED->response([
|
|
||||||
'message' => 'Token de arco no proporcionado'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buscar arco por token plano
|
|
||||||
$arco = Arco::buscarPorToken($tokenPlano);
|
|
||||||
|
|
||||||
if (!$arco) {
|
|
||||||
return ApiResponse::UNAUTHORIZED->response([
|
|
||||||
'message' => 'Token de arco inválido'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$arco->activo) {
|
|
||||||
return ApiResponse::FORBIDDEN->response([
|
|
||||||
'message' => 'Arco desactivado'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Agregar el arco al request para uso posterior
|
|
||||||
$request->merge(['arco_autenticado' => $arco]);
|
|
||||||
|
|
||||||
return $next($request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
<?php namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Services\VehicleService;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ProcesarDeteccionVehiculo implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Número de intentos antes de marcar como fallido
|
|
||||||
*/
|
|
||||||
public $tries = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Timeout en segundos (2 minutos)
|
|
||||||
*/
|
|
||||||
public $timeout = 120;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tiempo entre reintentos en segundos
|
|
||||||
*/
|
|
||||||
public $backoff = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor del Job
|
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
public string $fastId,
|
|
||||||
public int $arcoId,
|
|
||||||
public ?string $antena = null
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ejecutar el job
|
|
||||||
*/
|
|
||||||
public function handle(VehicleService $vehicleService): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// Procesar la detección usando el servicio
|
|
||||||
$vehicleService->procesarDeteccion(
|
|
||||||
$this->fastId,
|
|
||||||
$this->arcoId,
|
|
||||||
$this->antena
|
|
||||||
);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// Re-lanzar la excepción para que Laravel reintente el job
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manejar fallo del job después de todos los reintentos
|
|
||||||
*/
|
|
||||||
public function failed(\Throwable $exception): void
|
|
||||||
{
|
|
||||||
Log::error('ProcesarDeteccionVehiculo: FALLIDO después de todos los intentos', [
|
|
||||||
'fast_id' => $this->fastId,
|
|
||||||
'arco_id' => $this->arcoId,
|
|
||||||
'antena' => $this->antena,
|
|
||||||
'error' => $exception->getMessage(),
|
|
||||||
'intentos_realizados' => $this->tries
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
class AlertaRobo extends Model
|
|
||||||
{
|
|
||||||
protected $table = 'alertas_robos';
|
|
||||||
|
|
||||||
protected $fillable = [
|
|
||||||
'fast_id',
|
|
||||||
'vin',
|
|
||||||
'placa',
|
|
||||||
'marca',
|
|
||||||
'modelo',
|
|
||||||
'color',
|
|
||||||
'arco_id',
|
|
||||||
'arco_nombre',
|
|
||||||
'antena',
|
|
||||||
'fecha_deteccion',
|
|
||||||
'visto',
|
|
||||||
'usuario_id',
|
|
||||||
'fecha_confirmacion',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'visto' => 'boolean',
|
|
||||||
'fecha_deteccion' => 'datetime',
|
|
||||||
'fecha_confirmacion' => 'datetime',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relación con el arco
|
|
||||||
*/
|
|
||||||
public function arco(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Arco::class, 'arco_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relación con el usuario que confirmó
|
|
||||||
*/
|
|
||||||
public function usuario(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'usuario_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope para obtener alertas pendientes (no vistas)
|
|
||||||
*/
|
|
||||||
public function scopePendientes($query)
|
|
||||||
{
|
|
||||||
return $query->where('visto', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope para obtener alertas vistas
|
|
||||||
*/
|
|
||||||
public function scopeVistas($query)
|
|
||||||
{
|
|
||||||
return $query->where('visto', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope para ordenar por más reciente
|
|
||||||
*/
|
|
||||||
public function scopeRecientes($query)
|
|
||||||
{
|
|
||||||
return $query->orderBy('fecha_deteccion', 'desc');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,142 +0,0 @@
|
|||||||
<?php namespace App\Models;
|
|
||||||
/**
|
|
||||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
|
||||||
*/
|
|
||||||
|
|
||||||
use App\Helpers\EncryptionHelper;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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',
|
|
||||||
'antena_1',
|
|
||||||
'antena_2',
|
|
||||||
'antena_3',
|
|
||||||
'antena_4'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'activo' => 'boolean',
|
|
||||||
'created_at' => 'datetime',
|
|
||||||
'updated_at' => 'datetime'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $hidden = [
|
|
||||||
'api_token'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected static function boot()
|
|
||||||
{
|
|
||||||
parent::boot();
|
|
||||||
|
|
||||||
static::creating(function ($arco) {
|
|
||||||
if (!$arco->api_token) {
|
|
||||||
$plainToken = Str::random(64);
|
|
||||||
$arco->api_token = EncryptionHelper::encryptWithCustomKey(
|
|
||||||
$plainToken,
|
|
||||||
self::getEncryptionKey()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtener la clave de encriptación personalizada para tokens de arcos
|
|
||||||
*/
|
|
||||||
private static function getEncryptionKey(): string
|
|
||||||
{
|
|
||||||
$key = config('app.arco_token_encryption_key');
|
|
||||||
|
|
||||||
if (empty($key)) {
|
|
||||||
throw new \RuntimeException('ARCO_TOKEN_ENCRYPTION_KEY no está configurada en .env');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si la clave está en formato base64:xxxx, decodificarla
|
|
||||||
if (str_starts_with($key, 'base64:')) {
|
|
||||||
return base64_decode(substr($key, 7));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Buscar arco por API token (recibe token plano, busca desencriptando)
|
|
||||||
*/
|
|
||||||
public static function buscarPorToken(string $tokenPlano): ?self
|
|
||||||
{
|
|
||||||
$arcos = self::all();
|
|
||||||
|
|
||||||
foreach ($arcos as $arco) {
|
|
||||||
// Desencriptar el token de la BD y comparar con el token plano recibido
|
|
||||||
if (EncryptionHelper::verifyWithCustomKey($tokenPlano, $arco->api_token, self::getEncryptionKey())) {
|
|
||||||
return $arco;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regenerar token encriptado
|
|
||||||
*/
|
|
||||||
public function regenerarToken(): string
|
|
||||||
{
|
|
||||||
$plainToken = Str::random(64);
|
|
||||||
$this->api_token = EncryptionHelper::encryptWithCustomKey(
|
|
||||||
$plainToken,
|
|
||||||
self::getEncryptionKey()
|
|
||||||
);
|
|
||||||
$this->save();
|
|
||||||
|
|
||||||
return $plainToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtener token desencriptado
|
|
||||||
*/
|
|
||||||
public function obtenerTokenDesencriptado(): ?string
|
|
||||||
{
|
|
||||||
return EncryptionHelper::decryptWithCustomKey($this->api_token, self::getEncryptionKey());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
<?php namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class DailyDetection extends Model
|
|
||||||
{
|
|
||||||
public $timestamps = false;
|
|
||||||
|
|
||||||
protected $fillable = [
|
|
||||||
'arco_id',
|
|
||||||
'arco_nombre',
|
|
||||||
'antena',
|
|
||||||
'fast_id',
|
|
||||||
'vin',
|
|
||||||
'placa',
|
|
||||||
'marca',
|
|
||||||
'modelo',
|
|
||||||
'color',
|
|
||||||
'estado',
|
|
||||||
'tiene_reporte_robo',
|
|
||||||
'fecha_deteccion'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'fecha_deteccion' => 'datetime:Y-m-d H:i:s',
|
|
||||||
'tiene_reporte_robo' => 'boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relación con el modelo Arco
|
|
||||||
*/
|
|
||||||
public function arco()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Arco::class, 'arco_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope para filtrar por fecha del día
|
|
||||||
*/
|
|
||||||
public function scopeDelDia($query, ?string $fecha = null)
|
|
||||||
{
|
|
||||||
$fecha = $fecha ?? now()->format('Y-m-d');
|
|
||||||
return $query->whereDate('fecha_deteccion', $fecha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope para filtrar por arco
|
|
||||||
*/
|
|
||||||
public function scopePorArco($query, int $arcoId)
|
|
||||||
{
|
|
||||||
return $query->where('arco_id', $arcoId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
<?php namespace App\Models;
|
|
||||||
/**
|
|
||||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
|
||||||
*/
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modelo para registrar detecciones de vehículos por el ARCO RFID
|
|
||||||
*
|
|
||||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
|
||||||
*
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
class Detection extends Model
|
|
||||||
{
|
|
||||||
public $timestamps = false;
|
|
||||||
|
|
||||||
protected $fillable = [
|
|
||||||
'arco_id',
|
|
||||||
'antena',
|
|
||||||
'fast_id',
|
|
||||||
'vin',
|
|
||||||
'placa',
|
|
||||||
'marca',
|
|
||||||
'modelo',
|
|
||||||
'color',
|
|
||||||
'fecha_deteccion'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $casts = [
|
|
||||||
'fecha_deteccion' => 'datetime:Y-m-d H:i:s'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Relación con el modelo Arco
|
|
||||||
*/
|
|
||||||
public function arco()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Arco::class, 'arco_id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -45,7 +45,7 @@ class User extends Authenticatable
|
|||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'password',
|
'password',
|
||||||
//'profile_photo_path',
|
'profile_photo_path',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
class Vehicle extends Model
|
class Vehicle extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'fast_id',
|
'epc',
|
||||||
'vin',
|
'vin',
|
||||||
'placa',
|
'placa',
|
||||||
'fecha_robo',
|
'fecha_robo',
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
<?php namespace App\Models;
|
|
||||||
/**
|
|
||||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Descripción
|
|
||||||
*
|
|
||||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
|
||||||
*
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
class VehicleFake extends Model
|
|
||||||
{
|
|
||||||
protected $fillable = [
|
|
||||||
'folio_tag',
|
|
||||||
'tag_number',
|
|
||||||
'placa',
|
|
||||||
'vin',
|
|
||||||
'marca',
|
|
||||||
'color',
|
|
||||||
'modelo',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use App\Models\VehicleFake;
|
|
||||||
use App\Models\Detection;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ArcoSimuladorService
|
|
||||||
{
|
|
||||||
public function simularPasoVehiculo(): array
|
|
||||||
{
|
|
||||||
$vehiculo = VehicleFake::inRandomOrder()->first();
|
|
||||||
|
|
||||||
if (!$vehiculo) {
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No hay vehículos disponibles para simular'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$epc = $this->generarEPC($vehiculo->vin);
|
|
||||||
|
|
||||||
Detection::create([
|
|
||||||
'epc' => $epc,
|
|
||||||
'vin' => $vehiculo->vin,
|
|
||||||
'placa' => $vehiculo->placa,
|
|
||||||
'marca' => $vehiculo->marca,
|
|
||||||
'modelo' => $vehiculo->modelo,
|
|
||||||
'color' => $vehiculo->color,
|
|
||||||
'fecha_deteccion' => now()
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'vehiculo' => [
|
|
||||||
'placa' => $vehiculo->placa,
|
|
||||||
'vin' => $vehiculo->vin,
|
|
||||||
'marca' => $vehiculo->marca,
|
|
||||||
'modelo' => $vehiculo->modelo,
|
|
||||||
'color' => $vehiculo->color,
|
|
||||||
'epc' => $epc
|
|
||||||
],
|
|
||||||
'mensaje' => 'Detección registrada localmente'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function iniciarSimulacionContinua(int $minSegundos = 5, int $maxSegundos = 30): void
|
|
||||||
{
|
|
||||||
while (true) {
|
|
||||||
$this->simularPasoVehiculo();
|
|
||||||
|
|
||||||
$espera = rand($minSegundos, $maxSegundos);
|
|
||||||
Log::info("Esperando {$espera} segundos para próxima simulación...");
|
|
||||||
|
|
||||||
sleep($espera);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function generarEPC(string $vin): string
|
|
||||||
{
|
|
||||||
$prefijo = 'E280117000000';
|
|
||||||
$sufijo = substr(strtoupper($vin), -8);
|
|
||||||
$relleno = str_pad($sufijo, 11, '0', STR_PAD_LEFT);
|
|
||||||
|
|
||||||
return $prefijo . $relleno;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function obtenerTiempoAleatorio(int $min = 5, int $max = 30): int
|
|
||||||
{
|
|
||||||
return rand($min, $max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,6 +14,25 @@ public function __construct()
|
|||||||
$this->soapUrl = config('services.padron_estatal.url');
|
$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
|
* Consulta el padrón vehicular estatal vía SOAP
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,244 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class ConsultaRepuveConstancia
|
|
||||||
{
|
|
||||||
private string $baseUrl;
|
|
||||||
private string $loginEndpoint;
|
|
||||||
private string $vehiculosEndpoint;
|
|
||||||
private string $email;
|
|
||||||
private string $password;
|
|
||||||
private int $tokenTtl;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->baseUrl = config('services.vehiculos_externos.base_url');
|
|
||||||
$this->loginEndpoint = config('services.vehiculos_externos.login_endpoint');
|
|
||||||
$this->vehiculosEndpoint = config('services.vehiculos_externos.vehiculos_endpoint');
|
|
||||||
$this->email = config('services.vehiculos_externos.email');
|
|
||||||
$this->password = config('services.vehiculos_externos.password');
|
|
||||||
$this->tokenTtl = config('services.vehiculos_externos.token_ttl', 3600);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consultar vehículo por tag_number, placa o VIN
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public function consultarVehiculoPorTag(?string $criterio = null)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// Validar que se proporcionó algún criterio de búsqueda
|
|
||||||
if (empty($criterio)) {
|
|
||||||
Log::warning('ConsultaRepuveConstancia: No se proporcionó criterio de búsqueda');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtener token de autenticación
|
|
||||||
$token = $this->obtenerToken();
|
|
||||||
if (!$token) {
|
|
||||||
Log::error('ConsultaRepuveConstancia: No se pudo obtener token de autenticación');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = $this->baseUrl . $this->vehiculosEndpoint;
|
|
||||||
|
|
||||||
Log::info('ConsultaRepuveConstancia: Consultando', [
|
|
||||||
'url' => $url,
|
|
||||||
'criterio' => $criterio
|
|
||||||
]);
|
|
||||||
|
|
||||||
$headers = [
|
|
||||||
'Authorization' => 'Bearer ' . $token,
|
|
||||||
'Accept' => 'application/json',
|
|
||||||
];
|
|
||||||
|
|
||||||
// Intentar buscar por tag_number primero
|
|
||||||
$response = Http::withHeaders($headers)->timeout(30)->get($url, [
|
|
||||||
'tag_number' => $criterio
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->successful()) {
|
|
||||||
$data = $response->json();
|
|
||||||
if (
|
|
||||||
isset($data['status']) && $data['status'] === 'success'
|
|
||||||
&& isset($data['data']['records']['data'])
|
|
||||||
&& !empty($data['data']['records']['data'])
|
|
||||||
) {
|
|
||||||
$vehiculoEncontrado = $data['data']['records']['data'][0];
|
|
||||||
return $this->transformarDatosVehiculo($vehiculoEncontrado);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentar búsqueda por placa SOLO si el anterior NO encontró nada
|
|
||||||
$response = Http::withHeaders($headers)->timeout(30)->get($url, [
|
|
||||||
'placa' => $criterio
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($response->successful()) {
|
|
||||||
$data = $response->json();
|
|
||||||
if (
|
|
||||||
isset($data['status']) && $data['status'] === 'success'
|
|
||||||
&& isset($data['data']['records']['data'])
|
|
||||||
&& !empty($data['data']['records']['data'])
|
|
||||||
) {
|
|
||||||
$vehiculoEncontrado = $data['data']['records']['data'][0];
|
|
||||||
return $this->transformarDatosVehiculo($vehiculoEncontrado);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intentar búsqueda por VIN SOLO si los anteriores NO encontraron nada
|
|
||||||
$response = Http::withHeaders($headers)->timeout(30)->get($url, [
|
|
||||||
'vin' => $criterio
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!$response->successful()) {
|
|
||||||
Log::error('ConsultaRepuveConstancia: Error en petición HTTP', [
|
|
||||||
'status' => $response->status(),
|
|
||||||
'body' => $response->body()
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $response->json();
|
|
||||||
|
|
||||||
// Validar estructura de respuesta y que haya datos
|
|
||||||
if (
|
|
||||||
!isset($data['status']) || $data['status'] !== 'success'
|
|
||||||
|| !isset($data['data']['records']['data'])
|
|
||||||
|| empty($data['data']['records']['data'])
|
|
||||||
) {
|
|
||||||
|
|
||||||
Log::info('ConsultaRepuveConstancia: Vehículo no encontrado en ningún criterio', [
|
|
||||||
'criterio' => $criterio
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$vehiculoEncontrado = $data['data']['records']['data'][0];
|
|
||||||
return $this->transformarDatosVehiculo($vehiculoEncontrado);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error('ConsultaRepuveConstancia: Error al consultar vehículo', [
|
|
||||||
'criterio' => $criterio,
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString()
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtener token JWT
|
|
||||||
*/
|
|
||||||
private function obtenerToken()
|
|
||||||
{
|
|
||||||
$cacheKey = 'vehiculos_externos_jwt_token';
|
|
||||||
|
|
||||||
// Intentar obtener token de caché
|
|
||||||
$token = Cache::get($cacheKey);
|
|
||||||
if ($token) {
|
|
||||||
return $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si no está en caché, autenticar
|
|
||||||
try {
|
|
||||||
$url = $this->baseUrl . $this->loginEndpoint;
|
|
||||||
|
|
||||||
Log::info('ConsultaRepuveConstancia: Autenticando', [
|
|
||||||
'url' => $url,
|
|
||||||
'email' => $this->email
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = Http::timeout(30)->post($url, [
|
|
||||||
'email' => $this->email,
|
|
||||||
'password' => $this->password,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!$response->successful()) {
|
|
||||||
Log::error('ConsultaRepuveConstancia: Error al autenticar', [
|
|
||||||
'status' => $response->status(),
|
|
||||||
'body' => $response->body()
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = $response->json();
|
|
||||||
|
|
||||||
// Validar estructura de respuesta
|
|
||||||
if (!isset($data['status']) || $data['status'] !== 'success' || !isset($data['data']['token'])) {
|
|
||||||
Log::error('ConsultaRepuveConstancia: Respuesta de login inválida', [
|
|
||||||
'response' => $data
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$token = $data['data']['token'];
|
|
||||||
|
|
||||||
// Guardar en caché (hora por defecto)
|
|
||||||
Cache::put($cacheKey, $token, $this->tokenTtl);
|
|
||||||
|
|
||||||
Log::info('ConsultaRepuveConstancia: Token obtenido y guardado en caché');
|
|
||||||
|
|
||||||
return $token;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Log::error('ConsultaRepuveConstancia: Excepción al obtener token', [
|
|
||||||
'error' => $e->getMessage(),
|
|
||||||
'trace' => $e->getTraceAsString()
|
|
||||||
]);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transformar datos del vehículo al formato esperado
|
|
||||||
*/
|
|
||||||
private function transformarDatosVehiculo(array $record)
|
|
||||||
{
|
|
||||||
$vehiculo = $record['vehicle'];
|
|
||||||
$tag = $vehiculo['tag'] ?? [];
|
|
||||||
$owner = $vehiculo['owner'] ?? [];
|
|
||||||
|
|
||||||
return [
|
|
||||||
'tag_number' => $tag['tag_number'] ?? null,
|
|
||||||
'folio_tag' => $tag['folio'] ?? null,
|
|
||||||
'vin' => $vehiculo['niv'] ?? null,
|
|
||||||
'placa' => $vehiculo['placa'] ?? null,
|
|
||||||
'marca' => $vehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $vehiculo['modelo'] ?? null,
|
|
||||||
'linea' => $vehiculo['linea'] ?? null,
|
|
||||||
'sublinea' => $vehiculo['sublinea'] ?? null,
|
|
||||||
'color' => $vehiculo['color'] ?? null,
|
|
||||||
'numero_motor' => $vehiculo['numero_motor'] ?? null,
|
|
||||||
'clase_veh' => $vehiculo['clase_veh'] ?? null,
|
|
||||||
'tipo_servicio' => $vehiculo['tipo_servicio'] ?? null,
|
|
||||||
'rfv' => $vehiculo['rfv'] ?? null,
|
|
||||||
'nrpv' => $vehiculo['nrpv'] ?? null,
|
|
||||||
'reporte_robo' => $vehiculo['reporte_robo'] ?? false,
|
|
||||||
'propietario' => [
|
|
||||||
'id' => $owner['id'] ?? null,
|
|
||||||
'nombre_completo' => $owner['full_name'] ?? null,
|
|
||||||
'rfc' => $owner['rfc'] ?? null,
|
|
||||||
'curp' => $owner['curp'] ?? null,
|
|
||||||
'telefono' => $owner['telefono'] ?? null,
|
|
||||||
'direccion' => $owner['address'] ?? null,
|
|
||||||
],
|
|
||||||
'tag_status' => $tag['status']['name'] ?? null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limpiar token de caché (útil para forzar re-autenticación)
|
|
||||||
*/
|
|
||||||
public function limpiarToken()
|
|
||||||
{
|
|
||||||
Cache::forget('vehiculos_externos_jwt_token');
|
|
||||||
Log::info('ConsultaRepuveConstancia: Token eliminado de caché');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -20,7 +20,7 @@ public function __construct()
|
|||||||
$this->password = config('services.repuve_federal.password');
|
$this->password = config('services.repuve_federal.password');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function consultarRobado(?string $vin = null, ?string $placa = null)
|
public function consultarPorVin(?string $vin = null, ?string $placa = null): array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (empty($vin) && empty($placa)) {
|
if (empty($vin) && empty($placa)) {
|
||||||
@ -43,6 +43,11 @@ public function consultarRobado(?string $vin = null, ?string $placa = null)
|
|||||||
$arg2 = '||' . $placa . str_repeat('|', 5);
|
$arg2 = '||' . $placa . str_repeat('|', 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info('ReporteRoboService: Consultando REPUVE', [
|
||||||
|
'vin' => $vin,
|
||||||
|
'placa' => $placa
|
||||||
|
]);
|
||||||
|
|
||||||
$soapBody = <<<XML
|
$soapBody = <<<XML
|
||||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
|
||||||
<soapenv:Header/>
|
<soapenv:Header/>
|
||||||
@ -110,17 +115,17 @@ public function consultarRobado(?string $vin = null, ?string $placa = null)
|
|||||||
/**
|
/**
|
||||||
* Parsea la respuesta del servicio de robo
|
* Parsea la respuesta del servicio de robo
|
||||||
*/
|
*/
|
||||||
private function parseRoboResponse(string $soapResponse, string $valor)
|
private function parseRoboResponse(string $soapResponse, string $valor): array
|
||||||
{
|
{
|
||||||
// Extraer contenido
|
// Extraer contenido del tag <return>
|
||||||
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
|
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
|
||||||
|
|
||||||
if (!isset($matches[1])) {
|
if (!isset($matches[1])) {
|
||||||
Log::error('ReporteRoboService:');
|
Log::error('ReporteRoboService: No se encontró tag <return>');
|
||||||
return [
|
return [
|
||||||
'tiene_reporte' => false,
|
'tiene_reporte' => false,
|
||||||
'datos' => [],
|
'datos' => [],
|
||||||
'error' => 'Respuesta inválida'
|
'error' => 'Respuesta SOAP inválida'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,199 +177,11 @@ private function parseRoboResponse(string $soapResponse, string $valor)
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::error('ReporteRoboService:', ['contenido' => $contenido]);
|
Log::error('ReporteRoboService: Formato no reconocido', ['contenido' => $contenido]);
|
||||||
return [
|
return [
|
||||||
'tiene_reporte' => false,
|
'tiene_reporte' => false,
|
||||||
'datos' => [],
|
'datos' => [],
|
||||||
'error' => 'Formato de respuesta no reconocido'
|
'error' => 'Formato de respuesta no reconocido'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Consultar datos del vehículo (sin reporte de robo)
|
|
||||||
*/
|
|
||||||
public function consultarVehiculo(?string $niv = null, ?string $placa = null): array
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (empty($niv) && empty($placa)) {
|
|
||||||
Log::warning('ReporteRoboService: consultarVehiculo sin NIV ni PLACA');
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => 'Debe proporcionar al menos NIV o PLACA',
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$url = $this->baseUrl . '/jaxws-consultarpv/ConsultaRpv';
|
|
||||||
|
|
||||||
// Construir arg2
|
|
||||||
if ($placa) {
|
|
||||||
$arg2 = ($niv ?? '') . '|' . $placa . str_repeat('|', 5);
|
|
||||||
} else {
|
|
||||||
$arg2 = ($niv ?? '') . str_repeat('|', 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
$soapBody = <<<XML
|
|
||||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://consultaRpv.org/wsdl">
|
|
||||||
<soapenv:Header/>
|
|
||||||
<soapenv:Body>
|
|
||||||
<wsdl:doConsRPV>
|
|
||||||
<arg0>{$this->username}</arg0>
|
|
||||||
<arg1>{$this->password}</arg1>
|
|
||||||
<arg2>{$arg2}</arg2>
|
|
||||||
</wsdl:doConsRPV>
|
|
||||||
</soapenv:Body>
|
|
||||||
</soapenv:Envelope>
|
|
||||||
XML;
|
|
||||||
|
|
||||||
$ch = curl_init($url);
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $soapBody);
|
|
||||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
|
||||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
|
||||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
||||||
'Content-Type: text/xml; charset=utf-8',
|
|
||||||
'SOAPAction: "doConsRPV"',
|
|
||||||
'Content-Length: ' . strlen($soapBody),
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
$error = curl_error($ch);
|
|
||||||
|
|
||||||
if ($error) {
|
|
||||||
Log::error('ReporteRoboService: consultarVehiculo - Error de conexión', [
|
|
||||||
'error' => $error,
|
|
||||||
'niv' => $niv,
|
|
||||||
'placa' => $placa
|
|
||||||
]);
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => 'Error de conexión',
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($httpCode !== 200) {
|
|
||||||
Log::error('ReporteRoboService: consultarVehiculo - HTTP error', [
|
|
||||||
'http_code' => $httpCode,
|
|
||||||
'niv' => $niv,
|
|
||||||
'placa' => $placa
|
|
||||||
]);
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => "Error HTTP {$httpCode}",
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->parseConsultaVehiculoResponse($response);
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
unset($ch);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error('ReporteRoboService: consultarVehiculo - Excepción', [
|
|
||||||
'niv' => $niv,
|
|
||||||
'placa' => $placa,
|
|
||||||
'exception' => $e->getMessage()
|
|
||||||
]);
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => $e->getMessage(),
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsear respuesta de consulta de vehículo
|
|
||||||
* Extrae solo: VIN, placa, marca, modelo y color
|
|
||||||
*/
|
|
||||||
private function parseConsultaVehiculoResponse(string $soapResponse): array
|
|
||||||
{
|
|
||||||
// Extraer contenido del tag <return>
|
|
||||||
preg_match('/<return>(.*?)<\/return>/s', $soapResponse, $matches);
|
|
||||||
|
|
||||||
if (!isset($matches[1])) {
|
|
||||||
Log::error('ReporteRoboService: parseConsultaVehiculoResponse - Respuesta inválida');
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => 'Respuesta inválida del servicio',
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$contenido = trim($matches[1]);
|
|
||||||
|
|
||||||
// Verificar si hay error
|
|
||||||
if (preg_match('/(ERR|ERROR):(-?\d+)/i', $contenido, $errorMatch)) {
|
|
||||||
$errorCode = $errorMatch[2];
|
|
||||||
Log::warning('ReporteRoboService: parseConsultaVehiculoResponse - Error del servicio', [
|
|
||||||
'error_code' => $errorCode
|
|
||||||
]);
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => "Error REPUVE código {$errorCode}",
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si empieza con OK:, parsear los datos
|
|
||||||
if (str_starts_with($contenido, 'OK:')) {
|
|
||||||
$datos = str_replace('OK:', '', $contenido);
|
|
||||||
$valores = explode('|', $datos);
|
|
||||||
|
|
||||||
// Estructura esperada del REPUVE Nacional
|
|
||||||
// Posiciones aproximadas basadas en el servicio
|
|
||||||
$datosVehiculo = [
|
|
||||||
'vin' => $valores[0] ?? null,
|
|
||||||
'placa' => $valores[1] ?? null,
|
|
||||||
'marca' => $valores[2] ?? null,
|
|
||||||
'modelo' => $valores[3] ?? null,
|
|
||||||
'color' => $valores[4] ?? null,
|
|
||||||
];
|
|
||||||
|
|
||||||
// Filtrar valores vacíos
|
|
||||||
$datosVehiculo = array_filter($datosVehiculo, fn($v) => !empty($v));
|
|
||||||
|
|
||||||
if (empty($datosVehiculo)) {
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => false,
|
|
||||||
'error_message' => 'No se encontró información del vehículo',
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info('ReporteRoboService: Vehículo encontrado en REPUVE Nacional', [
|
|
||||||
'vin' => $datosVehiculo['vin'] ?? null,
|
|
||||||
'placa' => $datosVehiculo['placa'] ?? null
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'has_error' => false,
|
|
||||||
'datos' => $datosVehiculo
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::error('ReporteRoboService: parseConsultaVehiculoResponse - Formato no reconocido', [
|
|
||||||
'contenido' => $contenido
|
|
||||||
]);
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'has_error' => true,
|
|
||||||
'error_message' => 'Formato de respuesta no reconocido',
|
|
||||||
'datos' => []
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,106 +2,174 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Events\VehiculoRobadoDetectado;
|
use App\Models\Vehicle;
|
||||||
use App\Models\AlertaRobo;
|
|
||||||
use App\Models\Detection;
|
|
||||||
use App\Models\DailyDetection;
|
|
||||||
use App\Models\Arco;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class VehicleService
|
class VehicleService
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private ConsultaRepuveConstancia $consultaRepuveCons,
|
private ConsultaEstatalService $consultaEstatal,
|
||||||
private ReporteRoboService $reporteRoboService // Agregar esta dependencia
|
private ReporteRoboService $reporteRobo
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
public function procesarDeteccion(string $epc, ?int $arcoId = null): array
|
||||||
* Procesar detección de vehículo por fast_id
|
|
||||||
*/
|
|
||||||
public function procesarDeteccion(string $fastId, int $arcoId, ?string $antena = null): array
|
|
||||||
{
|
{
|
||||||
$key = "vehiculo:robado:{$fastId}";
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
// Verificar si está en Redis
|
// Verificar si está en Redis
|
||||||
$enRedis = Redis::get($key);
|
$enRedis = Redis::get($key);
|
||||||
|
|
||||||
if ($enRedis) {
|
if ($enRedis) {
|
||||||
// Ya está marcado como robado, verificar si sigue así
|
// Ya está marcado como robado, verificar si sigue así
|
||||||
$resultado = $this->verificarVehiculoRobado($fastId, json_decode($enRedis, true), $arcoId, $antena);
|
return $this->verificarVehiculoRobado($epc, $arcoId, json_decode($enRedis, true));
|
||||||
$this->registrarDeteccion($fastId, $resultado, $arcoId, $antena);
|
|
||||||
return $resultado;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No está en Redis, consultar servicios
|
// No está en Redis, consultar servicios
|
||||||
$resultado = $this->consultarNuevoVehiculo($fastId);
|
return $this->consultarNuevoVehiculo($epc, $arcoId);
|
||||||
$this->registrarDeteccion($fastId, $resultado, $arcoId, $antena);
|
|
||||||
return $resultado;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function consultarNuevoVehiculo(string $epc, ?int $arcoId): array
|
||||||
* Consultar vehículo por tag_id, placa o VIN
|
|
||||||
*/
|
|
||||||
public function consultarVehiculoPorTag(string $fastId)
|
|
||||||
{
|
{
|
||||||
try {
|
// Consultar padrón estatal
|
||||||
//Intentar con REPUVE Constancia primero
|
$datosEstatal = $this->consultaEstatal->consultarPorEpc($epc);
|
||||||
$vehiculoExterno = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId);
|
|
||||||
|
|
||||||
if ($vehiculoExterno) {
|
if (!$datosEstatal || !$datosEstatal['vin']) {
|
||||||
Log::info('VehicleService: Vehículo encontrado en REPUVE Constancia', [
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'tag_number' => $vehiculoExterno['tag_number'] ?? null,
|
|
||||||
'placa' => $vehiculoExterno['placa'] ?? null,
|
|
||||||
'vin' => $vehiculoExterno['vin'] ?? null
|
|
||||||
]);
|
|
||||||
return $vehiculoExterno;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Si no se encuentra, intentar con REPUVE Nacional
|
|
||||||
Log::info('VehicleService: Intentando consulta en REPUVE Nacional', [
|
|
||||||
'fast_id' => $fastId
|
|
||||||
]);
|
|
||||||
|
|
||||||
$resultadoNacional = $this->reporteRoboService->consultarVehiculo($fastId);
|
|
||||||
|
|
||||||
if ($resultadoNacional['success'] && !empty($resultadoNacional['datos'])) {
|
|
||||||
Log::info('VehicleService: Vehículo encontrado en REPUVE Nacional', [
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'datos' => $resultadoNacional['datos']
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Adaptar formato de REPUVE Nacional al formato esperado
|
|
||||||
$datos = $resultadoNacional['datos'];
|
|
||||||
return [
|
return [
|
||||||
'tag_number' => $fastId,
|
'success' => false,
|
||||||
'vin' => $datos['vin'] ?? null,
|
'message' => 'No se encontró información del vehículo'
|
||||||
'placa' => $datos['placa'] ?? null,
|
|
||||||
'marca' => $datos['marca'] ?? null,
|
|
||||||
'modelo' => $datos['modelo'] ?? null,
|
|
||||||
'color' => $datos['color'] ?? null,
|
|
||||||
'origen' => 'REPUVE_NACIONAL'
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info('VehicleService: Vehículo no encontrado en ningún servicio', [
|
// Consultar REPUVE
|
||||||
'fast_id' => $fastId
|
$reporteRobo = $this->reporteRobo->consultarPorVin(
|
||||||
|
$datosEstatal['vin'],
|
||||||
|
$datosEstatal['placa']
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($reporteRobo['tiene_reporte']) {
|
||||||
|
// Está robado → Guardar en Redis
|
||||||
|
$this->guardarEnRedis($epc, $datosEstatal, $reporteRobo['datos']);
|
||||||
|
|
||||||
|
Log::warning('¡VEHÍCULO ROBADO DETECTADO!', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosEstatal['vin'],
|
||||||
|
'placa' => $datosEstatal['placa']
|
||||||
]);
|
]);
|
||||||
return null;
|
|
||||||
} catch (\Exception $e) {
|
return [
|
||||||
Log::error('VehicleService: Error en consulta', [
|
'success' => true,
|
||||||
'fast_id' => $fastId,
|
'tiene_reporte_robo' => true,
|
||||||
'error' => $e->getMessage(),
|
'estado' => 'ROBADO',
|
||||||
'trace' => $e->getTraceAsString()
|
'accion' => 'GUARDADO_EN_REDIS',
|
||||||
]);
|
'vehiculo' => array_merge($datosEstatal, $reporteRobo['datos'])
|
||||||
return null;
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No está robado, no hacer nada
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => false,
|
||||||
|
'estado' => 'LIBRE',
|
||||||
|
'vehiculo' => $datosEstatal
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verificarVehiculoRobado(string $epc, ?int $arcoId, array $datosRedis): array
|
||||||
|
{
|
||||||
|
// Consultar REPUVE para verificar estado actual
|
||||||
|
$reporteRobo = $this->reporteRobo->consultarPorVin(
|
||||||
|
$datosRedis['vin'],
|
||||||
|
$datosRedis['placa']
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$reporteRobo['tiene_reporte']) {
|
||||||
|
// No tiene reporte robo
|
||||||
|
$this->registrarRecuperacion($epc, $arcoId, $datosRedis);
|
||||||
|
|
||||||
|
Log::info('¡VEHÍCULO RECUPERADO!', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa']
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => false,
|
||||||
|
'estado' => 'RECUPERADO',
|
||||||
|
'accion' => 'GUARDADO_EN_MYSQL_Y_ELIMINADO_DE_REDIS',
|
||||||
|
'vehiculo' => $datosRedis
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sigue robado → Actualizar contador en Redis
|
||||||
|
$this->actualizarDeteccionRedis($epc, $datosRedis);
|
||||||
|
|
||||||
|
Log::warning('Vehículo robado detectado nuevamente', [
|
||||||
|
'epc' => $epc,
|
||||||
|
'detecciones' => $datosRedis['detecciones'] + 1
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'tiene_reporte_robo' => true,
|
||||||
|
'estado' => 'ROBADO',
|
||||||
|
'accion' => 'ACTUALIZADO_EN_REDIS',
|
||||||
|
'vehiculo' => $datosRedis
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function guardarEnRedis(string $epc, array $datosEstatal, array $datosRobo): void
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
$datos = [
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosEstatal['vin'],
|
||||||
|
'placa' => $datosEstatal['placa'],
|
||||||
|
'fecha_robo' => $datosRobo['fecha_robo'] ?? null,
|
||||||
|
'autoridad' => $datosRobo['autoridad'] ?? null,
|
||||||
|
'acta' => $datosRobo['acta'] ?? null,
|
||||||
|
'denunciante' => $datosRobo['denunciante'] ?? null,
|
||||||
|
'fecha_acta' => $datosRobo['fecha_acta'] ?? null,
|
||||||
|
'primera_deteccion' => now()->toIso8601String(),
|
||||||
|
'ultima_deteccion' => now()->toIso8601String(),
|
||||||
|
'detecciones' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
Redis::set($key, json_encode($datos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function actualizarDeteccionRedis(string $epc, array $datosActuales): void
|
||||||
|
{
|
||||||
|
$key = "vehiculo:robado:{$epc}";
|
||||||
|
|
||||||
|
$datosActuales['ultima_deteccion'] = now()->toIso8601String();
|
||||||
|
$datosActuales['detecciones'] = ($datosActuales['detecciones'] ?? 0) + 1;
|
||||||
|
|
||||||
|
Redis::set($key, json_encode($datosActuales));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function registrarRecuperacion(string $epc, ?int $arcoId, array $datosRedis): void
|
||||||
|
{
|
||||||
|
// Guardar en MySQL
|
||||||
|
Vehicle::create([
|
||||||
|
'epc' => $epc,
|
||||||
|
'vin' => $datosRedis['vin'],
|
||||||
|
'placa' => $datosRedis['placa'],
|
||||||
|
'fecha_robo' => $datosRedis['fecha_robo'],
|
||||||
|
'autoridad_robo' => $datosRedis['autoridad'],
|
||||||
|
'acta_robo' => $datosRedis['acta'],
|
||||||
|
'denunciante' => $datosRedis['denunciante'],
|
||||||
|
'fecha_recuperacion' => now(),
|
||||||
|
'fecha_acta' => $datosRedis['fecha_acta'] ?? null,
|
||||||
|
'datos_robo_original' => $datosRedis
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Eliminar de Redis
|
||||||
|
Redis::del("vehiculo:robado:{$epc}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Listar todos los vehículos robados activos en Redis
|
|
||||||
*/
|
|
||||||
public function listarVehiculosRobados(): array
|
public function listarVehiculosRobados(): array
|
||||||
{
|
{
|
||||||
$keys = Redis::keys('vehiculo:robado:*');
|
$keys = Redis::keys('vehiculo:robado:*');
|
||||||
@ -116,288 +184,4 @@ public function listarVehiculosRobados(): array
|
|||||||
|
|
||||||
return $vehiculos;
|
return $vehiculos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Listar detecciones del día desde MySQL
|
|
||||||
*/
|
|
||||||
public function listarDeteccionesDelDia(?string $fecha = null, ?int $arcoId = null, int $segundos = 10): array
|
|
||||||
{
|
|
||||||
$fecha = $fecha ?? now()->format('Y-m-d');
|
|
||||||
|
|
||||||
// Calcular timestamp hace N segundos
|
|
||||||
$tiempoLimite = now()->subSeconds($segundos);
|
|
||||||
|
|
||||||
$query = DailyDetection::delDia($fecha)
|
|
||||||
->where('fecha_deteccion', '>=', $tiempoLimite);
|
|
||||||
|
|
||||||
// Filtrar por arco si se especifica
|
|
||||||
if ($arcoId !== null) {
|
|
||||||
$query->porArco($arcoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
$detecciones = $query
|
|
||||||
->orderBy('fecha_deteccion', 'desc')
|
|
||||||
->get()
|
|
||||||
->map(function ($deteccion) {
|
|
||||||
return [
|
|
||||||
'id' => $deteccion->id,
|
|
||||||
'fast_id' => $deteccion->fast_id,
|
|
||||||
'vin' => $deteccion->vin,
|
|
||||||
'placa' => $deteccion->placa,
|
|
||||||
'marca' => $deteccion->marca,
|
|
||||||
'modelo' => $deteccion->modelo,
|
|
||||||
'color' => $deteccion->color,
|
|
||||||
'arco_id' => $deteccion->arco_id,
|
|
||||||
'arco_nombre' => $deteccion->arco_nombre,
|
|
||||||
'antena' => $deteccion->antena,
|
|
||||||
'estado' => $deteccion->estado,
|
|
||||||
'tiene_reporte_robo' => $deteccion->tiene_reporte_robo,
|
|
||||||
'fecha_deteccion' => $deteccion->fecha_deteccion->toIso8601String(),
|
|
||||||
];
|
|
||||||
})
|
|
||||||
->toArray();
|
|
||||||
|
|
||||||
return $detecciones;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listar detecciones del día de un arco específico
|
|
||||||
*/
|
|
||||||
public function listarDeteccionesDelDiaPorArco(int $arcoId, ?string $fecha = null): array
|
|
||||||
{
|
|
||||||
return $this->listarDeteccionesDelDia($fecha, $arcoId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtener estadísticas de detecciones del día
|
|
||||||
*/
|
|
||||||
public function obtenerEstadisticasDelDia(?string $fecha = null): array
|
|
||||||
{
|
|
||||||
$detecciones = $this->listarDeteccionesDelDia($fecha);
|
|
||||||
|
|
||||||
$total = count($detecciones);
|
|
||||||
$robados = collect($detecciones)->where('tiene_reporte_robo', true)->count();
|
|
||||||
$libres = collect($detecciones)->where('tiene_reporte_robo', false)->count();
|
|
||||||
|
|
||||||
return [
|
|
||||||
'fecha' => $fecha ?? now()->format('Y-m-d'),
|
|
||||||
'total_detecciones' => $total,
|
|
||||||
'vehiculos_robados' => $robados,
|
|
||||||
'vehiculos_libres' => $libres,
|
|
||||||
'detecciones' => $detecciones
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consultar nuevo vehículo (no está en Redis de robados)
|
|
||||||
*/
|
|
||||||
private function consultarNuevoVehiculo(string $fastId): array
|
|
||||||
{
|
|
||||||
$datosVehiculo = $this->consultaRepuveCons->consultarVehiculoPorTag($fastId);
|
|
||||||
|
|
||||||
if ($datosVehiculo && isset($datosVehiculo['vin'])) {
|
|
||||||
Log::info('Vehículo encontrado en RepuveConstancia - LIBRE', [
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'vin' => $datosVehiculo['vin'],
|
|
||||||
'placa' => $datosVehiculo['placa'] ?? null,
|
|
||||||
'fuente' => 'RepuveConstancia'
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'tiene_reporte_robo' => false,
|
|
||||||
'estado' => 'LIBRE',
|
|
||||||
'vehiculo' => [
|
|
||||||
'vin' => $datosVehiculo['vin'] ?? null,
|
|
||||||
'placa' => $datosVehiculo['placa'] ?? null,
|
|
||||||
'marca' => $datosVehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $datosVehiculo['modelo'] ?? null,
|
|
||||||
'color' => $datosVehiculo['color'] ?? null,
|
|
||||||
],
|
|
||||||
'fuente' => 'RepuveConstancia'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$resultadoNacional = $this->reporteRoboService->consultarVehiculo($fastId, null);
|
|
||||||
|
|
||||||
if ($resultadoNacional['success'] && !empty($resultadoNacional['datos'])) {
|
|
||||||
$datos = $resultadoNacional['datos'];
|
|
||||||
|
|
||||||
Log::info('Vehículo encontrado en RepuveNacional - LIBRE', [
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'vin' => $datos['vin'] ?? null,
|
|
||||||
'placa' => $datos['placa'] ?? null,
|
|
||||||
'fuente' => 'RepuveNacional'
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'tiene_reporte_robo' => false,
|
|
||||||
'estado' => 'LIBRE',
|
|
||||||
'vehiculo' => [
|
|
||||||
'vin' => $datos['vin'] ?? null,
|
|
||||||
'placa' => $datos['placa'] ?? null,
|
|
||||||
'marca' => $datos['marca'] ?? null,
|
|
||||||
'modelo' => $datos['modelo'] ?? null,
|
|
||||||
'color' => $datos['color'] ?? null,
|
|
||||||
],
|
|
||||||
'fuente' => 'RepuveNacional'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'No se encontró información del vehículo en ningún servicio',
|
|
||||||
'fuente' => 'ninguno'
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verificar vehículo robado (si ya está en Redis)
|
|
||||||
*/
|
|
||||||
private function verificarVehiculoRobado(string $fastId, array $datosRedis, ?int $arcoId = null, ?string $antena = null): array
|
|
||||||
{
|
|
||||||
// Actualizar contador de detecciones
|
|
||||||
$this->actualizarDeteccionRedis($fastId, $datosRedis);
|
|
||||||
|
|
||||||
Log::warning('¡VEHÍCULO ROBADO DETECTADO!', [
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'vin' => $datosRedis['vin'] ?? null,
|
|
||||||
'placa' => $datosRedis['placa'] ?? null
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Crear alerta en la base de datos
|
|
||||||
$this->crearAlertaRobo($fastId, $datosRedis, $arcoId, $antena);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'success' => true,
|
|
||||||
'tiene_reporte_robo' => true,
|
|
||||||
'estado' => 'ROBADO',
|
|
||||||
'accion' => 'ACTUALIZADO_EN_REDIS',
|
|
||||||
'vehiculo' => $datosRedis
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Actualizar contador de detecciones en Redis
|
|
||||||
*/
|
|
||||||
private function actualizarDeteccionRedis(string $fastId, array $datosActuales)
|
|
||||||
{
|
|
||||||
$key = "vehiculo:robado:{$fastId}";
|
|
||||||
|
|
||||||
$datosActuales['ultima_deteccion'] = now()->toIso8601String();
|
|
||||||
$datosActuales['detecciones'] = ($datosActuales['detecciones'] ?? 0) + 1;
|
|
||||||
|
|
||||||
Redis::set($key, json_encode($datosActuales));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registrar detección en MySQL y Redis (detecciones del día)
|
|
||||||
*/
|
|
||||||
private function registrarDeteccion(string $fastId, array $resultado, ?int $arcoId = null, ?string $antena = null)
|
|
||||||
{
|
|
||||||
// Datos mínimos que siempre tenemos
|
|
||||||
$datosMinimos = [
|
|
||||||
'arco_id' => $arcoId,
|
|
||||||
'antena' => $antena,
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'fecha_deteccion' => now()
|
|
||||||
];
|
|
||||||
|
|
||||||
// Si encontramos el vehículo, agregamos datos completos
|
|
||||||
if ($resultado['success'] && isset($resultado['vehiculo'])) {
|
|
||||||
$vehiculo = $resultado['vehiculo'];
|
|
||||||
Detection::create(array_merge($datosMinimos, [
|
|
||||||
'vin' => $vehiculo['vin'] ?? null,
|
|
||||||
'placa' => $vehiculo['placa'] ?? null,
|
|
||||||
'marca' => $vehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $vehiculo['modelo'] ?? null,
|
|
||||||
'color' => $vehiculo['color'] ?? null,
|
|
||||||
]));
|
|
||||||
|
|
||||||
// También registrar en Redis (detecciones del día)
|
|
||||||
$this->registrarDeteccionDelDia($fastId, $vehiculo, $arcoId, $resultado, $antena);
|
|
||||||
} else {
|
|
||||||
// No encontrado: guardar solo datos mínimos
|
|
||||||
Detection::create(array_merge($datosMinimos, [
|
|
||||||
'vin' => null,
|
|
||||||
'placa' => null,
|
|
||||||
'marca' => null,
|
|
||||||
'modelo' => null,
|
|
||||||
'color' => null,
|
|
||||||
]));
|
|
||||||
|
|
||||||
// Registrar en Redis con datos incompletos
|
|
||||||
$this->registrarDeteccionDelDia($fastId, [], $arcoId, [
|
|
||||||
'estado' => 'DESCONOCIDO',
|
|
||||||
'tiene_reporte_robo' => false
|
|
||||||
], $antena);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registrar detección en MySQL para el día actual
|
|
||||||
*/
|
|
||||||
private function registrarDeteccionDelDia(string $fastId, array $vehiculo, ?int $arcoId, array $resultado, ?string $antena = null)
|
|
||||||
{
|
|
||||||
// Obtener nombre del arco
|
|
||||||
$arcoNombre = $arcoId ? Arco::find($arcoId)?->nombre : null;
|
|
||||||
|
|
||||||
// Crear la nueva detección
|
|
||||||
$nuevaDeteccion = DailyDetection::create([
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'vin' => $vehiculo['vin'] ?? null,
|
|
||||||
'placa' => $vehiculo['placa'] ?? null,
|
|
||||||
'marca' => $vehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $vehiculo['modelo'] ?? null,
|
|
||||||
'color' => $vehiculo['color'] ?? null,
|
|
||||||
'arco_id' => $arcoId,
|
|
||||||
'arco_nombre' => $arcoNombre,
|
|
||||||
'antena' => $antena,
|
|
||||||
'estado' => $resultado['estado'] ?? 'LIBRE',
|
|
||||||
'tiene_reporte_robo' => $resultado['tiene_reporte_robo'] ?? false,
|
|
||||||
'fecha_deteccion' => now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Log::info('Detección del día registrada en MySQL', [
|
|
||||||
'id' => $nuevaDeteccion->id,
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'arco_id' => $arcoId,
|
|
||||||
'estado' => $nuevaDeteccion->estado
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Crear alerta de vehículo robado y disparar evento
|
|
||||||
*/
|
|
||||||
private function crearAlertaRobo(string $fastId, array $datosVehiculo, ?int $arcoId, ?string $antena): void
|
|
||||||
{
|
|
||||||
// Obtener nombre del arco
|
|
||||||
$arcoNombre = $arcoId ? Arco::find($arcoId)?->nombre : 'Desconocido';
|
|
||||||
|
|
||||||
// Crear alerta en la base de datos
|
|
||||||
$alerta = AlertaRobo::create([
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'vin' => $datosVehiculo['vin'] ?? null,
|
|
||||||
'placa' => $datosVehiculo['placa'] ?? null,
|
|
||||||
'marca' => $datosVehiculo['marca'] ?? null,
|
|
||||||
'modelo' => $datosVehiculo['modelo'] ?? null,
|
|
||||||
'color' => $datosVehiculo['color'] ?? null,
|
|
||||||
'arco_id' => $arcoId,
|
|
||||||
'arco_nombre' => $arcoNombre,
|
|
||||||
'antena' => $antena,
|
|
||||||
'fecha_deteccion' => now(),
|
|
||||||
'visto' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Disparar evento para Laravel Reverb (WebSocket)
|
|
||||||
event(new VehiculoRobadoDetectado($alerta));
|
|
||||||
|
|
||||||
Log::info('Alerta de vehículo robado creada y evento disparado', [
|
|
||||||
'alerta_id' => $alerta->id,
|
|
||||||
'fast_id' => $fastId,
|
|
||||||
'placa' => $alerta->placa,
|
|
||||||
'arco' => $arcoNombre
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,17 +36,7 @@
|
|||||||
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
|
'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,
|
||||||
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
|
'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
|
||||||
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
|
'role_or_permission' => \Spatie\Permission\Middleware\RoleOrPermissionMiddleware::class,
|
||||||
'arco.token' => \App\Http\Middleware\ArcoTokenMiddleware::class,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Configurar proxies confiables para detectar IP real en Docker
|
|
||||||
$middleware->trustProxies(
|
|
||||||
at: '*',
|
|
||||||
headers: Request::HEADER_X_FORWARDED_FOR |
|
|
||||||
Request::HEADER_X_FORWARDED_HOST |
|
|
||||||
Request::HEADER_X_FORWARDED_PORT |
|
|
||||||
Request::HEADER_X_FORWARDED_PROTO
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions) {
|
->withExceptions(function (Exceptions $exceptions) {
|
||||||
$exceptions->render(function (ServiceUnavailableHttpException $e, Request $request) {
|
$exceptions->render(function (ServiceUnavailableHttpException $e, Request $request) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/passport": "^12.4",
|
"laravel/passport": "^12.4",
|
||||||
"laravel/pulse": "^1.4",
|
"laravel/pulse": "^1.4",
|
||||||
"laravel/reverb": "^1.7",
|
"laravel/reverb": "^1.4",
|
||||||
"laravel/tinker": "^2.10",
|
"laravel/tinker": "^2.10",
|
||||||
"notsoweb/laravel-core": "dev-main",
|
"notsoweb/laravel-core": "dev-main",
|
||||||
"spatie/laravel-permission": "^6.16",
|
"spatie/laravel-permission": "^6.16",
|
||||||
@ -70,10 +70,6 @@
|
|||||||
"composer run post-create-project-cmd",
|
"composer run post-create-project-cmd",
|
||||||
"@php artisan storage:link"
|
"@php artisan storage:link"
|
||||||
],
|
],
|
||||||
"db:update": [
|
|
||||||
"@php artisan migrate",
|
|
||||||
"@php artisan migrate --path=database/migrations/data"
|
|
||||||
],
|
|
||||||
"db:dev": [
|
"db:dev": [
|
||||||
"@php artisan migrate:fresh --seeder=DevSeeder",
|
"@php artisan migrate:fresh --seeder=DevSeeder",
|
||||||
"@php artisan passport:client --personal --name=Holos"
|
"@php artisan passport:client --personal --name=Holos"
|
||||||
|
|||||||
2195
composer.lock
generated
2195
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -104,19 +104,6 @@
|
|||||||
|
|
||||||
'key' => env('APP_KEY'),
|
'key' => env('APP_KEY'),
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Arco Token Encryption Key
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This key is used to encrypt the API tokens for RFID arcos. Unlike APP_KEY,
|
|
||||||
| this key should NEVER change once set, as changing it will invalidate all
|
|
||||||
| existing arco tokens. Store this key securely and back it up.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'arco_token_encryption_key' => env('ARCO_TOKEN_ENCRYPTION_KEY'),
|
|
||||||
|
|
||||||
'previous_keys' => [
|
'previous_keys' => [
|
||||||
...array_filter(
|
...array_filter(
|
||||||
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
explode(',', env('APP_PREVIOUS_KEYS', ''))
|
||||||
|
|||||||
@ -47,13 +47,4 @@
|
|||||||
'url' => env('REPUVE_EST_URL'),
|
'url' => env('REPUVE_EST_URL'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'vehiculos_externos' => [
|
|
||||||
'base_url' => env('REPUVE_CONSTANCIA_BASE_URL'),
|
|
||||||
'login_endpoint' => env('REPUVE_CONSTANCIA_LOGIN_ENDPOINT'),
|
|
||||||
'vehiculos_endpoint' => env('REPUVE_CONSTANCIA_CONSULTA_ENDPOINT'),
|
|
||||||
'email' => env('REPUVE_CONSTANCIA_EMAIL'),
|
|
||||||
'password' => env('REPUVE_CONSTANCIA_PASSWORD'),
|
|
||||||
'token_ttl' => env('REPUVE_CONSTANCIA_TOKEN_TTL'),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
<?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('vehicle_fakes', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('placa')->unique();
|
|
||||||
$table->string('vin')->unique();
|
|
||||||
$table->string('marca');
|
|
||||||
$table->string('color');
|
|
||||||
$table->string('modelo');
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('vehicle_fakes');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<?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('vehicle_fakes', function (Blueprint $table) {
|
|
||||||
$table->string('folio_tag')->unique()->after('id');
|
|
||||||
$table->string('tag_number')->unique()->after('folio_tag');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('vehicle_fakes', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('folio_tag');
|
|
||||||
$table->dropColumn('tag_number');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
<?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('detections', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
|
|
||||||
// Datos del vehículo detectado
|
|
||||||
$table->string('epc');
|
|
||||||
$table->string('vin')->nullable();
|
|
||||||
$table->string('placa')->nullable();
|
|
||||||
$table->string('marca')->nullable();
|
|
||||||
$table->string('modelo')->nullable();
|
|
||||||
$table->string('color')->nullable();
|
|
||||||
$table->timestamp('fecha_deteccion')->useCurrent();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('detections');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<?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')->default(true);
|
|
||||||
$table->timestamps();
|
|
||||||
|
|
||||||
$table->index('ip_address');
|
|
||||||
$table->index('activo');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('arcos');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<?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,30 +0,0 @@
|
|||||||
<?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('arcos', function (Blueprint $table) {
|
|
||||||
$table->string('api_token', 64)->unique()->nullable()->after('activo');
|
|
||||||
$table->index('api_token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('arcos', function (Blueprint $table) {
|
|
||||||
$table->dropIndex(['api_token']);
|
|
||||||
$table->dropColumn('api_token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$indexes = DB::select("SHOW INDEX FROM arcos WHERE Column_name = 'api_token'");
|
|
||||||
|
|
||||||
foreach ($indexes as $index) {
|
|
||||||
$indexName = $index->Key_name;
|
|
||||||
try {
|
|
||||||
DB::statement("ALTER TABLE arcos DROP INDEX `{$indexName}`");
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DB::statement('ALTER TABLE arcos MODIFY COLUMN api_token TEXT NULL');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
DB::statement('ALTER TABLE arcos MODIFY COLUMN api_token VARCHAR(64) NULL');
|
|
||||||
|
|
||||||
Schema::table('arcos', function (Blueprint $table) {
|
|
||||||
$table->unique('api_token');
|
|
||||||
$table->index('api_token');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?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('arcos', function (Blueprint $table) {
|
|
||||||
// Agregar 4 campos para antenas (todas opcionales/nullable)
|
|
||||||
$table->string('antena_1')->nullable()->after('activo');
|
|
||||||
$table->string('antena_2')->nullable()->after('antena_1');
|
|
||||||
$table->string('antena_3')->nullable()->after('antena_2');
|
|
||||||
$table->string('antena_4')->nullable()->after('antena_3');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('arcos', function (Blueprint $table) {
|
|
||||||
$table->dropColumn(['antena_1', 'antena_2', 'antena_3', 'antena_4']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
<?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->string('antena')->nullable()->after('arco_id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('detections', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('antena');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
<?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
|
|
||||||
{
|
|
||||||
// Renombrar columna en tabla detections
|
|
||||||
Schema::table('detections', function (Blueprint $table) {
|
|
||||||
$table->renameColumn('epc', 'fast_id');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Renombrar columna en tabla vehicles
|
|
||||||
Schema::table('vehicles', function (Blueprint $table) {
|
|
||||||
$table->renameColumn('epc', 'fast_id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
// Revertir cambio en tabla detections
|
|
||||||
Schema::table('detections', function (Blueprint $table) {
|
|
||||||
$table->renameColumn('fast_id', 'epc');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Revertir cambio en tabla vehicles
|
|
||||||
Schema::table('vehicles', function (Blueprint $table) {
|
|
||||||
$table->renameColumn('fast_id', 'epc');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('alertas_robos', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->string('fast_id');
|
|
||||||
$table->string('vin')->nullable();
|
|
||||||
$table->string('placa')->nullable();
|
|
||||||
$table->string('marca')->nullable();
|
|
||||||
$table->string('modelo')->nullable();
|
|
||||||
$table->string('color')->nullable();
|
|
||||||
$table->unsignedBigInteger('arco_id');
|
|
||||||
$table->string('arco_nombre');
|
|
||||||
$table->string('antena')->nullable();
|
|
||||||
$table->timestamp('fecha_deteccion');
|
|
||||||
$table->boolean('visto')->default(false);
|
|
||||||
$table->unsignedBigInteger('usuario_id')->nullable();
|
|
||||||
$table->timestamp('fecha_confirmacion')->nullable();
|
|
||||||
$table->timestamps();
|
|
||||||
|
|
||||||
// Índices para optimizar consultas
|
|
||||||
$table->index('visto');
|
|
||||||
$table->index('arco_id');
|
|
||||||
$table->index('fecha_deteccion');
|
|
||||||
|
|
||||||
// Foreign keys
|
|
||||||
$table->foreign('arco_id')->references('id')->on('arcos')->onDelete('cascade');
|
|
||||||
$table->foreign('usuario_id')->references('id')->on('users')->onDelete('set null');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('alertas_robos');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
<?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('daily_detections', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
|
|
||||||
// Información del arco y antena
|
|
||||||
$table->foreignId('arco_id')->nullable()->constrained('arcos')->onDelete('cascade');
|
|
||||||
$table->string('arco_nombre')->nullable();
|
|
||||||
$table->string('antena')->nullable();
|
|
||||||
|
|
||||||
// Datos del vehículo detectado
|
|
||||||
$table->string('fast_id');
|
|
||||||
$table->string('vin')->nullable();
|
|
||||||
$table->string('placa')->nullable();
|
|
||||||
$table->string('marca')->nullable();
|
|
||||||
$table->string('modelo')->nullable();
|
|
||||||
$table->string('color')->nullable();
|
|
||||||
|
|
||||||
// Estado de la detección
|
|
||||||
$table->enum('estado', ['LIBRE', 'ROBADO', 'DESCONOCIDO'])->default('DESCONOCIDO');
|
|
||||||
$table->boolean('tiene_reporte_robo')->default(false);
|
|
||||||
|
|
||||||
// Fecha de la detección
|
|
||||||
$table->timestamp('fecha_deteccion')->useCurrent();
|
|
||||||
$table->date('fecha_dia')->storedAs('DATE(fecha_deteccion)');
|
|
||||||
|
|
||||||
// Índices para optimizar consultas
|
|
||||||
$table->index('arco_id');
|
|
||||||
$table->index('fecha_dia');
|
|
||||||
$table->index(['arco_id', 'fecha_dia']);
|
|
||||||
$table->index('fast_id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('daily_detections');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
|
||||||
|
|
||||||
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
|
||||||
DB::table('role_has_permissions')->truncate();
|
|
||||||
DB::table('model_has_permissions')->truncate();
|
|
||||||
\Spatie\Permission\Models\Permission::truncate();
|
|
||||||
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
|
||||||
|
|
||||||
Artisan::call('db:seed', ['--class' => 'RoleSeeder', '--force' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<?php namespace Database\Seeders;
|
|
||||||
/**
|
|
||||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All Rights Reserved
|
|
||||||
*/
|
|
||||||
|
|
||||||
use App\Models\Arco;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
use Notsoweb\LaravelCore\Supports\UserSecureSupport;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Descripción
|
|
||||||
*
|
|
||||||
* @author Moisés Cortés C. <moises.cortes@notsoweb.com>
|
|
||||||
*
|
|
||||||
* @version 1.0.0
|
|
||||||
*/
|
|
||||||
class ArcoSeeder extends Seeder
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Ejecutar sembrado de base de datos
|
|
||||||
*/
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
$arco = Arco::create([
|
|
||||||
'nombre' => 'Arco',
|
|
||||||
'ip_address' => '10.30.204.125',
|
|
||||||
'ubicacion' => 'Ubicación del arco',
|
|
||||||
'antena_1' => 'FRONTERA - VILLAHERMOSA',
|
|
||||||
'antena_2' => 'VILLAHERMOSA - FRONTERA',
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Obtener el token desencriptado
|
|
||||||
$plainToken = $arco->obtenerTokenDesencriptado();
|
|
||||||
|
|
||||||
$this->command->getOutput()->writeln("\n Token del arco => {$plainToken}\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -22,6 +22,5 @@ public function run(): void
|
|||||||
$this->call(RoleSeeder::class);
|
$this->call(RoleSeeder::class);
|
||||||
$this->call(UserSeeder::class);
|
$this->call(UserSeeder::class);
|
||||||
$this->call(SettingSeeder::class);
|
$this->call(SettingSeeder::class);
|
||||||
$this->call(ArcoSeeder::class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,5 @@ public function run(): void
|
|||||||
$this->call(RoleSeeder::class);
|
$this->call(RoleSeeder::class);
|
||||||
$this->call(UserSeeder::class);
|
$this->call(UserSeeder::class);
|
||||||
$this->call(SettingSeeder::class);
|
$this->call(SettingSeeder::class);
|
||||||
$this->call(ArcoSeeder::class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class RoleSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$users = PermissionType::updateOrCreate([
|
$users = PermissionType::create([
|
||||||
'name' => 'Usuarios'
|
'name' => 'Usuarios'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -37,8 +37,9 @@ public function run(): void
|
|||||||
] = $this->onCRUD('users', $users, 'api');
|
] = $this->onCRUD('users', $users, 'api');
|
||||||
|
|
||||||
$userSettings = $this->onPermission('users.settings', 'Configuración de usuarios', $users, 'api');
|
$userSettings = $this->onPermission('users.settings', 'Configuración de usuarios', $users, 'api');
|
||||||
|
$userOnline = $this->onPermission('users.online', 'Usuarios en linea', $users, 'api');
|
||||||
|
|
||||||
$roles = PermissionType::updateOrCreate([
|
$roles = PermissionType::create([
|
||||||
'name' => 'Roles'
|
'name' => 'Roles'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -49,86 +50,47 @@ public function run(): void
|
|||||||
$roleDestroy
|
$roleDestroy
|
||||||
] = $this->onCRUD('roles', $roles, 'api');
|
] = $this->onCRUD('roles', $roles, 'api');
|
||||||
|
|
||||||
// Arcos
|
$pulse = PermissionType::create([
|
||||||
$arcos = PermissionType::updateOrCreate([
|
'name' => 'Sistema'
|
||||||
'name' => 'Gestión de Arcos'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
[
|
$systemPulse = $this->onPermission('pulse', 'Monitoreo de Pulse', $pulse, 'api');
|
||||||
$arcoIndex,
|
|
||||||
$arcoCreate,
|
|
||||||
$arcoEdit,
|
|
||||||
$arcoDestroy
|
|
||||||
] = $this->onCRUD('arcos', $arcos, 'api');
|
|
||||||
|
|
||||||
$arcosToggleEstado = $this->onPermission('arcos.toggle-estado', 'Activar/Desactivar arcos', $arcos, 'api');
|
$pulse = PermissionType::create([
|
||||||
$arcosDetecciones = $this->onPermission('arcos.detecciones.dia', 'Ver detecciones del día de un arco', $arcos, 'api');
|
'name' => 'Historial de actividades'
|
||||||
|
|
||||||
//Consulta de vehículo
|
|
||||||
$vehicles = PermissionType::updateOrCreate([
|
|
||||||
'name' => 'Gestión de Vehículos'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$vehiclesConsultaAlta = $this->onPermission('vehicles.consultar', 'Buscar un vehiculo y darle alta como Robado', $vehicles, 'api');
|
$activityIndex = $this->onIndex(
|
||||||
$vehiclesDetectar = $this->onPermission('vehicles.detectar', 'Historial de búsqueda de vehículo por placa o VIN', $vehicles, 'api');
|
code: 'activities',
|
||||||
|
type: $pulse,
|
||||||
|
guardName: 'api'
|
||||||
|
);
|
||||||
|
|
||||||
// Desarrollador
|
// Desarrollador
|
||||||
Role::updateOrCreate([
|
Role::create([
|
||||||
'name' => 'developer',
|
'name' => 'developer',
|
||||||
'description' => 'Desarrollador',
|
'description' => 'Desarrollador',
|
||||||
'guard_name' => 'api'
|
'guard_name' => 'api'
|
||||||
])->syncPermissions(Permission::all());
|
])->givePermissionTo(Permission::all());
|
||||||
|
|
||||||
// Administrador
|
// Administrador
|
||||||
Role::updateOrCreate([
|
Role::create([
|
||||||
'name' => 'admin',
|
'name' => 'admin',
|
||||||
'description' => 'Administrador',
|
'description' => 'Administrador',
|
||||||
'guard_name' => 'api'
|
'guard_name' => 'api'
|
||||||
])->syncPermissions(
|
])->givePermissionTo(
|
||||||
$userIndex,
|
$userIndex,
|
||||||
$userCreate,
|
$userCreate,
|
||||||
$userEdit,
|
$userEdit,
|
||||||
$userDestroy,
|
$userDestroy,
|
||||||
$userSettings,
|
$userSettings,
|
||||||
|
$userOnline,
|
||||||
$roleIndex,
|
$roleIndex,
|
||||||
$roleCreate,
|
$roleCreate,
|
||||||
$roleEdit,
|
$roleEdit,
|
||||||
$roleDestroy,
|
$roleDestroy,
|
||||||
$arcoIndex, //arcos
|
$systemPulse,
|
||||||
$arcoCreate,
|
$activityIndex
|
||||||
$arcoEdit,
|
|
||||||
$arcoDestroy,
|
|
||||||
$arcosToggleEstado,
|
|
||||||
$arcosDetecciones,
|
|
||||||
$vehiclesConsultaAlta, //vehicles
|
|
||||||
$vehiclesDetectar,
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
Role::updateOrCreate([
|
|
||||||
'name' => 'supervisor',
|
|
||||||
'description' => 'Supervisor',
|
|
||||||
'guard_name' => 'api'
|
|
||||||
])->syncPermissions(
|
|
||||||
$vehiclesConsultaAlta, //vehicles
|
|
||||||
$vehiclesDetectar,
|
|
||||||
);
|
|
||||||
|
|
||||||
Role::updateOrCreate([
|
|
||||||
'name' => 'operador',
|
|
||||||
'description' => 'Operador',
|
|
||||||
'guard_name' => 'api'
|
|
||||||
])->syncPermissions(
|
|
||||||
$vehiclesConsultaAlta, //vehicles
|
|
||||||
$vehiclesDetectar,
|
|
||||||
);
|
|
||||||
|
|
||||||
Role::updateOrCreate([
|
|
||||||
'name' => 'investigador',
|
|
||||||
'description' => 'Investigador',
|
|
||||||
'guard_name' => 'api'
|
|
||||||
])->syncPermissions(
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,25 +21,34 @@ class UserSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$developer = UserSecureSupport::create('developer@golsystems.com');
|
$developer = UserSecureSupport::create('developer@notsoweb.com');
|
||||||
|
|
||||||
User::create([
|
User::create([
|
||||||
'name' => 'admin',
|
'name' => 'Developer',
|
||||||
'paternal' => 'golsystems',
|
'paternal' => 'Notsoweb',
|
||||||
'maternal' => 'desarrollo',
|
'maternal' => 'Software',
|
||||||
'email' => $developer->email,
|
'email' => $developer->email,
|
||||||
'password' => $developer->hash,
|
'password' => $developer->hash,
|
||||||
])->assignRole(__('developer'));
|
])->assignRole(__('developer'));
|
||||||
|
|
||||||
$admin = UserSecureSupport::create('admin@golsystems.com');
|
$admin = UserSecureSupport::create('admin@notsoweb.com');
|
||||||
|
|
||||||
User::create([
|
User::create([
|
||||||
'name' => 'Admin',
|
'name' => 'Admin',
|
||||||
'paternal' => 'Golsystems',
|
'paternal' => 'Notsoweb',
|
||||||
'maternal' => 'Desarrollo',
|
'maternal' => 'Software',
|
||||||
'email' => $admin->email,
|
'email' => $admin->email,
|
||||||
'password' => $admin->hash,
|
'password' => $admin->hash,
|
||||||
])->assignRole(__('admin'));
|
])->assignRole(__('admin'));
|
||||||
|
|
||||||
|
$demo = UserSecureSupport::create('demo@notsoweb.com');
|
||||||
|
|
||||||
|
User::create([
|
||||||
|
'name' => 'Demo',
|
||||||
|
'paternal' => 'Notsoweb',
|
||||||
|
'maternal' => 'Software',
|
||||||
|
'email' => $demo->email,
|
||||||
|
'password' => $demo->hash,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,153 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
|
|
||||||
class VehicleTestSeeder extends Seeder
|
|
||||||
{
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
$this->command->info('Creando datos de prueba de vehículos...');
|
|
||||||
|
|
||||||
// Limpiar Redis antes de empezar
|
|
||||||
$keys = Redis::keys('vehiculo:*');
|
|
||||||
if (count($keys) > 0) {
|
|
||||||
foreach ($keys as $key) {
|
|
||||||
Redis::del($key);
|
|
||||||
}
|
|
||||||
$this->command->warn('Redis limpiado: ' . count($keys) . ' vehículos eliminados');
|
|
||||||
}
|
|
||||||
|
|
||||||
// VEHÍCULOS CON REPORTE DE ROBO
|
|
||||||
|
|
||||||
$vehiculosRobados = [
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000001111AAAA',
|
|
||||||
'vin' => '3VWDX7AJ9CM111111',
|
|
||||||
'placa' => 'ABC1111',
|
|
||||||
'fecha_robo' => '2024-01-15',
|
|
||||||
'autoridad' => 'PGJ - BAJA CALIFORNIA',
|
|
||||||
'acta' => 'BC/ROB/2024/001',
|
|
||||||
'denunciante' => 'JUAN GARCIA LOPEZ',
|
|
||||||
'fecha_acta' => '2024-01-15',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000002222BBBB',
|
|
||||||
'vin' => '1HGBH41JXMN222222',
|
|
||||||
'placa' => 'XYZ2222',
|
|
||||||
'fecha_robo' => '2024-03-20',
|
|
||||||
'autoridad' => 'FISCALÍA GENERAL DEL ESTADO',
|
|
||||||
'acta' => 'FGE/ROB/2024/002',
|
|
||||||
'denunciante' => 'MARIA RODRIGUEZ SANCHEZ',
|
|
||||||
'fecha_acta' => '2024-03-20',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000003333CCCC',
|
|
||||||
'vin' => '2HGFG12858H333333',
|
|
||||||
'placa' => 'DEF3333',
|
|
||||||
'fecha_robo' => '2024-06-10',
|
|
||||||
'autoridad' => 'PGJ - SONORA',
|
|
||||||
'acta' => 'SON/ROB/2024/003',
|
|
||||||
'denunciante' => 'CARLOS MARTINEZ GONZALEZ',
|
|
||||||
'fecha_acta' => '2024-06-10',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000004444DDDD',
|
|
||||||
'vin' => '5FNRL5H64GB444444',
|
|
||||||
'placa' => 'GHI4444',
|
|
||||||
'fecha_robo' => '2024-08-05',
|
|
||||||
'autoridad' => 'FISCALÍA ESPECIALIZADA EN ROBO DE VEHÍCULOS',
|
|
||||||
'acta' => 'FERV/2024/004',
|
|
||||||
'denunciante' => 'ANA LOPEZ HERNANDEZ',
|
|
||||||
'fecha_acta' => '2024-08-05',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000005555EEEE',
|
|
||||||
'vin' => '1G1ZD5ST0LF555555',
|
|
||||||
'placa' => 'JKL5555',
|
|
||||||
'fecha_robo' => '2024-11-25',
|
|
||||||
'autoridad' => 'PGJ - CHIHUAHUA',
|
|
||||||
'acta' => 'CHI/ROB/2024/005',
|
|
||||||
'denunciante' => 'PEDRO RAMIREZ FLORES',
|
|
||||||
'fecha_acta' => '2024-11-25',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000006666HHHH',
|
|
||||||
'vin' => 'KNDJT2A27G7666666',
|
|
||||||
'placa' => 'MNO6666',
|
|
||||||
'fecha_robo' => '2024-12-01',
|
|
||||||
'autoridad' => 'PGJ - SINALOA',
|
|
||||||
'acta' => 'SIN/ROB/2024/006',
|
|
||||||
'denunciante' => 'LUIS FERNANDEZ TORRES',
|
|
||||||
'fecha_acta' => '2024-12-01',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000007777IIII',
|
|
||||||
'vin' => '3FADP4BJ6FM777777',
|
|
||||||
'placa' => 'PQR7777',
|
|
||||||
'fecha_robo' => '2024-12-05',
|
|
||||||
'autoridad' => 'FISCALÍA GENERAL - JALISCO',
|
|
||||||
'acta' => 'JAL/ROB/2024/007',
|
|
||||||
'denunciante' => 'ROSA MARTINEZ DIAZ',
|
|
||||||
'fecha_acta' => '2024-12-05',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000008888JJJJ',
|
|
||||||
'vin' => '1N4AL3AP5JC888888',
|
|
||||||
'placa' => 'STU8888',
|
|
||||||
'fecha_robo' => '2024-12-10',
|
|
||||||
'autoridad' => 'PGJ - NUEVO LEON',
|
|
||||||
'acta' => 'NL/ROB/2024/008',
|
|
||||||
'denunciante' => 'MIGUEL ANGEL RUIZ',
|
|
||||||
'fecha_acta' => '2024-12-10',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000009999KKKK',
|
|
||||||
'vin' => 'JN1CV6AP3BM999999',
|
|
||||||
'placa' => 'VWX9999',
|
|
||||||
'fecha_robo' => '2024-12-12',
|
|
||||||
'autoridad' => 'FISCALÍA - VERACRUZ',
|
|
||||||
'acta' => 'VER/ROB/2024/009',
|
|
||||||
'denunciante' => 'PATRICIA GOMEZ CASTRO',
|
|
||||||
'fecha_acta' => '2024-12-12',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'epc' => 'E2801170000010000LLLL',
|
|
||||||
'vin' => 'WBAFR9C58BC000000',
|
|
||||||
'placa' => 'YZA0000',
|
|
||||||
'fecha_robo' => '2024-12-15',
|
|
||||||
'autoridad' => 'PGJ - TAMAULIPAS',
|
|
||||||
'acta' => 'TAM/ROB/2024/010',
|
|
||||||
'denunciante' => 'FRANCISCO HERNANDEZ SILVA',
|
|
||||||
'fecha_acta' => '2024-12-15',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Guardar vehículos robados en Redis
|
|
||||||
foreach ($vehiculosRobados as $vehiculo) {
|
|
||||||
$key = "vehiculo:robado:{$vehiculo['epc']}";
|
|
||||||
|
|
||||||
$datos = [
|
|
||||||
'epc' => $vehiculo['epc'],
|
|
||||||
'vin' => $vehiculo['vin'],
|
|
||||||
'placa' => $vehiculo['placa'],
|
|
||||||
'fecha_robo' => $vehiculo['fecha_robo'],
|
|
||||||
'autoridad' => $vehiculo['autoridad'],
|
|
||||||
'acta' => $vehiculo['acta'],
|
|
||||||
'denunciante' => $vehiculo['denunciante'],
|
|
||||||
'fecha_acta' => $vehiculo['fecha_acta'],
|
|
||||||
'primera_deteccion' => now()->subDays(rand(1, 30))->toIso8601String(),
|
|
||||||
'ultima_deteccion' => now()->subHours(rand(1, 24))->toIso8601String(),
|
|
||||||
'detecciones' => rand(1, 50),
|
|
||||||
'origen' => 'SEEDER_PRUEBA'
|
|
||||||
];
|
|
||||||
|
|
||||||
Redis::set($key, json_encode($datos));
|
|
||||||
|
|
||||||
$this->command->info("Robado: {$vehiculo['placa']} - {$vehiculo['vin']}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,9 +3,6 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: dockerfile
|
dockerfile: dockerfile
|
||||||
args:
|
|
||||||
USER_ID: 1000
|
|
||||||
GROUP_ID: 1000
|
|
||||||
working_dir: /var/www/arcos-backend
|
working_dir: /var/www/arcos-backend
|
||||||
environment:
|
environment:
|
||||||
- DB_HOST=mysql
|
- DB_HOST=mysql
|
||||||
@ -26,7 +23,7 @@ services:
|
|||||||
nginx:
|
nginx:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
ports:
|
ports:
|
||||||
- "${NGINX_PORT}:7003"
|
- "${NGINX_PORT}:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./public:/var/www/arcos-backend/public
|
- ./public:/var/www/arcos-backend/public
|
||||||
- ./storage:/var/www/arcos-backend/storage
|
- ./storage:/var/www/arcos-backend/storage
|
||||||
@ -45,7 +42,7 @@ services:
|
|||||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||||
MYSQL_USER: ${DB_USERNAME}
|
MYSQL_USER: ${DB_USERNAME}
|
||||||
ports:
|
ports:
|
||||||
- ${DB_PORT}:3306
|
- ${DB_PORT}:${DB_PORT}
|
||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
networks:
|
networks:
|
||||||
@ -56,6 +53,19 @@ services:
|
|||||||
timeout: 15s
|
timeout: 15s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
phpmyadmin:
|
||||||
|
image: phpmyadmin/phpmyadmin
|
||||||
|
environment:
|
||||||
|
PMA_HOST: mysql
|
||||||
|
PMA_PORT: 3306
|
||||||
|
ports:
|
||||||
|
- '${PMA_PORT}:80'
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
networks:
|
||||||
|
- arcos-network
|
||||||
|
mem_limit: 400m
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
ports:
|
ports:
|
||||||
@ -70,6 +80,18 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
|
redis-commander:
|
||||||
|
image: rediscommander/redis-commander:latest
|
||||||
|
environment:
|
||||||
|
- ${REDIS_HOST}:redis:6379
|
||||||
|
ports:
|
||||||
|
- "${REDIS_COMMANDER_PORT}:8081"
|
||||||
|
networks:
|
||||||
|
- arcos-network
|
||||||
|
mem_limit: 256m
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
nginx_data:
|
nginx_data:
|
||||||
driver: local
|
driver: local
|
||||||
|
|||||||
17
dockerfile
17
dockerfile
@ -15,14 +15,8 @@ RUN apk add --no-cache \
|
|||||||
openssl \
|
openssl \
|
||||||
bash \
|
bash \
|
||||||
mysql-client \
|
mysql-client \
|
||||||
su-exec \
|
|
||||||
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
|
||||||
|
|
||||||
# Instalar extensión de Redis
|
|
||||||
RUN apk add --no-cache pcre-dev $PHPIZE_DEPS \
|
|
||||||
&& pecl install redis \
|
|
||||||
&& docker-php-ext-enable redis
|
|
||||||
|
|
||||||
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
COPY composer.json composer.lock ./
|
COPY composer.json composer.lock ./
|
||||||
@ -34,13 +28,12 @@ COPY . .
|
|||||||
COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh
|
COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh
|
||||||
RUN chmod +x /usr/local/bin/entrypoint-dev.sh
|
RUN chmod +x /usr/local/bin/entrypoint-dev.sh
|
||||||
|
|
||||||
ARG USER_ID=1000
|
RUN mkdir -p storage/app/keys storage/logs bootstrap/cache
|
||||||
ARG GROUP_ID=1000
|
|
||||||
RUN apk add --no-cache shadow && \
|
|
||||||
usermod -u ${USER_ID} www-data && \
|
|
||||||
groupmod -g ${GROUP_ID} www-data
|
|
||||||
|
|
||||||
EXPOSE 7003
|
RUN chown -R www-data:www-data /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache
|
||||||
|
RUN chmod -R 775 /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/entrypoint-dev.sh"]
|
ENTRYPOINT ["/usr/local/bin/entrypoint-dev.sh"]
|
||||||
CMD ["php-fpm"]
|
CMD ["php-fpm"]
|
||||||
|
|||||||
@ -1,485 +0,0 @@
|
|||||||
# Documentación Técnica — Arcos Backend
|
|
||||||
|
|
||||||
## Índice
|
|
||||||
|
|
||||||
1. [Visión General](#visión-general)
|
|
||||||
2. [Flujo de Detección RFID](#flujo-de-detección-rfid)
|
|
||||||
3. [Gestión de Vehículos Robados](#gestión-de-vehículos-robados)
|
|
||||||
4. [Integraciones REPUVE](#integraciones-repuve)
|
|
||||||
5. [Gestión de Arcos (Lectores RFID)](#gestión-de-arcos-lectores-rfid)
|
|
||||||
6. [Sistema de Alertas](#sistema-de-alertas)
|
|
||||||
7. [Registro de Detecciones](#registro-de-detecciones)
|
|
||||||
8. [Transmisión en Tiempo Real](#transmisión-en-tiempo-real)
|
|
||||||
9. [Esquema de Base de Datos](#esquema-de-base-de-datos)
|
|
||||||
10. [Comandos y Tareas Programadas](#comandos-y-tareas-programadas)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Visión General
|
|
||||||
|
|
||||||
**Arcos** es una API REST construida en Laravel 12 para la detección de vehículos robados mediante lectores RFID ("arcos"). El sistema conecta lectores de hardware en campo con registros federales y estatales de robo vehicular (REPUVE), registra cada detección en base de datos y emite alertas en tiempo real vía WebSocket cuando un vehículo reportado como robado es detectado.
|
|
||||||
|
|
||||||
### Stack tecnológico relevante
|
|
||||||
|
|
||||||
| Componente | Tecnología |
|
|
||||||
|---|---|
|
|
||||||
| Framework | Laravel 12 / PHP 8.3 |
|
|
||||||
| Base de datos principal | MySQL 8.0 |
|
|
||||||
| Almacenamiento en caliente | Redis |
|
|
||||||
| Cola de trabajos | Laravel Queue (driver: database) |
|
|
||||||
| WebSockets | Laravel Reverb |
|
|
||||||
| Autenticación hardware | Token AES-256-CBC propio |
|
|
||||||
| Autenticación usuarios | Laravel Passport (OAuth 2.0) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Flujo de Detección RFID
|
|
||||||
|
|
||||||
Este es el flujo central del sistema. Ocurre cada vez que un lector RFID detecta una etiqueta vehicular.
|
|
||||||
|
|
||||||
```
|
|
||||||
Lector RFID (hardware)
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
POST /api/vehicles/buscar
|
|
||||||
Authorization: Bearer {arco_token}
|
|
||||||
{ fast_id, antena?, timestamp? }
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
ArcoTokenMiddleware
|
|
||||||
├─ Desencripta token del encabezado y lo compara contra arcos.api_token
|
|
||||||
├─ Verifica que el arco esté activo (activo = true)
|
|
||||||
└─ Inyecta el objeto Arco en $request->get('arco_autenticado')
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
VehicleController::buscarPorTag()
|
|
||||||
└─ Despacha ProcesarDeteccionVehiculo al queue
|
|
||||||
│ (responde 200 OK inmediato al hardware)
|
|
||||||
│
|
|
||||||
▼ (proceso asíncrono)
|
|
||||||
Job: ProcesarDeteccionVehiculo
|
|
||||||
Reintentos: 3 | Backoff: 10 s | Timeout: 120 s
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
VehicleService::procesarDeteccion($fastId, $arcoId, $antena)
|
|
||||||
│
|
|
||||||
├─ ¿fast_id existe en Redis como robado?
|
|
||||||
│ │
|
|
||||||
│ ├─ SÍ ──► verificarVehiculoRobado()
|
|
||||||
│ │ ├─ Incrementa contador en Redis
|
|
||||||
│ │ ├─ Crea registro en alertas_robos
|
|
||||||
│ │ ├─ Emite evento WebSocket VehiculoRobadoDetectado
|
|
||||||
│ │ └─ Registra en detections + daily_detections (estado: ROBADO)
|
|
||||||
│ │
|
|
||||||
│ └─ NO ──► consultarNuevoVehiculo()
|
|
||||||
│ ├─ Consulta ConsultaRepuveConstancia (API REST)
|
|
||||||
│ │ └─ Si responde → datos del vehículo
|
|
||||||
│ ├─ Si no encontrado → Consulta ReporteRoboService (SOAP federal)
|
|
||||||
│ └─ Registra en detections + daily_detections (estado: LIBRE / DESCONOCIDO)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Autenticación del hardware (ArcoTokenMiddleware)
|
|
||||||
|
|
||||||
Cada lector RFID tiene un `api_token` único almacenado encriptado en la tabla `arcos`. El middleware:
|
|
||||||
|
|
||||||
1. Extrae el Bearer token del encabezado `Authorization`
|
|
||||||
2. Itera los arcos activos comparando el token desencriptado con `EncryptionHelper::encryptWithCustomKey()`
|
|
||||||
3. Rechaza con `401` si el token no corresponde a ningún arco
|
|
||||||
4. Rechaza con `403` si el arco está inactivo (`activo = false`)
|
|
||||||
|
|
||||||
La clave de encriptación proviene de `ARCO_TOKEN_ENCRYPTION_KEY` en `.env`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Gestión de Vehículos Robados
|
|
||||||
|
|
||||||
El registro activo de vehículos robados vive en **Redis**, no en MySQL. Esto permite búsquedas en tiempo O(1) durante cada detección.
|
|
||||||
|
|
||||||
### Registrar vehículo como robado
|
|
||||||
|
|
||||||
**`POST /api/vehicles/consultar`** — requiere `auth:api`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"placa": "ABC1234",
|
|
||||||
"vin": "1G1FB1RX1234567890",
|
|
||||||
"fecha_robo": "2025-02-01",
|
|
||||||
"autoridad": "PROCURADURÍA",
|
|
||||||
"acta": "ACT-2025-001",
|
|
||||||
"denunciante": "Nombre Denunciante",
|
|
||||||
"fecha_acta": "2025-02-01"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
El proceso interno:
|
|
||||||
|
|
||||||
1. Consulta el `fast_id` (tag RFID) del vehículo via `VehicleService::consultarVehiculoPorTag()` usando placa o VIN como llave de búsqueda en REPUVE Constancia
|
|
||||||
2. Verifica que no esté ya registrado en Redis: `Redis::exists("vehiculo:robado:{fastId}")`
|
|
||||||
3. Almacena la entrada en Redis con la clave `vehiculo:robado:{fastId}`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
[
|
|
||||||
'fast_id' => string,
|
|
||||||
'vin' => string,
|
|
||||||
'placa' => string,
|
|
||||||
'marca' => string,
|
|
||||||
'modelo' => string,
|
|
||||||
'color' => string,
|
|
||||||
'fecha_robo' => string,
|
|
||||||
'autoridad' => string,
|
|
||||||
'acta' => string,
|
|
||||||
'denunciante' => string,
|
|
||||||
'fecha_acta' => string,
|
|
||||||
'primera_deteccion' => ISO8601,
|
|
||||||
'ultima_deteccion' => ISO8601,
|
|
||||||
'detecciones' => int // contador incremental
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Recuperar vehículo
|
|
||||||
|
|
||||||
**`POST /api/vehicles/recuperar`** — requiere `auth:api`
|
|
||||||
|
|
||||||
1. Busca el vehículo en Redis por `placa` o `vin` iterando claves `vehiculo:robado:*`
|
|
||||||
2. Crea un registro permanente en MySQL (`vehicles`) con los datos de robo y la fecha de recuperación
|
|
||||||
3. Elimina la clave de Redis: `Redis::del("vehiculo:robado:{fastId}")`
|
|
||||||
|
|
||||||
### Consultar estado de un vehículo
|
|
||||||
|
|
||||||
**`GET /api/vehicles/detectar?placa=...`** o **`GET /api/vehicles/robado?vin=...`**
|
|
||||||
|
|
||||||
Busca primero en Redis y luego consulta las APIs REPUVE según sea necesario.
|
|
||||||
|
|
||||||
### Listar vehículos robados activos
|
|
||||||
|
|
||||||
**`GET /api/vehicles/robados?placa=...&vin=...`**
|
|
||||||
|
|
||||||
Lee todas las claves `vehiculo:robado:*` de Redis. Soporta filtro por `placa` y `vin`.
|
|
||||||
|
|
||||||
### Listar vehículos recuperados
|
|
||||||
|
|
||||||
**`GET /api/vehicles`**
|
|
||||||
|
|
||||||
Lee la tabla `vehicles` en MySQL (archivo histórico de recuperaciones).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Integraciones REPUVE
|
|
||||||
|
|
||||||
El sistema consume tres APIs externas de registros vehiculares. Las credenciales se configuran en `.env` y se exponen en `config/services.php`.
|
|
||||||
|
|
||||||
### 1. REPUVE Constancia (`ConsultaRepuveConstancia`)
|
|
||||||
|
|
||||||
API REST con autenticación JWT. Es la fuente principal de datos vehiculares.
|
|
||||||
|
|
||||||
**Variables de entorno:**
|
|
||||||
```
|
|
||||||
REPUVE_CONSTANCIA_BASE_URL
|
|
||||||
REPUVE_CONSTANCIA_LOGIN_ENDPOINT
|
|
||||||
REPUVE_CONSTANCIA_CONSULTA_ENDPOINT
|
|
||||||
REPUVE_CONSTANCIA_EMAIL
|
|
||||||
REPUVE_CONSTANCIA_PASSWORD
|
|
||||||
REPUVE_CONSTANCIA_TOKEN_TTL # TTL del token en caché (segundos)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Flujo de autenticación:** El servicio obtiene un JWT via email/contraseña y lo almacena en caché por `TOKEN_TTL` segundos. Se renueva automáticamente al expirar.
|
|
||||||
|
|
||||||
**Búsqueda de vehículo** — `consultarVehiculoPorTag()`:
|
|
||||||
|
|
||||||
Intenta en orden: `tag_number` → `placa` → `vin`
|
|
||||||
|
|
||||||
**Respuesta normalizada:**
|
|
||||||
```php
|
|
||||||
[
|
|
||||||
'tag_number' => string, // fast_id / folio RFID
|
|
||||||
'folio_tag' => string,
|
|
||||||
'vin' => string,
|
|
||||||
'placa' => string,
|
|
||||||
'marca' => string,
|
|
||||||
'modelo' => string,
|
|
||||||
'linea' => string,
|
|
||||||
'sublinea' => string,
|
|
||||||
'color' => string,
|
|
||||||
'numero_motor' => string,
|
|
||||||
'clase_veh' => string,
|
|
||||||
'tipo_servicio' => string,
|
|
||||||
'reporte_robo' => bool, // indicador de robo en REPUVE
|
|
||||||
'propietario' => [
|
|
||||||
'id', 'nombre_completo', 'rfc', 'curp', 'telefono', 'direccion'
|
|
||||||
],
|
|
||||||
'tag_status' => string // ACTIVO / INACTIVO
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. REPUVE Federal — Reporte de Robo (`ReporteRoboService`)
|
|
||||||
|
|
||||||
Servicio SOAP sobre cURL. Consulta el padrón federal de vehículos robados.
|
|
||||||
|
|
||||||
**Variables de entorno:**
|
|
||||||
```
|
|
||||||
REPUVE_FED_BASE_URL
|
|
||||||
REPUVE_FED_USERNAME
|
|
||||||
REPUVE_FED_PASSWORD
|
|
||||||
```
|
|
||||||
|
|
||||||
**Método `consultarRobado($vin, $placa)`**
|
|
||||||
|
|
||||||
Protocolo SOAP; el cuerpo de la solicitud:
|
|
||||||
```xml
|
|
||||||
<wsdl:doConsRepRobo>
|
|
||||||
<arg0>{username}</arg0>
|
|
||||||
<arg1>{password}</arg1>
|
|
||||||
<arg2>{vin}|{placa}|||||</arg2>
|
|
||||||
</wsdl:doConsRepRobo>
|
|
||||||
```
|
|
||||||
|
|
||||||
Respuesta parseada desde `<return>`: `OK:indicador|fecha|placa|vin|autoridad|acta|denunciante|fecha_acta`
|
|
||||||
|
|
||||||
```php
|
|
||||||
[
|
|
||||||
'tiene_reporte' => bool, // true si indicador == '1'
|
|
||||||
'datos' => [
|
|
||||||
'indicador' => '1', // 1 = robado, 0 = no robado
|
|
||||||
'fecha_robo' => 'YYYY-MM-DD',
|
|
||||||
'placa' => string,
|
|
||||||
'vin' => string,
|
|
||||||
'autoridad' => string,
|
|
||||||
'acta' => string,
|
|
||||||
'denunciante' => string,
|
|
||||||
'fecha_acta' => 'YYYY-MM-DD',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Método `consultarVehiculo($vin, $placa)`**
|
|
||||||
|
|
||||||
Consulta el padrón nacional para obtener datos del vehículo (sin estado de robo).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Padrón Estatal (`ConsultaEstatalService`)
|
|
||||||
|
|
||||||
Servicio SOAP que responde en JSON. Consulta el registro estatal de vehículos.
|
|
||||||
|
|
||||||
**Variable de entorno:** `REPUVE_EST_URL`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Jerarquía de consulta durante una detección
|
|
||||||
|
|
||||||
```
|
|
||||||
fast_id detectado
|
|
||||||
│
|
|
||||||
├─ 1° Redis (robados en caliente) → respuesta inmediata
|
|
||||||
├─ 2° REPUVE Constancia (datos + robo) → API REST / JWT
|
|
||||||
└─ 3° REPUVE Federal (backup) → SOAP
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Gestión de Arcos (Lectores RFID)
|
|
||||||
|
|
||||||
Un **arco** representa un lector RFID físico instalado en campo.
|
|
||||||
|
|
||||||
### Modelo `Arco`
|
|
||||||
|
|
||||||
Campos relevantes de la tabla `arcos`:
|
|
||||||
|
|
||||||
| Campo | Tipo | Descripción |
|
|
||||||
|---|---|---|
|
|
||||||
| `nombre` | string | Nombre descriptivo del arco |
|
|
||||||
| `ip_address` | string (unique) | IP del lector en la red |
|
|
||||||
| `ubicacion` | string | Descripción de la ubicación física |
|
|
||||||
| `activo` | boolean | Habilita/deshabilita el arco |
|
|
||||||
| `antena_1..4` | string | Etiquetas para cada antena |
|
|
||||||
| `api_token` | string | Token encriptado AES-256-CBC |
|
|
||||||
|
|
||||||
**Generación de token:** Al crear un arco se genera automáticamente un token de 64 caracteres aleatorios, encriptado con `EncryptionHelper::encryptWithCustomKey()` antes de persistir en BD.
|
|
||||||
|
|
||||||
### Endpoints
|
|
||||||
|
|
||||||
| Método | Ruta | Acción |
|
|
||||||
|---|---|---|
|
|
||||||
| `GET` | `/api/arcos` | Listar arcos (filtros: `activo`, `ip`) |
|
|
||||||
| `POST` | `/api/arcos` | Crear arco |
|
|
||||||
| `GET` | `/api/arcos/{id}` | Detalle de un arco |
|
|
||||||
| `PUT/PATCH` | `/api/arcos/{id}` | Actualizar arco |
|
|
||||||
| `DELETE` | `/api/arcos/{id}` | Eliminar arco |
|
|
||||||
| `PATCH` | `/api/arcos/{id}/toggle-estado` | Activar / desactivar |
|
|
||||||
| `GET` | `/api/arcos/{id}/detecciones/dia` | Detecciones del día para ese arco |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sistema de Alertas
|
|
||||||
|
|
||||||
Una alerta se genera automáticamente cuando el job de detección identifica que el `fast_id` detectado corresponde a un vehículo registrado como robado en Redis.
|
|
||||||
|
|
||||||
### Generación (`VehicleService::crearAlertaRobo()`)
|
|
||||||
|
|
||||||
1. Inserta registro en `alertas_robos`
|
|
||||||
2. Emite evento WebSocket `VehiculoRobadoDetectado` al canal `alertas-robos`
|
|
||||||
|
|
||||||
### Confirmar alerta
|
|
||||||
|
|
||||||
**`PUT /api/alertas/{id}/confirmar`** — requiere `auth:api`
|
|
||||||
|
|
||||||
- Si ya fue confirmada (`visto = true`), devuelve `400` con los datos del usuario que la confirmó
|
|
||||||
- Si no, actualiza: `visto = true`, `usuario_id = auth()->id()`, `fecha_confirmacion = now()`
|
|
||||||
|
|
||||||
### Endpoints de consulta
|
|
||||||
|
|
||||||
| Endpoint | Descripción |
|
|
||||||
|---|---|
|
|
||||||
| `GET /api/alertas/pendientes` | Sin confirmar, ordenadas por más recientes |
|
|
||||||
| `GET /api/alertas` | Todas, filtrable por `visto`, `arco_id`, `placa`, `vin`, rango de fechas |
|
|
||||||
| `GET /api/alertas/{id}` | Detalle de una alerta |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Registro de Detecciones
|
|
||||||
|
|
||||||
Cada detección se persiste en **dos tablas** con propósitos distintos.
|
|
||||||
|
|
||||||
### `detections` — Historial completo
|
|
||||||
|
|
||||||
Registro individual e inmutable de cada evento RFID.
|
|
||||||
|
|
||||||
| Campo | Descripción |
|
|
||||||
|---|---|
|
|
||||||
| `arco_id` | FK al arco que detectó |
|
|
||||||
| `fast_id` | Tag RFID leído |
|
|
||||||
| `vin`, `placa`, `marca`, `modelo`, `color` | Datos del vehículo al momento de detección |
|
|
||||||
| `antena` | Número de antena del arco |
|
|
||||||
| `fecha_deteccion` | Timestamp exacto |
|
|
||||||
|
|
||||||
### `daily_detections` — Resumen diario
|
|
||||||
|
|
||||||
Incluye información adicional de estado, útil para dashboards y reportes.
|
|
||||||
|
|
||||||
| Campo | Descripción |
|
|
||||||
|---|---|
|
|
||||||
| `arco_nombre` | Nombre del arco (desnormalizado) |
|
|
||||||
| `estado` | `LIBRE` \| `ROBADO` \| `DESCONOCIDO` |
|
|
||||||
| `tiene_reporte_robo` | Boolean — si REPUVE reporta robo |
|
|
||||||
| `fecha_dia` | Columna virtual: `DATE(fecha_deteccion)` |
|
|
||||||
|
|
||||||
Índices: `arco_id`, `fecha_dia`, `[arco_id + fecha_dia]`, `fast_id`
|
|
||||||
|
|
||||||
### Endpoints de consulta
|
|
||||||
|
|
||||||
| Endpoint | Descripción |
|
|
||||||
|---|---|
|
|
||||||
| `GET /api/vehicles/detecciones` | Historial paginado (filtros: `placa`, `vin`) |
|
|
||||||
| `GET /api/vehicles/detecciones/dia?fecha=YYYY-MM-DD` | Resumen diario (ventana de últimos 10 s) |
|
|
||||||
| `GET /api/arcos/{id}/detecciones/dia?fecha=YYYY-MM-DD` | Resumen diario por arco |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Transmisión en Tiempo Real
|
|
||||||
|
|
||||||
**Servidor:** Laravel Reverb (WebSocket)
|
|
||||||
|
|
||||||
Cuando se detecta un vehículo robado, se emite el evento `VehiculoRobadoDetectado`:
|
|
||||||
|
|
||||||
- **Canal:** `alertas-robos` (público)
|
|
||||||
- **Nombre en frontend:** `vehiculo.robado.detectado`
|
|
||||||
- **Payload:**
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"alerta_id": 42,
|
|
||||||
"fast_id": "1234567890",
|
|
||||||
"vin": "1G1FB1RX1234567890",
|
|
||||||
"placa": "ABC1234",
|
|
||||||
"marca": "Chevrolet",
|
|
||||||
"modelo": "Cruze",
|
|
||||||
"color": "BLANCO",
|
|
||||||
"arco_id": 3,
|
|
||||||
"arco_nombre": "Arco Reforma Norte",
|
|
||||||
"antena": "1",
|
|
||||||
"fecha_deteccion": "2025-02-01T14:30:00Z",
|
|
||||||
"mensaje": "🚨 VEHÍCULO ROBADO DETECTADO: ABC1234 en Arco Reforma Norte"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
El frontend conecta vía **Laravel Echo + Pusher.js**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Esquema de Base de Datos
|
|
||||||
|
|
||||||
### `arcos`
|
|
||||||
```sql
|
|
||||||
id, nombre, ip_address (UNIQUE), ubicacion, descripcion,
|
|
||||||
activo BOOLEAN DEFAULT true,
|
|
||||||
antena_1, antena_2, antena_3, antena_4,
|
|
||||||
api_token,
|
|
||||||
created_at, updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### `detections`
|
|
||||||
```sql
|
|
||||||
id, arco_id (FK arcos), fast_id, vin, placa,
|
|
||||||
marca, modelo, color, antena, fecha_deteccion,
|
|
||||||
created_at, updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### `daily_detections`
|
|
||||||
```sql
|
|
||||||
id, arco_id (FK arcos), arco_nombre, antena,
|
|
||||||
fast_id, vin, placa, marca, modelo, color,
|
|
||||||
estado ENUM('LIBRE','ROBADO','DESCONOCIDO'),
|
|
||||||
tiene_reporte_robo BOOLEAN,
|
|
||||||
fecha_deteccion, fecha_dia (virtual: DATE(fecha_deteccion)),
|
|
||||||
created_at, updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### `vehicles` — Archivo de recuperaciones
|
|
||||||
```sql
|
|
||||||
id, fast_id, vin, placa,
|
|
||||||
fecha_robo, acta_robo, denunciante, fecha_acta,
|
|
||||||
fecha_recuperacion,
|
|
||||||
datos_robo_original JSON,
|
|
||||||
created_at, updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### `alertas_robos`
|
|
||||||
```sql
|
|
||||||
id, fast_id, vin, placa, marca, modelo, color,
|
|
||||||
arco_id (FK arcos), arco_nombre, antena,
|
|
||||||
fecha_deteccion,
|
|
||||||
visto BOOLEAN DEFAULT false,
|
|
||||||
usuario_id (FK users, NULLABLE),
|
|
||||||
fecha_confirmacion NULLABLE,
|
|
||||||
created_at, updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comandos y Tareas Programadas
|
|
||||||
|
|
||||||
### `php artisan detecciones:limpiar {--dias=1}`
|
|
||||||
|
|
||||||
Elimina registros de `daily_detections` con más de N días de antigüedad (default: 1 día). Se puede ejecutar manualmente o agregar al scheduler.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
php artisan detecciones:limpiar # elimina detecciones de más de 1 día
|
|
||||||
php artisan detecciones:limpiar --dias=7 # elimina detecciones de más de 7 días
|
|
||||||
```
|
|
||||||
|
|
||||||
### Servicios en producción (PM2)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
composer run services:start # inicia queue worker, scheduler y Reverb
|
|
||||||
composer run services:stop # detiene todos los servicios
|
|
||||||
composer run services:status # muestra estado de cada proceso PM2
|
|
||||||
```
|
|
||||||
|
|
||||||
Procesos individuales:
|
|
||||||
```bash
|
|
||||||
php artisan queue:work # procesa detecciones encoladas
|
|
||||||
php artisan schedule:work # ejecuta tareas programadas
|
|
||||||
php artisan reverb:start # servidor WebSocket
|
|
||||||
```
|
|
||||||
@ -1,16 +1,10 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Configurar Git sin necesidad de permisos globales
|
git config --global --add safe.directory /var/www/repuve-v1
|
||||||
export GIT_CONFIG_GLOBAL=/tmp/.gitconfig
|
|
||||||
git config --global --add safe.directory /var/www/arcos-backend
|
|
||||||
|
|
||||||
echo "=== Iniciando entrypoint DESARROLLO ==="
|
echo "=== Iniciando entrypoint DESARROLLO ==="
|
||||||
|
|
||||||
# Asegurar permisos correctos para directorios críticos
|
|
||||||
echo "Verificando permisos de directorios..."
|
|
||||||
chown -R www-data:www-data /var/www/arcos-backend/vendor /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache 2>/dev/null || true
|
|
||||||
|
|
||||||
# Variables desde Docker environment
|
# Variables desde Docker environment
|
||||||
DB_HOST=${DB_HOST:-mysql}
|
DB_HOST=${DB_HOST:-mysql}
|
||||||
DB_USERNAME=${DB_USERNAME:-root}
|
DB_USERNAME=${DB_USERNAME:-root}
|
||||||
@ -85,6 +79,7 @@ fi
|
|||||||
# Establecer permisos correctos para las claves
|
# Establecer permisos correctos para las claves
|
||||||
chmod 600 storage/app/keys/oauth-private.key
|
chmod 600 storage/app/keys/oauth-private.key
|
||||||
chmod 644 storage/app/keys/oauth-public.key
|
chmod 644 storage/app/keys/oauth-public.key
|
||||||
|
chown www-data:www-data storage/app/keys/oauth-*.key
|
||||||
|
|
||||||
echo "✓ Claves de Passport verificadas"
|
echo "✓ Claves de Passport verificadas"
|
||||||
|
|
||||||
@ -114,10 +109,7 @@ fi
|
|||||||
|
|
||||||
echo "✓ Configuración de desarrollo completada"
|
echo "✓ Configuración de desarrollo completada"
|
||||||
|
|
||||||
echo "=== Iniciando scheduler en background ==="
|
|
||||||
# Ejecutar scheduler cada minuto en background
|
|
||||||
(while true; do php artisan schedule:run >> /var/www/arcos-backend/storage/logs/scheduler.log 2>&1; sleep 3600; done) &
|
|
||||||
|
|
||||||
echo "=== Iniciando PHP-FPM DESARROLLO ==="
|
echo "=== Iniciando PHP-FPM DESARROLLO ==="
|
||||||
|
|
||||||
|
# Iniciar PHP-FPM
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "arcos-backend",
|
"name": "holos.backend",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\Api\AlertaRoboController;
|
|
||||||
use App\Http\Controllers\Api\PruebaReverbController;
|
|
||||||
use App\Http\Controllers\Api\VehicleController;
|
use App\Http\Controllers\Api\VehicleController;
|
||||||
use App\Http\Controllers\Api\ArcoController;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,35 +17,12 @@
|
|||||||
* Procura revisar que no existan rutas que entren en conflicto con las rutas del núcleo.
|
* Procura revisar que no existan rutas que entren en conflicto con las rutas del núcleo.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Rutas con autenticación de arco (token único por arco) */
|
/** Rutas protegidas (requieren autenticación) */
|
||||||
Route::middleware('arco.token')->group(function() {
|
|
||||||
Route::post('/vehicles/buscar', [VehicleController::class, 'buscarPorTag']);
|
|
||||||
});
|
|
||||||
|
|
||||||
/** Rutas protegidas (requieren autenticación JWT de usuario) */
|
|
||||||
Route::middleware('auth:api')->group(function() {
|
Route::middleware('auth:api')->group(function() {
|
||||||
|
|
||||||
// Rutas de Vehículos
|
Route::post('/vehicles/consultar', [VehicleController::class, 'consultarVehiculo']);
|
||||||
Route::post('/vehicles/consultar', [VehicleController::class, 'vehiculoRobadoAlta']);
|
|
||||||
Route::post('/vehicles/recuperar', [VehicleController::class, 'recuperarVehiculo']);
|
Route::post('/vehicles/recuperar', [VehicleController::class, 'recuperarVehiculo']);
|
||||||
Route::get('/vehicles/detectar', [VehicleController::class, 'buscarVehiculo']);
|
|
||||||
Route::get('/vehicles/robados', [VehicleController::class, 'listarRobados']);
|
Route::get('/vehicles/robados', [VehicleController::class, 'listarRobados']);
|
||||||
Route::get('/vehicles', [VehicleController::class, 'listarRecuperados']);
|
|
||||||
Route::get('/vehicles/detecciones', [VehicleController::class, 'listarDetecciones']);
|
|
||||||
Route::get('/vehicles/detecciones/dia', [VehicleController::class, 'listarDeteccionesDelDia']);
|
|
||||||
Route::get('/vehicles/robado', [VehicleController::class, 'buscarVehiculoRobado']);
|
|
||||||
|
|
||||||
// Rutas de Arcos RFID
|
|
||||||
Route::resource('/arcos', ArcoController::class);
|
|
||||||
Route::patch('/arcos/{id}/toggle-estado', [ArcoController::class, 'toggleEstado']);
|
|
||||||
Route::get('/arcos/{id}/detecciones/dia', [ArcoController::class, 'deteccionesDelDia']);
|
|
||||||
|
|
||||||
//alerta
|
|
||||||
Route::get('/alertas/pendientes', [AlertaRoboController::class, 'pendientes']);
|
|
||||||
Route::get('/alertas', [AlertaRoboController::class, 'index']);
|
|
||||||
Route::get('/alertas/{id}', [AlertaRoboController::class, 'show']);
|
|
||||||
Route::put('/alertas/{id}/confirmar', [AlertaRoboController::class, 'confirmar']);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Rutas públicas */
|
/** Rutas públicas */
|
||||||
|
|||||||
@ -8,8 +8,3 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Schedule::call(new DeleteResetPasswords)->hourly();
|
Schedule::call(new DeleteResetPasswords)->hourly();
|
||||||
|
|
||||||
// Limpiar detecciones diarias a las 00:00
|
|
||||||
Schedule::command('detecciones:limpiar')
|
|
||||||
->dailyAt('00:00')
|
|
||||||
->timezone(config('app.timezone', 'UTC'));
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user