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.
131 lines
4.7 KiB
Python
131 lines
4.7 KiB
Python
"""
|
|
Modelo de Vehículo para gestión de la flota.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import (
|
|
Boolean,
|
|
DateTime,
|
|
Float,
|
|
ForeignKey,
|
|
Integer,
|
|
String,
|
|
Text,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.core.database import Base
|
|
from app.models.base import TimestampMixin
|
|
|
|
|
|
class Vehiculo(Base, TimestampMixin):
|
|
"""Modelo de vehículo de la flota."""
|
|
|
|
__tablename__ = "vehiculos"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
|
|
# Identificación
|
|
nombre: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
placa: Mapped[str] = mapped_column(String(20), unique=True, nullable=False, index=True)
|
|
vin: Mapped[str | None] = mapped_column(String(17), unique=True, nullable=True) # Vehicle Identification Number
|
|
numero_economico: Mapped[str | None] = mapped_column(String(50), nullable=True) # Número interno
|
|
|
|
# Características del vehículo
|
|
marca: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
modelo: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
año: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
color: Mapped[str | None] = mapped_column(String(30), nullable=True)
|
|
tipo: Mapped[str | None] = mapped_column(String(50), nullable=True) # Sedan, SUV, Camión, etc.
|
|
|
|
# Capacidades
|
|
capacidad_carga_kg: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
capacidad_pasajeros: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
capacidad_combustible_litros: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
tipo_combustible: Mapped[str | None] = mapped_column(String(20), nullable=True) # Gasolina, Diesel, Eléctrico
|
|
|
|
# Odómetro
|
|
odometro_inicial: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
|
|
odometro_actual: Mapped[float] = mapped_column(Float, default=0.0, nullable=False)
|
|
|
|
# Visualización
|
|
icono: Mapped[str | None] = mapped_column(String(50), nullable=True) # Nombre del icono en el mapa
|
|
color_marcador: Mapped[str] = mapped_column(String(7), default="#3B82F6", nullable=False) # Hex color
|
|
|
|
# Relaciones
|
|
conductor_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("conductores.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
index=True,
|
|
)
|
|
grupo_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("grupos_vehiculos.id", ondelete="SET NULL"),
|
|
nullable=True,
|
|
index=True,
|
|
)
|
|
|
|
# Estado
|
|
activo: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
en_servicio: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
notas: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
# Última ubicación conocida (para consultas rápidas)
|
|
ultima_lat: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
ultima_lng: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
ultima_velocidad: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
ultimo_rumbo: Mapped[float | None] = mapped_column(Float, nullable=True)
|
|
ultima_ubicacion_tiempo: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
motor_encendido: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
|
|
|
# Relaciones ORM
|
|
conductor: Mapped["Conductor | None"] = relationship(
|
|
"Conductor",
|
|
back_populates="vehiculos",
|
|
lazy="selectin",
|
|
)
|
|
grupo: Mapped["GrupoVehiculos | None"] = relationship(
|
|
"GrupoVehiculos",
|
|
back_populates="vehiculos",
|
|
lazy="selectin",
|
|
)
|
|
dispositivos: Mapped[list["Dispositivo"]] = relationship(
|
|
"Dispositivo",
|
|
back_populates="vehiculo",
|
|
lazy="selectin",
|
|
cascade="all, delete-orphan",
|
|
)
|
|
viajes: Mapped[list["Viaje"]] = relationship(
|
|
"Viaje",
|
|
back_populates="vehiculo",
|
|
lazy="dynamic",
|
|
)
|
|
alertas: Mapped[list["Alerta"]] = relationship(
|
|
"Alerta",
|
|
back_populates="vehiculo",
|
|
lazy="dynamic",
|
|
)
|
|
cargas_combustible: Mapped[list["CargaCombustible"]] = relationship(
|
|
"CargaCombustible",
|
|
back_populates="vehiculo",
|
|
lazy="dynamic",
|
|
)
|
|
mantenimientos: Mapped[list["Mantenimiento"]] = relationship(
|
|
"Mantenimiento",
|
|
back_populates="vehiculo",
|
|
lazy="dynamic",
|
|
)
|
|
camaras: Mapped[list["Camara"]] = relationship(
|
|
"Camara",
|
|
back_populates="vehiculo",
|
|
lazy="selectin",
|
|
)
|
|
|
|
@property
|
|
def distancia_recorrida(self) -> float:
|
|
"""Calcula la distancia total recorrida."""
|
|
return self.odometro_actual - self.odometro_inicial
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Vehiculo(id={self.id}, placa='{self.placa}', nombre='{self.nombre}')>"
|