199 lines
7.8 KiB
Python
199 lines
7.8 KiB
Python
"""
|
||
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
|