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:
362
backend/app/api/v1/dispositivos.py
Normal file
362
backend/app/api/v1/dispositivos.py
Normal file
@@ -0,0 +1,362 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user