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