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.
363 lines
12 KiB
Python
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,
|
|
)
|