Sistema completo para monitoreo y gestion de flotas de vehiculos con: - Backend FastAPI con PostgreSQL/TimescaleDB - Frontend React con TypeScript y TailwindCSS - App movil React Native con Expo - Soporte para dispositivos GPS, Meshtastic y celulares - Video streaming en vivo con MediaMTX - Geocercas, alertas, viajes y reportes - Autenticacion JWT y WebSockets en tiempo real Documentacion completa y guias de usuario incluidas.
238 lines
6.5 KiB
Python
238 lines
6.5 KiB
Python
"""
|
|
Endpoints para recepción y consulta de ubicaciones GPS.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, Query, Request
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.database import get_db
|
|
from app.core.security import get_current_user
|
|
from app.models.usuario import Usuario
|
|
from app.schemas.ubicacion import (
|
|
UbicacionCreate,
|
|
UbicacionBulkCreate,
|
|
UbicacionResponse,
|
|
HistorialUbicacionesResponse,
|
|
OsmAndLocationCreate,
|
|
)
|
|
from app.services.ubicacion_service import UbicacionService
|
|
from app.services.alerta_service import AlertaService
|
|
from app.services.viaje_service import ViajeService
|
|
|
|
router = APIRouter(prefix="/ubicaciones", tags=["Ubicaciones"])
|
|
|
|
|
|
@router.post("", response_model=Optional[UbicacionResponse])
|
|
async def recibir_ubicacion(
|
|
ubicacion_data: UbicacionCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""
|
|
Recibe una ubicación GPS desde la app móvil o dispositivo.
|
|
|
|
Este endpoint no requiere autenticación para facilitar
|
|
la integración con dispositivos GPS simples.
|
|
|
|
Args:
|
|
ubicacion_data: Datos de la ubicación.
|
|
|
|
Returns:
|
|
Ubicación procesada o None si se descartó.
|
|
"""
|
|
ubicacion_service = UbicacionService(db)
|
|
resultado = await ubicacion_service.procesar_ubicacion(ubicacion_data)
|
|
|
|
if resultado:
|
|
# Procesar alertas y viajes en background
|
|
alerta_service = AlertaService(db)
|
|
viaje_service = ViajeService(db)
|
|
|
|
# Verificar velocidad
|
|
if ubicacion_data.velocidad:
|
|
await alerta_service.verificar_velocidad(
|
|
resultado.vehiculo_id,
|
|
ubicacion_data.velocidad,
|
|
ubicacion_data.lat,
|
|
ubicacion_data.lng,
|
|
)
|
|
|
|
# Verificar batería
|
|
if ubicacion_data.bateria_dispositivo:
|
|
await alerta_service.verificar_bateria_baja(
|
|
resultado.vehiculo_id,
|
|
ubicacion_data.bateria_dispositivo,
|
|
ubicacion_data.lat,
|
|
ubicacion_data.lng,
|
|
)
|
|
|
|
# Procesar viaje
|
|
await viaje_service.procesar_ubicacion_viaje(
|
|
resultado.vehiculo_id,
|
|
ubicacion_data.lat,
|
|
ubicacion_data.lng,
|
|
ubicacion_data.velocidad or 0,
|
|
resultado.tiempo,
|
|
)
|
|
|
|
return resultado
|
|
|
|
|
|
@router.post("/bulk", response_model=dict)
|
|
async def recibir_ubicaciones_bulk(
|
|
data: UbicacionBulkCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""
|
|
Recibe múltiples ubicaciones en una sola petición.
|
|
|
|
Útil para sincronización de datos acumulados cuando
|
|
el dispositivo estuvo offline.
|
|
|
|
Args:
|
|
data: Lista de ubicaciones.
|
|
|
|
Returns:
|
|
Conteo de ubicaciones procesadas.
|
|
"""
|
|
ubicacion_service = UbicacionService(db)
|
|
procesadas = 0
|
|
errores = 0
|
|
|
|
for ubicacion_data in data.ubicaciones:
|
|
try:
|
|
resultado = await ubicacion_service.procesar_ubicacion(ubicacion_data)
|
|
if resultado:
|
|
procesadas += 1
|
|
except Exception:
|
|
errores += 1
|
|
|
|
return {
|
|
"total": len(data.ubicaciones),
|
|
"procesadas": procesadas,
|
|
"errores": errores,
|
|
}
|
|
|
|
|
|
@router.get("/osmand")
|
|
async def recibir_ubicacion_osmand(
|
|
request: Request,
|
|
id: str,
|
|
lat: float,
|
|
lon: float,
|
|
timestamp: Optional[int] = None,
|
|
speed: Optional[float] = None,
|
|
bearing: Optional[float] = None,
|
|
altitude: Optional[float] = None,
|
|
accuracy: Optional[float] = None,
|
|
batt: Optional[float] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""
|
|
Endpoint compatible con OsmAnd Live Tracking.
|
|
|
|
OsmAnd envía ubicaciones mediante GET con parámetros en URL.
|
|
|
|
Args:
|
|
id: Identificador del dispositivo.
|
|
lat: Latitud.
|
|
lon: Longitud.
|
|
timestamp: Unix timestamp (opcional).
|
|
speed: Velocidad en km/h (opcional).
|
|
bearing: Rumbo en grados (opcional).
|
|
altitude: Altitud en metros (opcional).
|
|
accuracy: Precisión en metros (opcional).
|
|
batt: Porcentaje de batería (opcional).
|
|
|
|
Returns:
|
|
Confirmación de recepción.
|
|
"""
|
|
ubicacion_data = UbicacionCreate(
|
|
dispositivo_id=id,
|
|
lat=lat,
|
|
lng=lon,
|
|
velocidad=speed,
|
|
rumbo=bearing,
|
|
altitud=altitude,
|
|
precision=accuracy,
|
|
bateria_dispositivo=batt,
|
|
tiempo=datetime.fromtimestamp(timestamp) if timestamp else None,
|
|
fuente="osmand",
|
|
)
|
|
|
|
ubicacion_service = UbicacionService(db)
|
|
resultado = await ubicacion_service.procesar_ubicacion(ubicacion_data)
|
|
|
|
if resultado:
|
|
return {"status": "ok"}
|
|
return {"status": "device_not_found"}
|
|
|
|
|
|
@router.get("/historial/{vehiculo_id}", response_model=HistorialUbicacionesResponse)
|
|
async def obtener_historial(
|
|
vehiculo_id: int,
|
|
desde: datetime,
|
|
hasta: datetime,
|
|
simplificar: bool = True,
|
|
intervalo_segundos: Optional[int] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Usuario = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Obtiene el historial de ubicaciones de un vehículo.
|
|
|
|
Args:
|
|
vehiculo_id: ID del vehículo.
|
|
desde: Fecha/hora de inicio.
|
|
hasta: Fecha/hora de fin.
|
|
simplificar: Simplificar ruta (Douglas-Peucker).
|
|
intervalo_segundos: Muestreo por intervalo.
|
|
|
|
Returns:
|
|
Historial con estadísticas.
|
|
"""
|
|
ubicacion_service = UbicacionService(db)
|
|
return await ubicacion_service.obtener_historial(
|
|
vehiculo_id,
|
|
desde,
|
|
hasta,
|
|
simplificar,
|
|
intervalo_segundos,
|
|
)
|
|
|
|
|
|
@router.get("/ultima/{vehiculo_id}", response_model=Optional[UbicacionResponse])
|
|
async def obtener_ultima_ubicacion(
|
|
vehiculo_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Usuario = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Obtiene la última ubicación de un vehículo.
|
|
|
|
Args:
|
|
vehiculo_id: ID del vehículo.
|
|
|
|
Returns:
|
|
Última ubicación conocida.
|
|
"""
|
|
ubicacion_service = UbicacionService(db)
|
|
return await ubicacion_service.obtener_ultima_ubicacion(vehiculo_id)
|
|
|
|
|
|
@router.get("/flota")
|
|
async def obtener_ubicaciones_flota(
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Usuario = Depends(get_current_user),
|
|
):
|
|
"""
|
|
Obtiene las ubicaciones actuales de toda la flota.
|
|
|
|
Returns:
|
|
Lista de ubicaciones de todos los vehículos activos.
|
|
"""
|
|
ubicacion_service = UbicacionService(db)
|
|
return await ubicacion_service.obtener_ubicaciones_flota()
|