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,160 @@
"""
Schemas Pydantic para Geocerca.
"""
from typing import List, Optional
from pydantic import Field, field_validator
from app.schemas.base import BaseSchema, TimestampSchema
class GeocercaBase(BaseSchema):
"""Schema base de geocerca."""
nombre: str = Field(..., min_length=2, max_length=100)
descripcion: Optional[str] = None
tipo: str = Field(default="circular", pattern="^(circular|poligono)$")
color: str = Field(default="#3B82F6", pattern=r"^#[0-9A-Fa-f]{6}$")
opacidad: float = Field(default=0.3, ge=0, le=1)
color_borde: str = Field(default="#1D4ED8", pattern=r"^#[0-9A-Fa-f]{6}$")
categoria: Optional[str] = Field(None, max_length=50)
class GeocercaCircularCreate(GeocercaBase):
"""Schema para crear geocerca circular."""
tipo: str = "circular"
centro_lat: float = Field(..., ge=-90, le=90)
centro_lng: float = Field(..., ge=-180, le=180)
radio_metros: float = Field(..., gt=0, le=100000)
# Configuración de alertas
alerta_entrada: bool = True
alerta_salida: bool = True
velocidad_maxima: Optional[float] = Field(None, ge=0)
# Horario (opcional, JSON)
horario_json: Optional[str] = None
# Vehículos asignados (opcional, vacío = todos)
vehiculos_ids: Optional[List[int]] = None
class GeocercaPoligonoCreate(GeocercaBase):
"""Schema para crear geocerca poligonal."""
tipo: str = "poligono"
coordenadas: List[List[float]] # [[lat, lng], [lat, lng], ...]
# Configuración de alertas
alerta_entrada: bool = True
alerta_salida: bool = True
velocidad_maxima: Optional[float] = Field(None, ge=0)
# Horario (opcional, JSON)
horario_json: Optional[str] = None
# Vehículos asignados (opcional, vacío = todos)
vehiculos_ids: Optional[List[int]] = None
@field_validator("coordenadas")
@classmethod
def validate_coordenadas(cls, v: List[List[float]]) -> List[List[float]]:
if len(v) < 3:
raise ValueError("Un polígono debe tener al menos 3 puntos")
for coord in v:
if len(coord) != 2:
raise ValueError("Cada coordenada debe tener [lat, lng]")
if not (-90 <= coord[0] <= 90):
raise ValueError("Latitud debe estar entre -90 y 90")
if not (-180 <= coord[1] <= 180):
raise ValueError("Longitud debe estar entre -180 y 180")
return v
class GeocercaUpdate(BaseSchema):
"""Schema para actualizar geocerca."""
nombre: Optional[str] = Field(None, min_length=2, max_length=100)
descripcion: Optional[str] = None
color: Optional[str] = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$")
opacidad: Optional[float] = Field(None, ge=0, le=1)
color_borde: Optional[str] = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$")
categoria: Optional[str] = Field(None, max_length=50)
# Para circular
centro_lat: Optional[float] = Field(None, ge=-90, le=90)
centro_lng: Optional[float] = Field(None, ge=-180, le=180)
radio_metros: Optional[float] = Field(None, gt=0, le=100000)
# Para polígono
coordenadas: Optional[List[List[float]]] = None
# Configuración
alerta_entrada: Optional[bool] = None
alerta_salida: Optional[bool] = None
velocidad_maxima: Optional[float] = Field(None, ge=0)
horario_json: Optional[str] = None
activa: Optional[bool] = None
class GeocercaResponse(GeocercaBase, TimestampSchema):
"""Schema de respuesta de geocerca."""
id: int
centro_lat: Optional[float] = None
centro_lng: Optional[float] = None
radio_metros: Optional[float] = None
coordenadas_json: Optional[str] = None
alerta_entrada: bool
alerta_salida: bool
velocidad_maxima: Optional[float] = None
horario_json: Optional[str] = None
activa: bool
# Calculado
aplica_todos_vehiculos: bool
class GeocercaConVehiculos(GeocercaResponse):
"""Schema de geocerca con lista de vehículos asignados."""
vehiculos_asignados: List["VehiculoResumen"] = []
class GeocercaGeoJSON(BaseSchema):
"""Schema de geocerca en formato GeoJSON."""
type: str = "Feature"
geometry: dict
properties: dict
class AsignarVehiculosRequest(BaseSchema):
"""Schema para asignar vehículos a una geocerca."""
vehiculos_ids: List[int]
reemplazar: bool = False # True = reemplaza todos, False = agrega a existentes
class VerificarPuntoRequest(BaseSchema):
"""Schema para verificar si un punto está dentro de una geocerca."""
lat: float = Field(..., ge=-90, le=90)
lng: float = Field(..., ge=-180, le=180)
class VerificarPuntoResponse(BaseSchema):
"""Schema de respuesta de verificación de punto."""
dentro: bool
geocerca_id: int
geocerca_nombre: str
distancia_metros: Optional[float] = None # Distancia al borde si está fuera
# Import fix
from app.schemas.vehiculo import VehiculoResumen # noqa: E402
GeocercaConVehiculos.model_rebuild()