Files
hackathon-acapulquitos-boys…/server/app/data/repositories/ruta_repository.py

199 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Capa de Datos — Supabase PostgreSQL con caché
"""
from datetime import datetime, timedelta
from typing import Optional
from app.db.database import get_db
from app.domain.entities.ruta import (
Coordenada, EstadoCamion, ETAResult,
NotificationPreferences, PuntoRuta, Ruta, TruckStatus,
)
class SupabaseRutaRepository:
def __init__(self):
self.db = get_db()
# ── Rutas y puntos ────────────────────────────────────────────────
def obtener_ruta(self, route_id: str) -> Optional[Ruta]:
try:
ruta_data = self.db.table("rutas").select("*").eq("id", route_id).execute()
if not ruta_data.data:
return None
ruta_row = ruta_data.data[0]
puntos_data = self.db.table("puntos_ruta").select("*").eq("ruta_id", route_id).order("orden").execute()
puntos = [
PuntoRuta(
orden=p["orden"],
nombre=p["nombre"],
coordenada=Coordenada(p["lat"], p["lng"]),
tiempo_estimado_min=p["tiempo_estimado_min"],
)
for p in puntos_data.data
]
return Ruta(
id=ruta_row["id"],
nombre=ruta_row["nombre"],
puntos=puntos,
turno=ruta_row["turno"]
)
except Exception as e:
print(f"Error obtener_ruta: {e}")
return None
def obtener_ruta_por_address(self, address_id: int) -> Optional[Ruta]:
try:
addr_data = self.db.table("addresses").select("route_id").eq("id", address_id).execute()
if not addr_data.data:
return None
return self.obtener_ruta(addr_data.data[0]["route_id"])
except Exception as e:
print(f"Error obtener_ruta_por_address: {e}")
return None
# ── truck_status ──────────────────────────────────────────
def obtener_truck_status(self, route_id: str) -> Optional[TruckStatus]:
try:
ts_data = self.db.table("truck_status").select("*").eq("route_id", route_id).execute()
if not ts_data.data:
return None
row = ts_data.data[0]
return TruckStatus(
route_id=row["route_id"],
current_position_id=row["current_position_id"],
last_update=datetime.fromisoformat(row["last_update"].replace("Z", "+00:00")),
status=EstadoCamion(row["status"]),
)
except Exception as e:
print(f"Error obtener_truck_status: {e}")
return None
def guardar_truck_status(self, ts: TruckStatus) -> None:
try:
self.db.table("truck_status").upsert({
"route_id": ts.route_id,
"current_position_id": ts.current_position_id,
"last_update": ts.last_update.isoformat(),
"status": ts.status.value,
}).execute()
# Invalidar caché
from app.core.cache import invalidate_route_cache
import asyncio
asyncio.create_task(invalidate_route_cache(ts.route_id))
except Exception as e:
print(f"Error guardar_truck_status: {e}")
# ── Preferencias de notificación ─────────────────────────────────
def obtener_preferencias(self, user_id: str) -> NotificationPreferences:
try:
prefs_data = self.db.table("notification_preferences").select("*").eq("user_id", user_id).execute()
if not prefs_data.data:
return NotificationPreferences(user_id=user_id)
row = prefs_data.data[0]
return NotificationPreferences(
user_id=user_id,
notify_proximity=row["notify_proximity"],
notify_breakdown=row["notify_breakdown"],
notify_delay=row["notify_delay"],
notify_route_start=row["notify_route_start"],
)
except Exception as e:
print(f"Error obtener_preferencias: {e}")
return NotificationPreferences(user_id=user_id)
def obtener_usuarios_por_ruta(self, route_id: str) -> list[dict]:
try:
users_data = self.db.table("addresses").select("id, user_id").eq("route_id", route_id).execute()
return [{"address_id": u["id"], "user_id": u["user_id"]} for u in users_data.data]
except Exception as e:
print(f"Error obtener_usuarios_por_ruta: {e}")
return []
# ── Templates de notificación ─────────────────────────────────────
def obtener_template(self, trigger_event: str) -> Optional[dict]:
try:
template_data = self.db.table("notification_templates").select("*").eq("trigger_event", trigger_event).execute()
if not template_data.data:
return None
return template_data.data[0]
except Exception as e:
print(f"Error obtener_template: {e}")
return None
# ── ETA calculado ────────────────────────────────────────────────
def calcular_eta(self, address_id: int) -> Optional[ETAResult]:
ruta = self.obtener_ruta_por_address(address_id)
if not ruta:
return None
ts = self.obtener_truck_status(ruta.id)
if not ts:
return ETAResult(
address_id=address_id, route_id=ruta.id,
status="SIN_INICIAR", eta_minutos=None,
ventana_inicio=None, ventana_fin=None,
mensaje="El camión aún no ha iniciado su ruta.",
)
pos = ts.current_position_id
puntos = {p.orden: p for p in ruta.puntos}
ultimo = ruta.puntos[-1]
actual = puntos.get(pos, ruta.puntos[0])
eta_min = max(0, ultimo.tiempo_estimado_min - actual.tiempo_estimado_min)
ahora = datetime.now()
llegada = ahora + timedelta(minutes=eta_min)
v_ini = (llegada - timedelta(minutes=7)).strftime("%I:%M %p").lstrip("0")
v_fin = (llegada + timedelta(minutes=7)).strftime("%I:%M %p").lstrip("0")
if ts.status == EstadoCamion.APROXIMANDOSE:
msg = f"El camión llegará a tu zona entre las {v_ini} y {v_fin}."
elif ts.status in (EstadoCamion.AVERIADA, EstadoCamion.RETRASADA):
msg = "El camión reportó una incidencia. Te notificaremos cuando se reanude."
v_ini = v_fin = None
else:
msg = f"El camión está en camino. Llegada estimada: {v_ini} {v_fin}."
return ETAResult(
address_id=address_id,
route_id=ruta.id,
status=ts.status.value,
eta_minutos=eta_min,
ventana_inicio=v_ini,
ventana_fin=v_fin,
mensaje=msg,
)
def guardar_notificacion(self, tipo: str, route_id: str,
address_id: int, mensaje: str,
eta_minutos: Optional[int]) -> None:
try:
self.db.table("notificaciones").insert({
"tipo": tipo,
"ruta_id": route_id,
"address_id": address_id,
"mensaje": mensaje,
"eta_minutos": eta_minutos,
"creada_en": datetime.utcnow().isoformat(),
}).execute()
except Exception as e:
print(f"Error guardar_notificacion: {e}")
# Alias para compatibilidad
SQLiteRutaRepository = SupabaseRutaRepository