laravel reverb
This commit is contained in:
parent
0487421758
commit
1b4eba2c6c
59
app/Events/VehiculoRobadoDetectado.php
Normal file
59
app/Events/VehiculoRobadoDetectado.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?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}",
|
||||
];
|
||||
}
|
||||
}
|
||||
133
app/Http/Controllers/Api/AlertaRoboController.php
Normal file
133
app/Http/Controllers/Api/AlertaRoboController.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?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(int $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, int $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
|
||||
]);
|
||||
}
|
||||
}
|
||||
73
app/Models/AlertaRobo.php
Normal file
73
app/Models/AlertaRobo.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,8 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Vehicle;
|
||||
use App\Events\VehiculoRobadoDetectado;
|
||||
use App\Models\AlertaRobo;
|
||||
use App\Models\Detection;
|
||||
use App\Models\Arco;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
@ -27,7 +28,7 @@ public function procesarDeteccion(string $fastId, int $arcoId, ?string $antena =
|
||||
|
||||
if ($enRedis) {
|
||||
// Ya está marcado como robado, verificar si sigue así
|
||||
$resultado = $this->verificarVehiculoRobado($fastId, json_decode($enRedis, true));
|
||||
$resultado = $this->verificarVehiculoRobado($fastId, json_decode($enRedis, true), $arcoId, $antena);
|
||||
$this->registrarDeteccion($fastId, $resultado, $arcoId, $antena);
|
||||
return $resultado;
|
||||
}
|
||||
@ -87,7 +88,6 @@ public function consultarVehiculoPorTag(string $fastId)
|
||||
'fast_id' => $fastId
|
||||
]);
|
||||
return null;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('VehicleService: Error en consulta', [
|
||||
'fast_id' => $fastId,
|
||||
@ -118,8 +118,6 @@ public function listarVehiculosRobados(): array
|
||||
|
||||
/**
|
||||
* Listar todas las detecciones del día desde Redis
|
||||
* @param string|null $fecha Fecha en formato Y-m-d (opcional, por defecto hoy)
|
||||
* @return array
|
||||
*/
|
||||
public function listarDeteccionesDelDia(?string $fecha = null): array
|
||||
{
|
||||
@ -145,8 +143,6 @@ public function listarDeteccionesDelDia(?string $fecha = null): array
|
||||
|
||||
/**
|
||||
* Obtener estadísticas de detecciones del día
|
||||
* @param string|null $fecha Fecha en formato Y-m-d (opcional, por defecto hoy)
|
||||
* @return array
|
||||
*/
|
||||
public function obtenerEstadisticasDelDia(?string $fecha = null): array
|
||||
{
|
||||
@ -245,19 +241,22 @@ private function consultarNuevoVehiculo(string $fastId): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar vehículo robado (ya está en Redis)
|
||||
* Verificar vehículo robado (si ya está en Redis)
|
||||
*/
|
||||
private function verificarVehiculoRobado(string $fastId, array $datosRedis): array
|
||||
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 nuevamente', [
|
||||
Log::warning('¡VEHÍCULO ROBADO DETECTADO!', [
|
||||
'fast_id' => $fastId,
|
||||
'vin' => $datosRedis['vin'] ?? null,
|
||||
'placa' => $datosRedis['placa'] ?? null,
|
||||
'detecciones' => ($datosRedis['detecciones'] ?? 0) + 1
|
||||
'placa' => $datosRedis['placa'] ?? null
|
||||
]);
|
||||
|
||||
// Crear alerta en la base de datos
|
||||
$this->crearAlertaRobo($fastId, $datosRedis, $arcoId, $antena);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'tiene_reporte_robo' => true,
|
||||
@ -267,6 +266,7 @@ private function verificarVehiculoRobado(string $fastId, array $datosRedis): arr
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Actualizar contador de detecciones en Redis
|
||||
*/
|
||||
@ -363,4 +363,38 @@ private function registrarDeteccionDelDia(string $fastId, array $vehiculo, ?int
|
||||
// Establecer expiración automática al final del día
|
||||
Redis::expire($key, $segundosHastaFinDelDia);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/passport": "^12.4",
|
||||
"laravel/pulse": "^1.4",
|
||||
"laravel/reverb": "^1.4",
|
||||
"laravel/reverb": "^1.7",
|
||||
"laravel/tinker": "^2.10",
|
||||
"notsoweb/laravel-core": "dev-main",
|
||||
"spatie/laravel-permission": "^6.16",
|
||||
|
||||
20
composer.lock
generated
20
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "0cdf26aa072a7f833793cfba72e94e81",
|
||||
"content-hash": "2aee9315866597be53eadf5c315e8b69",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -1855,16 +1855,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/reverb",
|
||||
"version": "v1.5.0",
|
||||
"version": "v1.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/reverb.git",
|
||||
"reference": "bf84766ad35d9174fb508147f956e8bcf2e46e91"
|
||||
"reference": "4300fbbc3535f5cbbdf600b00edca1e4c515bfcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/reverb/zipball/bf84766ad35d9174fb508147f956e8bcf2e46e91",
|
||||
"reference": "bf84766ad35d9174fb508147f956e8bcf2e46e91",
|
||||
"url": "https://api.github.com/repos/laravel/reverb/zipball/4300fbbc3535f5cbbdf600b00edca1e4c515bfcf",
|
||||
"reference": "4300fbbc3535f5cbbdf600b00edca1e4c515bfcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -1884,8 +1884,8 @@
|
||||
"symfony/http-foundation": "^6.3|^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"orchestra/testbench": "^8.0|^9.0|^10.0",
|
||||
"pestphp/pest": "^2.0|^3.0",
|
||||
"orchestra/testbench": "^8.36|^9.15|^10.8",
|
||||
"pestphp/pest": "^2.0|^3.0|^4.0",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"ratchet/pawl": "^0.4.1",
|
||||
"react/async": "^4.2",
|
||||
@ -1931,9 +1931,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/reverb/issues",
|
||||
"source": "https://github.com/laravel/reverb/tree/v1.5.0"
|
||||
"source": "https://github.com/laravel/reverb/tree/v1.7.0"
|
||||
},
|
||||
"time": "2025-03-31T14:06:47+00:00"
|
||||
"time": "2026-01-06T16:26:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
@ -10494,5 +10494,5 @@
|
||||
"php": "^8.3"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.6.0"
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
<?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');
|
||||
}
|
||||
};
|
||||
@ -13,6 +13,8 @@ services:
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- DB_DATABASE=${DB_DATABASE}
|
||||
- DB_PORT=${DB_PORT}
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./:/var/www/arcos-backend
|
||||
- ./vendor:/var/www/arcos/vendor
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\AlertaRoboController;
|
||||
use App\Http\Controllers\Api\PruebaReverbController;
|
||||
use App\Http\Controllers\Api\VehicleController;
|
||||
use App\Http\Controllers\Api\ArcoController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -40,6 +42,13 @@
|
||||
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', [AlertaRoboController::class, 'index']);
|
||||
Route::get('/alertas/{id}', [AlertaRoboController::class, 'show']);
|
||||
Route::get('/alertas/pendientes', [AlertaRoboController::class, 'pendientes']);
|
||||
Route::put('/alertas/{id}/confirmar', [AlertaRoboController::class, 'confirmar']);
|
||||
|
||||
});
|
||||
|
||||
/** Rutas públicas */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user