""" Modelo de Viaje para registrar trayectos de vehículos. """ from datetime import datetime from sqlalchemy import ( DateTime, Float, ForeignKey, Index, Integer, String, Text, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base from app.models.base import TimestampMixin class Viaje(Base, TimestampMixin): """ Modelo de viaje/trayecto de un vehículo. Un viaje se define desde que el vehículo inicia movimiento hasta que se detiene por un período prolongado. """ __tablename__ = "viajes" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) # Relaciones vehiculo_id: Mapped[int] = mapped_column( ForeignKey("vehiculos.id", ondelete="CASCADE"), nullable=False, index=True, ) conductor_id: Mapped[int | None] = mapped_column( ForeignKey("conductores.id", ondelete="SET NULL"), nullable=True, index=True, ) # Tiempo inicio_tiempo: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) fin_tiempo: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) # Ubicación de inicio inicio_lat: Mapped[float] = mapped_column(Float, nullable=False) inicio_lng: Mapped[float] = mapped_column(Float, nullable=False) inicio_direccion: Mapped[str | None] = mapped_column(String(255), nullable=True) # Ubicación de fin fin_lat: Mapped[float | None] = mapped_column(Float, nullable=True) fin_lng: Mapped[float | None] = mapped_column(Float, nullable=True) fin_direccion: Mapped[str | None] = mapped_column(String(255), nullable=True) # Estadísticas de distancia distancia_km: Mapped[float | None] = mapped_column(Float, nullable=True) # Estadísticas de tiempo duracion_segundos: Mapped[int | None] = mapped_column(Integer, nullable=True) tiempo_movimiento_segundos: Mapped[int | None] = mapped_column(Integer, nullable=True) tiempo_parado_segundos: Mapped[int | None] = mapped_column(Integer, nullable=True) # Estadísticas de velocidad velocidad_promedio: Mapped[float | None] = mapped_column(Float, nullable=True) # km/h velocidad_maxima: Mapped[float | None] = mapped_column(Float, nullable=True) # km/h # Combustible combustible_usado: Mapped[float | None] = mapped_column(Float, nullable=True) # litros rendimiento: Mapped[float | None] = mapped_column(Float, nullable=True) # km/litro # Odómetro odometro_inicio: Mapped[float | None] = mapped_column(Float, nullable=True) odometro_fin: Mapped[float | None] = mapped_column(Float, nullable=True) # Estado estado: Mapped[str] = mapped_column( String(20), default="en_curso", nullable=False, ) # en_curso, completado, cancelado # Notas proposito: Mapped[str | None] = mapped_column(String(100), nullable=True) # Trabajo, personal, etc. notas: Mapped[str | None] = mapped_column(Text, nullable=True) # Número de puntos GPS registrados puntos_gps: Mapped[int] = mapped_column(Integer, default=0, nullable=False) # Relaciones ORM vehiculo: Mapped["Vehiculo"] = relationship( "Vehiculo", back_populates="viajes", lazy="selectin", ) conductor: Mapped["Conductor | None"] = relationship( "Conductor", back_populates="viajes", lazy="selectin", ) paradas: Mapped[list["Parada"]] = relationship( "Parada", back_populates="viaje", lazy="selectin", cascade="all, delete-orphan", ) # Índices __table_args__ = ( Index("idx_viajes_vehiculo_inicio", "vehiculo_id", "inicio_tiempo"), Index("idx_viajes_estado", "estado"), ) @property def duracion_formateada(self) -> str: """Retorna la duración en formato legible (ej: 2h 30m).""" if not self.duracion_segundos: return "N/A" horas = self.duracion_segundos // 3600 minutos = (self.duracion_segundos % 3600) // 60 if horas > 0: return f"{horas}h {minutos}m" return f"{minutos}m" @property def en_curso(self) -> bool: """Verifica si el viaje está en curso.""" return self.estado == "en_curso" def __repr__(self) -> str: return f""