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:
FlotillasGPS Developer
2026-01-21 08:18:00 +00:00
commit 51d78bacf4
248 changed files with 50171 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
"""
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}')>"