Files
ATLAS/backend/app/api/v1/dispositivos.py
FlotillasGPS Developer 51d78bacf4 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.
2026-01-21 08:18:00 +00:00

363 lines
12 KiB
Python

"""
Endpoints para gestión de dispositivos GPS.
"""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.core.database import get_db
from app.core.security import get_current_user
from app.models.usuario import Usuario
from app.models.dispositivo import Dispositivo
from app.schemas.dispositivo import (
DispositivoCreate,
DispositivoUpdate,
DispositivoResponse,
DispositivoResumen,
DispositivoConVehiculo,
)
router = APIRouter(prefix="/dispositivos", tags=["Dispositivos"])
@router.get("", response_model=List[DispositivoResumen])
async def listar_dispositivos(
vehiculo_id: Optional[int] = None,
tipo: Optional[str] = None,
activo: Optional[bool] = None,
conectado: Optional[bool] = None,
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
db: AsyncSession = Depends(get_db),
current_user: Usuario = Depends(get_current_user),
):
"""
Lista dispositivos con filtros opcionales.
Args:
vehiculo_id: Filtrar por vehículo.
tipo: Filtrar por tipo.
activo: Filtrar por estado activo.
conectado: Filtrar por estado de conexión.
skip: Registros a saltar.
limit: Límite de registros.
Returns:
Lista de dispositivos.
"""
query = select(Dispositivo).order_by(Dispositivo.identificador)
if vehiculo_id:
query = query.where(Dispositivo.vehiculo_id == vehiculo_id)
if tipo:
query = query.where(Dispositivo.tipo == tipo)
if activo is not None:
query = query.where(Dispositivo.activo == activo)
if conectado is not None:
query = query.where(Dispositivo.conectado == conectado)
query = query.offset(skip).limit(limit)
result = await db.execute(query)
dispositivos = result.scalars().all()
return [
DispositivoResumen(
id=d.id,
identificador=d.identificador,
tipo=d.tipo,
protocolo=d.protocolo,
activo=d.activo,
conectado=d.conectado,
ultimo_contacto=d.ultimo_contacto,
bateria=d.bateria,
)
for d in dispositivos
]
@router.get("/{dispositivo_id}", response_model=DispositivoConVehiculo)
async def obtener_dispositivo(
dispositivo_id: int,
db: AsyncSession = Depends(get_db),
current_user: Usuario = Depends(get_current_user),
):
"""
Obtiene un dispositivo por su ID.
Args:
dispositivo_id: ID del dispositivo.
Returns:
Dispositivo con información del vehículo.
"""
result = await db.execute(
select(Dispositivo)
.options(selectinload(Dispositivo.vehiculo))
.where(Dispositivo.id == dispositivo_id)
)
dispositivo = result.scalar_one_or_none()
if not dispositivo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Dispositivo con id {dispositivo_id} no encontrado",
)
return DispositivoConVehiculo(
id=dispositivo.id,
vehiculo_id=dispositivo.vehiculo_id,
tipo=dispositivo.tipo,
identificador=dispositivo.identificador,
nombre=dispositivo.nombre,
marca=dispositivo.marca,
modelo=dispositivo.modelo,
numero_serie=dispositivo.numero_serie,
telefono_sim=dispositivo.telefono_sim,
operador_sim=dispositivo.operador_sim,
iccid=dispositivo.iccid,
imei=dispositivo.imei,
protocolo=dispositivo.protocolo,
ultimo_contacto=dispositivo.ultimo_contacto,
bateria=dispositivo.bateria,
señal_gsm=dispositivo.señal_gsm,
satelites=dispositivo.satelites,
intervalo_reporte=dispositivo.intervalo_reporte,
configuracion=dispositivo.configuracion,
firmware_version=dispositivo.firmware_version,
activo=dispositivo.activo,
conectado=dispositivo.conectado,
notas=dispositivo.notas,
esta_online=dispositivo.esta_online,
creado_en=dispositivo.creado_en,
actualizado_en=dispositivo.actualizado_en,
vehiculo_nombre=dispositivo.vehiculo.nombre if dispositivo.vehiculo else None,
vehiculo_placa=dispositivo.vehiculo.placa if dispositivo.vehiculo else None,
)
@router.post("", response_model=DispositivoResponse, status_code=status.HTTP_201_CREATED)
async def crear_dispositivo(
dispositivo_data: DispositivoCreate,
db: AsyncSession = Depends(get_db),
current_user: Usuario = Depends(get_current_user),
):
"""
Crea un nuevo dispositivo.
Args:
dispositivo_data: Datos del dispositivo.
Returns:
Dispositivo creado.
"""
# Verificar identificador único
result = await db.execute(
select(Dispositivo).where(Dispositivo.identificador == dispositivo_data.identificador)
)
if result.scalar_one_or_none():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Ya existe un dispositivo con el identificador {dispositivo_data.identificador}",
)
# Verificar IMEI único si se proporciona
if dispositivo_data.imei:
result = await db.execute(
select(Dispositivo).where(Dispositivo.imei == dispositivo_data.imei)
)
if result.scalar_one_or_none():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Ya existe un dispositivo con el IMEI {dispositivo_data.imei}",
)
dispositivo = Dispositivo(**dispositivo_data.model_dump())
db.add(dispositivo)
await db.commit()
await db.refresh(dispositivo)
return DispositivoResponse(
id=dispositivo.id,
vehiculo_id=dispositivo.vehiculo_id,
tipo=dispositivo.tipo,
identificador=dispositivo.identificador,
nombre=dispositivo.nombre,
marca=dispositivo.marca,
modelo=dispositivo.modelo,
numero_serie=dispositivo.numero_serie,
telefono_sim=dispositivo.telefono_sim,
operador_sim=dispositivo.operador_sim,
iccid=dispositivo.iccid,
imei=dispositivo.imei,
protocolo=dispositivo.protocolo,
ultimo_contacto=dispositivo.ultimo_contacto,
bateria=dispositivo.bateria,
señal_gsm=dispositivo.señal_gsm,
satelites=dispositivo.satelites,
intervalo_reporte=dispositivo.intervalo_reporte,
configuracion=dispositivo.configuracion,
firmware_version=dispositivo.firmware_version,
activo=dispositivo.activo,
conectado=dispositivo.conectado,
notas=dispositivo.notas,
esta_online=dispositivo.esta_online,
creado_en=dispositivo.creado_en,
actualizado_en=dispositivo.actualizado_en,
)
@router.put("/{dispositivo_id}", response_model=DispositivoResponse)
async def actualizar_dispositivo(
dispositivo_id: int,
dispositivo_data: DispositivoUpdate,
db: AsyncSession = Depends(get_db),
current_user: Usuario = Depends(get_current_user),
):
"""
Actualiza un dispositivo.
Args:
dispositivo_id: ID del dispositivo.
dispositivo_data: Datos a actualizar.
Returns:
Dispositivo actualizado.
"""
result = await db.execute(
select(Dispositivo).where(Dispositivo.id == dispositivo_id)
)
dispositivo = result.scalar_one_or_none()
if not dispositivo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Dispositivo con id {dispositivo_id} no encontrado",
)
update_data = dispositivo_data.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(dispositivo, field, value)
await db.commit()
await db.refresh(dispositivo)
return DispositivoResponse(
id=dispositivo.id,
vehiculo_id=dispositivo.vehiculo_id,
tipo=dispositivo.tipo,
identificador=dispositivo.identificador,
nombre=dispositivo.nombre,
marca=dispositivo.marca,
modelo=dispositivo.modelo,
numero_serie=dispositivo.numero_serie,
telefono_sim=dispositivo.telefono_sim,
operador_sim=dispositivo.operador_sim,
iccid=dispositivo.iccid,
imei=dispositivo.imei,
protocolo=dispositivo.protocolo,
ultimo_contacto=dispositivo.ultimo_contacto,
bateria=dispositivo.bateria,
señal_gsm=dispositivo.señal_gsm,
satelites=dispositivo.satelites,
intervalo_reporte=dispositivo.intervalo_reporte,
configuracion=dispositivo.configuracion,
firmware_version=dispositivo.firmware_version,
activo=dispositivo.activo,
conectado=dispositivo.conectado,
notas=dispositivo.notas,
esta_online=dispositivo.esta_online,
creado_en=dispositivo.creado_en,
actualizado_en=dispositivo.actualizado_en,
)
@router.delete("/{dispositivo_id}", status_code=status.HTTP_204_NO_CONTENT)
async def eliminar_dispositivo(
dispositivo_id: int,
db: AsyncSession = Depends(get_db),
current_user: Usuario = Depends(get_current_user),
):
"""
Elimina un dispositivo (soft delete - desactiva).
Args:
dispositivo_id: ID del dispositivo.
"""
result = await db.execute(
select(Dispositivo).where(Dispositivo.id == dispositivo_id)
)
dispositivo = result.scalar_one_or_none()
if not dispositivo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Dispositivo con id {dispositivo_id} no encontrado",
)
dispositivo.activo = False
await db.commit()
@router.get("/por-identificador/{identificador}", response_model=DispositivoResponse)
async def obtener_dispositivo_por_identificador(
identificador: str,
db: AsyncSession = Depends(get_db),
):
"""
Obtiene un dispositivo por su identificador.
Este endpoint no requiere autenticación para
facilitar la búsqueda desde dispositivos.
Args:
identificador: Identificador del dispositivo.
Returns:
Dispositivo encontrado.
"""
result = await db.execute(
select(Dispositivo).where(Dispositivo.identificador == identificador)
)
dispositivo = result.scalar_one_or_none()
if not dispositivo:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Dispositivo con identificador {identificador} no encontrado",
)
return DispositivoResponse(
id=dispositivo.id,
vehiculo_id=dispositivo.vehiculo_id,
tipo=dispositivo.tipo,
identificador=dispositivo.identificador,
nombre=dispositivo.nombre,
marca=dispositivo.marca,
modelo=dispositivo.modelo,
numero_serie=dispositivo.numero_serie,
telefono_sim=dispositivo.telefono_sim,
operador_sim=dispositivo.operador_sim,
iccid=dispositivo.iccid,
imei=dispositivo.imei,
protocolo=dispositivo.protocolo,
ultimo_contacto=dispositivo.ultimo_contacto,
bateria=dispositivo.bateria,
señal_gsm=dispositivo.señal_gsm,
satelites=dispositivo.satelites,
intervalo_reporte=dispositivo.intervalo_reporte,
configuracion=dispositivo.configuracion,
firmware_version=dispositivo.firmware_version,
activo=dispositivo.activo,
conectado=dispositivo.conectado,
notas=dispositivo.notas,
esta_online=dispositivo.esta_online,
creado_en=dispositivo.creado_en,
actualizado_en=dispositivo.actualizado_en,
)