Co-authored-by: MENDOZA BALLARDO GAEL RICARDO <gael-meb123@users.noreply.github.com>

version final final ya enserio la final del proyecto :)
This commit is contained in:
shinra32
2026-05-23 08:42:27 -06:00
parent 92f570294a
commit 56c51378b8
10 changed files with 464 additions and 289 deletions

View File

@@ -3,11 +3,13 @@ Endpoints de administración — Solo accesibles para usuarios con role='admin'.
Operan directamente contra Supabase (RLS bypaseado por service_role).
"""
import traceback
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from app.core.deps import get_current_user, require_role
from app.core.supabase_client import supabase_admin
from app.services import notifications
from app.schemas.admin import (
AdminUser,
AdminUserCreate,
@@ -213,10 +215,30 @@ def update_route(route_id: str, body: AdminRouteUpdate):
payload = body.model_dump(exclude_none=True)
if not payload:
raise HTTPException(400, "Sin cambios")
try:
old_res = supabase_admin.table("routes").select("truck_id").eq("id", route_id).maybe_single().execute()
old_truck_id = old_res.data.get("truck_id") if old_res.data else None
except Exception:
old_truck_id = None
try:
supabase_admin.table("routes").update(payload).eq("id", route_id).execute()
except Exception as e:
raise HTTPException(400, f"Error al actualizar la ruta: {e}")
if body.truck_id is not None and old_truck_id != body.truck_id:
try:
notifications.send_to_topic(
f"topic_{route_id}",
{
"title": "Ruta reasignada 🚛",
"body": f"Tu recolección ha sido reasignada a la unidad #{body.truck_id}. El servicio se reanudará en breve.",
}
)
except Exception as e:
print(f"Error al enviar alerta ciudadana por reasignación: {e}")
res = (
supabase_admin.table("routes")
.select(_ROUTE_COLS)
@@ -270,13 +292,50 @@ def update_unit(unit_id: int, body: AdminUnitUpdate):
supabase_admin.table("units").update(payload).eq("id", unit_id).execute()
except Exception as e:
raise HTTPException(400, f"Error al actualizar la unidad: {e}")
res = (
supabase_admin.table("units")
.select("id, plate, status")
.eq("id", unit_id)
.maybe_single()
.execute()
)
# ── ALERTA EN VIVO: Notificar a ciudadanos si la unidad se inhabilita o va a taller ──
if body.status in ["inactive", "maintenance"]:
try:
routes_res = supabase_admin.table("routes").select("id").eq("truck_id", unit_id).execute()
for route in (routes_res.data or []):
route_id = route["id"]
if route_id == "RUTA-01":
msg_title = "Aviso de reprogramación ⚠️"
msg_body = "El camión de tu ruta ha presentado una falla. El servicio matutino se suspende y tu recolección se retomará en la tarde. Por favor, resguarda tus residuos."
else:
msg_title = "Aviso sobre tu recolección ⚠️"
msg_body = "El camión de tu ruta ha sido enviado a taller o inhabilitado. El servicio podría sufrir retrasos. Trabajamos para reasignar la ruta."
notifications.send_to_topic(
f"topic_{route_id}",
{
"title": msg_title,
"body": msg_body,
}
)
except Exception as e:
print(f"Error al enviar alerta ciudadana por falla de unidad: {e}")
# ── Salvavidas Anti-Desconexión para el Hackathon ──
try:
res = (
supabase_admin.table("units")
.select("id, plate, status")
.eq("id", unit_id)
.maybe_single()
.execute()
)
except Exception as e:
print(f"Reintentando lectura por micro-corte de red en Supabase: {e}")
res = (
supabase_admin.table("units")
.select("id, plate, status")
.eq("id", unit_id)
.maybe_single()
.execute()
)
if not res.data:
raise HTTPException(404, "Unidad no encontrada")
return AdminUnit(**res.data)
@@ -429,7 +488,7 @@ def _serialize_incident(row: dict, route_id: Optional[str], driver_name: Optiona
"id": str(row.get("id")),
"unit_id": row.get("unit_id"),
"route_id": route_id,
"type": row.get("category"),
"type": row.get("type"),
"description": row.get("description"),
"driver_name": driver_name,
"status": row.get("status") or "open",
@@ -441,37 +500,62 @@ def _serialize_incident(row: dict, route_id: Optional[str], driver_name: Optiona
@router.get("/units/{unit_id}/incidents")
def list_unit_incidents(unit_id: int):
"""Reportes ciudadanos asociados a esta unidad, más reciente primero."""
# Verifica que la unidad exista (devuelve 404 si no)
unit_res = (
supabase_admin.table("units")
.select("id")
.eq("id", unit_id)
.maybe_single()
.execute()
)
if not unit_res.data:
raise HTTPException(404, "Unidad no encontrada")
print(f"[admin] GET /admin/units/{unit_id}/incidents")
# Verifica que la unidad exista. Usamos limit(1) (no maybe_single)
# porque maybe_single() lanza APIError cuando no hay filas en supabase-py 2.x.
try:
unit_res = (
supabase_admin.table("units")
.select("id")
.eq("id", unit_id)
.limit(1)
.execute()
)
except Exception as e:
traceback.print_exc()
raise HTTPException(500, f"verificar unidad: {type(e).__name__}: {e}")
if not (unit_res.data or []):
raise HTTPException(404, f"Unidad {unit_id} no existe en la base de datos")
try:
res = (
supabase_admin.table("incidents")
.select("id, unit_id, user_id, category, description, status, photo_url, created_at")
.select("id, unit_id, user_id, type, description, status, photo_url, created_at")
.eq("unit_id", unit_id)
.order("created_at", desc=True)
.execute()
)
rows = res.data or []
except Exception as e:
raise HTTPException(500, f"Error al listar incidencias: {e}")
traceback.print_exc()
if "Could not find the table" in str(e):
print("⚠️ ADVERTENCIA: La tabla 'incidents' no existe en Supabase.")
rows = []
else:
raise HTTPException(500, f"listar incidencias: {type(e).__name__}: {e}")
print(f"[admin] incidents para unit {unit_id}: {len(rows)} filas")
rows = res.data or []
route_id = _route_for_unit(unit_id)
# Hidratación route_id y driver_name (no debe romper la respuesta).
try:
route_id = _route_for_unit(unit_id)
except Exception as e:
traceback.print_exc()
route_id = None
# Pre-cargar el mapa de nombres una sola vez
users_res = supabase_admin.table("users").select("id, name").execute()
users_map: dict[str, dict] = {
str(u["id"]): {"name": u.get("name")} for u in (users_res.data or [])
}
driver_name = _driver_for_unit(unit_id, users_map)
try:
users_res = supabase_admin.table("users").select("id, name").execute()
users_map: dict[str, dict] = {
str(u["id"]): {"name": u.get("name")} for u in (users_res.data or [])
}
except Exception as e:
traceback.print_exc()
users_map = {}
try:
driver_name = _driver_for_unit(unit_id, users_map)
except Exception as e:
traceback.print_exc()
driver_name = None
return [_serialize_incident(r, route_id, driver_name) for r in rows]
@@ -496,20 +580,23 @@ def create_unit_incident(
description = description or category
# La unidad debe existir
unit_res = (
supabase_admin.table("units")
.select("id")
.eq("id", unit_id)
.maybe_single()
.execute()
)
if not unit_res.data:
raise HTTPException(404, "Unidad no encontrada")
try:
unit_res = (
supabase_admin.table("units")
.select("id")
.eq("id", unit_id)
.limit(1)
.execute()
)
except Exception as e:
raise HTTPException(500, f"Error al verificar la unidad: {e}")
if not (unit_res.data or []):
raise HTTPException(404, f"Unidad {unit_id} no existe en la base de datos")
payload = {
"user_id": current_user["user_id"],
"unit_id": unit_id,
"category": category,
"type": category,
"description": description,
}
try: