""" 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()