FlotillasGPS - Sistema completo de monitoreo de flotillas GPS
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.
This commit is contained in:
237
backend/app/api/v1/ubicaciones.py
Normal file
237
backend/app/api/v1/ubicaciones.py
Normal file
@@ -0,0 +1,237 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user