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,156 @@
"""
Modelo de Ubicación para almacenar datos GPS.
Utiliza TimescaleDB hypertable para almacenamiento eficiente
de series temporales de ubicaciones.
"""
from datetime import datetime
from sqlalchemy import (
Boolean,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
event,
)
from sqlalchemy.orm import Mapped, mapped_column
from app.core.database import Base
class Ubicacion(Base):
"""
Modelo de ubicación GPS.
Esta tabla está diseñada para ser una hypertable de TimescaleDB,
optimizada para almacenar millones de registros de ubicación.
"""
__tablename__ = "ubicaciones"
# Clave primaria compuesta: tiempo + vehiculo_id
tiempo: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
primary_key=True,
nullable=False,
)
vehiculo_id: Mapped[int] = mapped_column(
ForeignKey("vehiculos.id", ondelete="CASCADE"),
primary_key=True,
nullable=False,
)
# Coordenadas
lat: Mapped[float] = mapped_column(Float, nullable=False)
lng: Mapped[float] = mapped_column(Float, nullable=False)
altitud: Mapped[float | None] = mapped_column(Float, nullable=True) # metros sobre nivel del mar
# Movimiento
velocidad: Mapped[float | None] = mapped_column(Float, nullable=True) # km/h
rumbo: Mapped[float | None] = mapped_column(Float, nullable=True) # grados (0-360)
# Precisión
precision: Mapped[float | None] = mapped_column(Float, nullable=True) # metros
hdop: Mapped[float | None] = mapped_column(Float, nullable=True) # Horizontal Dilution of Precision
# Información GPS
satelites: Mapped[int | None] = mapped_column(Integer, nullable=True)
fuente: Mapped[str] = mapped_column(
String(20),
default="gps",
nullable=False,
) # gps, network, fused, meshtastic
# Estado del dispositivo
bateria_dispositivo: Mapped[float | None] = mapped_column(Float, nullable=True) # porcentaje
bateria_vehiculo: Mapped[float | None] = mapped_column(Float, nullable=True) # voltaje
# Estado del vehículo
motor_encendido: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
odometro: Mapped[float | None] = mapped_column(Float, nullable=True) # km
# Sensores OBD (opcional)
rpm: Mapped[int | None] = mapped_column(Integer, nullable=True)
temperatura_motor: Mapped[float | None] = mapped_column(Float, nullable=True) # Celsius
nivel_combustible: Mapped[float | None] = mapped_column(Float, nullable=True) # porcentaje
# Índices para consultas frecuentes
__table_args__ = (
# Índice espacial aproximado para consultas por área
Index("idx_ubicaciones_coords", "lat", "lng"),
# Índice para consultas por vehículo en un rango de tiempo
Index("idx_ubicaciones_vehiculo_tiempo", "vehiculo_id", "tiempo"),
# Índice para encontrar paradas (velocidad 0)
Index("idx_ubicaciones_velocidad", "velocidad"),
# Configuración para TimescaleDB
{
"timescaledb_hypertable": {
"time_column_name": "tiempo",
"chunk_time_interval": "1 day",
}
},
)
def __repr__(self) -> str:
return f"<Ubicacion(vehiculo_id={self.vehiculo_id}, tiempo={self.tiempo}, lat={self.lat}, lng={self.lng})>"
def to_geojson(self) -> dict:
"""Convierte la ubicación a formato GeoJSON Point."""
return {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [self.lng, self.lat],
},
"properties": {
"vehiculo_id": self.vehiculo_id,
"tiempo": self.tiempo.isoformat(),
"velocidad": self.velocidad,
"rumbo": self.rumbo,
"motor_encendido": self.motor_encendido,
},
}
# Función para crear la hypertable en TimescaleDB
# Se ejecuta después de crear la tabla
def create_hypertable(target, connection, **kw):
"""Crea la hypertable de TimescaleDB después de crear la tabla."""
# Esta función se ejecutará solo si TimescaleDB está instalado
try:
connection.execute(
"""
SELECT create_hypertable(
'ubicaciones',
'tiempo',
if_not_exists => TRUE,
chunk_time_interval => INTERVAL '1 day'
);
"""
)
# Habilitar compresión después de 7 días
connection.execute(
"""
ALTER TABLE ubicaciones SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'vehiculo_id'
);
"""
)
# Política de compresión automática
connection.execute(
"""
SELECT add_compression_policy('ubicaciones', INTERVAL '7 days', if_not_exists => TRUE);
"""
)
except Exception:
# Si TimescaleDB no está instalado, continuar sin hypertable
pass
# Registrar evento para crear hypertable
event.listen(Ubicacion.__table__, "after_create", create_hypertable)