feat: WebSockets para ETA en tiempo real y endpoint alertas operativas

This commit is contained in:
2026-05-23 01:03:01 -06:00
parent c829342b48
commit 51fbe69e13
2 changed files with 84 additions and 6 deletions

View File

@@ -1,8 +1,11 @@
from fastapi import FastAPI, Depends, HTTPException from fastapi import FastAPI, Depends, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from database import engine, get_db from database import engine, get_db
from typing import Dict, Set
import asyncio
import json
import models, schemas, auth, simulator import models, schemas, auth, simulator
models.Base.metadata.create_all(bind=engine) models.Base.metadata.create_all(bind=engine)
@@ -12,6 +15,32 @@ app = FastAPI(title="HackOnLinces 2026 - Recolección de Residuos")
app.add_middleware(CORSMiddleware, allow_origins=["*"], app.add_middleware(CORSMiddleware, allow_origins=["*"],
allow_methods=["*"], allow_headers=["*"]) allow_methods=["*"], allow_headers=["*"])
class ConnectionManager:
def __init__(self):
self.active_connections: Dict[str, Set[WebSocket]] = {}
async def connect(self, websocket: WebSocket, route_id: str):
await websocket.accept()
if route_id not in self.active_connections:
self.active_connections[route_id] = set()
self.active_connections[route_id].add(websocket)
def disconnect(self, websocket: WebSocket, route_id: str):
if route_id in self.active_connections:
self.active_connections[route_id].discard(websocket)
async def broadcast_to_route(self, route_id: str, message: dict):
if route_id in self.active_connections:
dead = set()
for ws in self.active_connections[route_id]:
try:
await ws.send_json(message)
except:
dead.add(ws)
self.active_connections[route_id] -= dead
manager = ConnectionManager()
scheduler = BackgroundScheduler() scheduler = BackgroundScheduler()
scheduler.add_job(simulator.avanzar_rutas, "interval", minutes=2) scheduler.add_job(simulator.avanzar_rutas, "interval", minutes=2)
scheduler.start() scheduler.start()
@@ -62,6 +91,14 @@ def crear_domicilio(data: schemas.DomicilioCreate,
db.refresh(dom) db.refresh(dom)
return dom return dom
@app.get("/domicilios")
def listar_domicilios(
current_user=Depends(auth.get_current_user),
db: Session = Depends(get_db)
):
domicilios = db.query(models.Domicilio).filter_by(usuario_id=current_user.id).all()
return [{"id": d.id, "direccion": d.direccion, "colonia": d.colonia, "route_id": d.route_id} for d in domicilios]
@app.get("/eta/{domicilio_id}", response_model=schemas.ETAResponse) @app.get("/eta/{domicilio_id}", response_model=schemas.ETAResponse)
def get_eta(domicilio_id: int, def get_eta(domicilio_id: int,
current_user=Depends(auth.get_current_user), current_user=Depends(auth.get_current_user),
@@ -95,10 +132,51 @@ def crear_reporte(
"estado": "PENDIENTE" "estado": "PENDIENTE"
} }
@app.get("/domicilios") @app.post("/alertas/operativa")
def listar_domicilios( def crear_alerta_operativa(
current_user=Depends(auth.get_current_user), route_id: str,
tipo: str,
mensaje: str,
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
domicilios = db.query(models.Domicilio).filter_by(usuario_id=current_user.id).all() estado = db.query(models.EstadoRuta).filter_by(route_id=route_id).first()
return [{"id": d.id, "direccion": d.direccion, "colonia": d.colonia, "route_id": d.route_id} for d in domicilios] if not estado:
raise HTTPException(status_code=404, detail="Ruta no encontrada")
return {
"route_id": route_id,
"tipo": tipo,
"mensaje": mensaje,
"evento": "ALERTA_OPERATIVA",
"estado": "ENVIADA"
}
@app.websocket("/ws/eta/{domicilio_id}")
async def websocket_eta(websocket: WebSocket, domicilio_id: int,
token: str, db: Session = Depends(get_db)):
try:
payload = auth.jwt.decode(token, auth.SECRET_KEY, algorithms=[auth.ALGORITHM])
email = payload.get("sub")
user = db.query(models.Usuario).filter_by(email=email).first()
if not user:
await websocket.close(code=1008)
return
dom = db.query(models.Domicilio).filter_by(id=domicilio_id).first()
if not dom or dom.usuario_id != user.id:
await websocket.close(code=1008)
return
except:
await websocket.close(code=1008)
return
await manager.connect(websocket, dom.route_id)
try:
eta = simulator.get_eta(dom.route_id, db)
if eta:
await websocket.send_json({**eta, "route_id": dom.route_id, "colonia": dom.colonia})
while True:
await asyncio.sleep(30)
eta = simulator.get_eta(dom.route_id, db)
if eta:
await websocket.send_json({**eta, "route_id": dom.route_id, "colonia": dom.colonia})
except WebSocketDisconnect:
manager.disconnect(websocket, dom.route_id)