""" 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()