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,264 @@
"""
Schemas Pydantic para Cámara, Grabación y Evento de Video.
"""
from datetime import datetime
from typing import List, Optional
from pydantic import Field
from app.schemas.base import BaseSchema, TimestampSchema
# ============================================================================
# Schemas de Cámara
# ============================================================================
class CamaraBase(BaseSchema):
"""Schema base de cámara."""
nombre: str = Field(..., min_length=2, max_length=100)
posicion: str = Field(default="frontal", max_length=50)
tipo: str = Field(default="ip", max_length=50)
class CamaraCreate(CamaraBase):
"""Schema para crear cámara."""
vehiculo_id: int
marca: Optional[str] = Field(None, max_length=50)
modelo: Optional[str] = Field(None, max_length=50)
numero_serie: Optional[str] = Field(None, max_length=100)
resolucion: Optional[str] = Field(None, max_length=20)
url_stream: Optional[str] = Field(None, max_length=500)
puerto: Optional[int] = Field(None, ge=1, le=65535)
protocolo: str = Field(default="rtsp", max_length=20)
usuario: Optional[str] = Field(None, max_length=100)
password: Optional[str] = Field(None, max_length=100) # Se encriptará
mediamtx_path: Optional[str] = Field(None, max_length=100)
grabacion_continua: bool = False
grabacion_evento: bool = True
duracion_pre_evento: int = Field(default=10, ge=0, le=60)
duracion_post_evento: int = Field(default=20, ge=0, le=120)
deteccion_colision: bool = False
deteccion_distraccion: bool = False
deteccion_fatiga: bool = False
deteccion_cambio_carril: bool = False
notas: Optional[str] = None
class CamaraUpdate(BaseSchema):
"""Schema para actualizar cámara."""
nombre: Optional[str] = Field(None, min_length=2, max_length=100)
posicion: Optional[str] = Field(None, max_length=50)
tipo: Optional[str] = Field(None, max_length=50)
marca: Optional[str] = Field(None, max_length=50)
modelo: Optional[str] = Field(None, max_length=50)
numero_serie: Optional[str] = Field(None, max_length=100)
resolucion: Optional[str] = Field(None, max_length=20)
url_stream: Optional[str] = Field(None, max_length=500)
puerto: Optional[int] = Field(None, ge=1, le=65535)
protocolo: Optional[str] = Field(None, max_length=20)
usuario: Optional[str] = Field(None, max_length=100)
password: Optional[str] = Field(None, max_length=100)
mediamtx_path: Optional[str] = Field(None, max_length=100)
grabacion_continua: Optional[bool] = None
grabacion_evento: Optional[bool] = None
duracion_pre_evento: Optional[int] = Field(None, ge=0, le=60)
duracion_post_evento: Optional[int] = Field(None, ge=0, le=120)
deteccion_colision: Optional[bool] = None
deteccion_distraccion: Optional[bool] = None
deteccion_fatiga: Optional[bool] = None
deteccion_cambio_carril: Optional[bool] = None
activa: Optional[bool] = None
notas: Optional[str] = None
class CamaraResponse(CamaraBase, TimestampSchema):
"""Schema de respuesta de cámara."""
id: int
vehiculo_id: int
marca: Optional[str] = None
modelo: Optional[str] = None
numero_serie: Optional[str] = None
resolucion: Optional[str] = None
url_stream: Optional[str] = None
puerto: Optional[int] = None
protocolo: str
usuario: Optional[str] = None
# password no se expone
mediamtx_path: Optional[str] = None
estado: str
activa: bool
ultima_conexion: Optional[datetime] = None
grabacion_continua: bool
grabacion_evento: bool
duracion_pre_evento: int
duracion_post_evento: int
deteccion_colision: bool
deteccion_distraccion: bool
deteccion_fatiga: bool
deteccion_cambio_carril: bool
notas: Optional[str] = None
class CamaraConVehiculo(CamaraResponse):
"""Schema de cámara con información del vehículo."""
vehiculo_nombre: Optional[str] = None
vehiculo_placa: Optional[str] = None
class CamaraStreamURL(BaseSchema):
"""Schema con URLs de streaming de una cámara."""
camara_id: int
camara_nombre: str
rtsp_url: Optional[str] = None
hls_url: Optional[str] = None
webrtc_url: Optional[str] = None
estado: str
# ============================================================================
# Schemas de Grabación
# ============================================================================
class GrabacionBase(BaseSchema):
"""Schema base de grabación."""
camara_id: int
vehiculo_id: int
inicio_tiempo: datetime
tipo: str = Field(default="continua", max_length=50)
class GrabacionCreate(GrabacionBase):
"""Schema para crear registro de grabación."""
archivo_url: str = Field(..., max_length=500)
archivo_nombre: str = Field(..., max_length=255)
formato: str = Field(default="mp4", max_length=10)
class GrabacionResponse(GrabacionBase, TimestampSchema):
"""Schema de respuesta de grabación."""
id: int
fin_tiempo: Optional[datetime] = None
duracion_segundos: Optional[int] = None
archivo_url: str
archivo_nombre: str
tamaño_mb: Optional[float] = None
formato: str
resolucion: Optional[str] = None
evento_video_id: Optional[int] = None
lat: Optional[float] = None
lng: Optional[float] = None
estado: str
thumbnail_url: Optional[str] = None
notas: Optional[str] = None
# Calculado
duracion_formateada: str
class GrabacionResumen(BaseSchema):
"""Schema resumido de grabación."""
id: int
camara_id: int
vehiculo_id: int
inicio_tiempo: datetime
duracion_formateada: str
tipo: str
thumbnail_url: Optional[str] = None
# ============================================================================
# Schemas de Evento de Video
# ============================================================================
class EventoVideoBase(BaseSchema):
"""Schema base de evento de video."""
camara_id: int
vehiculo_id: int
tipo: str = Field(..., max_length=50)
severidad: str = Field(default="media", pattern="^(baja|media|alta|critica)$")
tiempo: datetime
class EventoVideoCreate(EventoVideoBase):
"""Schema para crear evento de video."""
lat: Optional[float] = Field(None, ge=-90, le=90)
lng: Optional[float] = Field(None, ge=-180, le=180)
velocidad: Optional[float] = Field(None, ge=0)
descripcion: Optional[str] = None
confianza: Optional[float] = Field(None, ge=0, le=100)
datos_extra: Optional[str] = None
snapshot_url: Optional[str] = Field(None, max_length=500)
clip_url: Optional[str] = Field(None, max_length=500)
clip_duracion: Optional[int] = Field(None, ge=0)
class EventoVideoUpdate(BaseSchema):
"""Schema para actualizar evento de video."""
revisado: Optional[bool] = None
notas_revision: Optional[str] = None
falso_positivo: Optional[bool] = None
class EventoVideoResponse(EventoVideoBase, TimestampSchema):
"""Schema de respuesta de evento de video."""
id: int
lat: Optional[float] = None
lng: Optional[float] = None
velocidad: Optional[float] = None
descripcion: Optional[str] = None
confianza: Optional[float] = None
datos_extra: Optional[str] = None
revisado: bool
revisado_por_id: Optional[int] = None
revisado_en: Optional[datetime] = None
notas_revision: Optional[str] = None
falso_positivo: bool
snapshot_url: Optional[str] = None
clip_url: Optional[str] = None
clip_duracion: Optional[int] = None
class EventoVideoConRelaciones(EventoVideoResponse):
"""Schema con información de cámara y vehículo."""
camara_nombre: Optional[str] = None
vehiculo_nombre: Optional[str] = None
vehiculo_placa: Optional[str] = None
class EventoVideoResumen(BaseSchema):
"""Schema resumido de evento de video."""
id: int
tipo: str
severidad: str
tiempo: datetime
vehiculo_nombre: str
camara_nombre: str
revisado: bool
falso_positivo: bool
snapshot_url: Optional[str] = None
class TiposEventoVideoResponse(BaseSchema):
"""Schema con tipos de eventos de video disponibles."""
tipos: List[dict] # [{codigo, nombre, severidad}]