simulacion de estados y flujo de notificacion, modificacion de estilos en todas las vistas

This commit is contained in:
shinra32
2026-05-23 07:08:49 -06:00
parent ca076607c7
commit 92f570294a
43 changed files with 4335 additions and 2035 deletions

View File

@@ -6,7 +6,7 @@ Operan directamente contra Supabase (RLS bypaseado por service_role).
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from app.core.deps import require_role
from app.core.deps import get_current_user, require_role
from app.core.supabase_client import supabase_admin
from app.schemas.admin import (
AdminUser,
@@ -375,3 +375,156 @@ def delete_driver(driver_id: str):
except Exception as e:
raise HTTPException(400, f"Error al borrar el conductor: {e}")
return None
# ── Incidents por unidad ──────────────────────────────────────────────────────
_INCIDENT_CATEGORIES = {
"derrame",
"dano_propiedad",
"conducta",
"no_recoleccion",
"otro",
}
def _route_for_unit(unit_id: int) -> Optional[str]:
"""Devuelve el route_id que actualmente tiene asignada la unidad
(vía routes.truck_id), o None si no está asignada a ninguna ruta."""
try:
res = (
supabase_admin.table("routes")
.select("id")
.eq("truck_id", unit_id)
.limit(1)
.execute()
)
rows = res.data or []
return rows[0]["id"] if rows else None
except Exception:
return None
def _driver_for_unit(unit_id: int, users_map: dict[str, dict]) -> Optional[str]:
"""Devuelve el nombre del chofer asignado a la unidad (drivers.unit_id)."""
try:
res = (
supabase_admin.table("drivers")
.select("user_id")
.eq("unit_id", unit_id)
.limit(1)
.execute()
)
rows = res.data or []
if not rows:
return None
user_id = str(rows[0].get("user_id"))
return (users_map.get(user_id) or {}).get("name")
except Exception:
return None
def _serialize_incident(row: dict, route_id: Optional[str], driver_name: Optional[str]) -> dict:
"""Da la forma que espera el cliente Flutter (admin_incident.dart)."""
return {
"id": str(row.get("id")),
"unit_id": row.get("unit_id"),
"route_id": route_id,
"type": row.get("category"),
"description": row.get("description"),
"driver_name": driver_name,
"status": row.get("status") or "open",
"photo_url": row.get("photo_url"),
"created_at": row.get("created_at") and str(row["created_at"]),
}
@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")
try:
res = (
supabase_admin.table("incidents")
.select("id, unit_id, user_id, category, description, status, photo_url, created_at")
.eq("unit_id", unit_id)
.order("created_at", desc=True)
.execute()
)
except Exception as e:
raise HTTPException(500, f"Error al listar incidencias: {e}")
rows = res.data or []
route_id = _route_for_unit(unit_id)
# 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)
return [_serialize_incident(r, route_id, driver_name) for r in rows]
@router.post("/units/{unit_id}/incidents", status_code=201)
def create_unit_incident(
unit_id: int,
body: dict,
current_user: dict = Depends(get_current_user),
):
"""Crea una incidencia para esta unidad. El admin queda como reporter."""
category = (body.get("type") or body.get("category") or "").strip()
description = (body.get("description") or "").strip()
if category not in _INCIDENT_CATEGORIES:
raise HTTPException(
400,
f"Categoría inválida. Use una de: {sorted(_INCIDENT_CATEGORIES)}",
)
if len(description) < 3:
# La tabla requiere description NOT NULL y un mínimo razonable.
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")
payload = {
"user_id": current_user["user_id"],
"unit_id": unit_id,
"category": category,
"description": description,
}
try:
res = supabase_admin.table("incidents").insert(payload).execute()
except Exception as e:
raise HTTPException(500, f"Error al crear la incidencia: {e}")
row = (res.data or [None])[0]
if not row:
raise HTTPException(500, "Supabase no devolvió la fila creada")
route_id = _route_for_unit(unit_id)
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)
return _serialize_incident(row, route_id, driver_name)