Files
hackathon-innovaflow5.0-cdf…/claude (1).md
shinra32 c91b6e2091 Co-authored-by: MENDOZA BALLARDO GAEL RICARDO <gael-meb123@users.noreply.github.com>
Co-authored-by: Azareth-Tr <Azareth-Tr@users.noreply.github.com>
Co-authored-by: eddgranados12 <eddgranados12@users.noreply.github.com>

implementacion de login, vistas, correcion de errores en vista registro, domicilios
2026-05-22 23:07:24 -06:00

19 KiB
Raw Blame History

try { final response = await dio.post('/auth/register', data: {...}); } on DioException catch (e) { // Aquí puedes ver el JSON real que envía tu backend en e.response?.data print('Error del backend: ${e.response?.data}'); }

Contexto del Proyecto — Sistema de Recolección Inteligente y Privada de Residuos

Documento para compartir con asistentes de IA (Claude, ChatGPT, etc.)
Última actualización: Mayo 2026
Equipo: 4 personas, hackathon, stack principal Flutter + FastAPI


1. ¿Qué estamos construyendo?

Una app móvil (Flutter) que notifica a los ciudadanos cuándo llegará el camión recolector a su domicilio sin revelar la ubicación del camión en tiempo real ni mostrar el mapa completo de la ruta. Incluye educación sobre separación de residuos mediante una mascota de IA y gestión de cambios operativos (fallas mecánicas, reasignaciones de rutas). Tres roles: ciudadano, chofer, admin.

El problema que resuelve

La gente no sabe cuándo pasará el camión → saca la basura demasiado temprano/tarde → problemas de salud pública. La solución obvia (GPS en vivo del camión) es un riesgo de seguridad y uso inadecuado. Nuestra app balancea información útil con privacidad operativa.

Alcance del MVP

  • 7 colonias de Celaya, Gto. mapeadas a 6 rutas (datos provistos en JSON).
  • Notificaciones push en 3 momentos: ruta inicia, camión cerca (~15 min), servicio finalizado.
  • Validación de domicilio con OCR de recibo (luz/agua) que se borra tras extraer la dirección.
  • Mascota IA interactiva + guía offline de separación de residuos.
  • Feedback ciudadano + quiz post-recolección.
  • Panel admin con mapa (única vista que ve coordenadas) y gestión de reasignaciones.

2. Reglas innegociables (Privacidad por Diseño)

Estas son las prohibiciones del reto convertidas en requisitos de código. Cualquier feature que las viole se rechaza:

  1. NUNCA se devuelven coordenadas del camión al ciudadano. Solo texto: "Llega en ~15 min" / "7:207:35 p.m.".
  2. NUNCA hay un mapa con el camión moviéndose en la vista ciudadana (prohibido el route tracker).
  3. Visión de túnel: el ciudadano solo ve su ruta asignada. Cero visibilidad de colonias vecinas u otros usuarios (anti-snooping).
  4. La ubicación del camión solo existe en el panel admin (para troubleshooting).
  5. Mensajería preventiva: textos que desalientan sacar basura fuera de horario o perseguir la unidad.
  6. Quejas hacia la unidad no exponen la identidad del chofer (solo "unidad 101"; el admin sí ve quién la conduce).
  7. Los recibos (validación de domicilio) no se almacenan crudos: se procesan, se valida, se guarda solo verified=true y se borra la imagen.

Implementación técnica del túnel:

  • Backend: Row Level Security (RLS) de Supabase — el ciudadano físicamente no puede leer route_positions ni domicilios ajenos aunque evada la API.
  • Frontend: notificaciones FCM por topic de ruta (topic_RUTA-01) — cada ciudadano se suscribe solo al topic de su routeId.

3. Stack tecnológico (y por qué)

Capa Tecnología Razón
Frontend móvil Flutter + Riverpod (estado) + dio (HTTP) + go_router Multiplataforma iOS/Android con un solo código. Riverpod evita polling excesivo.
Backend / API Python + FastAPI REST async, rápida de escribir, separa lógica de negocio de datos.
Base de datos PostgreSQL (vía Supabase); PostGIS opcional El mapeo colonia→ruta es directo (JSON), así que no necesitamos point-in-polygon. PostGIS solo si quieren validar por polígono en el futuro.
Auth + RBAC Supabase Auth (JWT) + Row Level Security (RLS) RLS aplica el túnel a nivel de BD, no solo en código — aunque alguien evada la API, la BD niega el acceso.
Almacenamiento Supabase Storage Para subir recibo temporalmente. Se borra tras validación.
Simulación APScheduler (FastAPI cron job) Avanza positionId de cada ruta (1→8) y dispara notificaciones. No hace falta cálculo geoespacial.
Push Firebase Cloud Messaging (FCM) Asíncrono, ligero, integración nativa con Flutter.
Mascota IA API de Claude/Gemini con system prompt sobre separación El chat educativo. Guía estática (JSON local) funciona offline.
OCR validación Tesseract o modelo de visión en backend Extrae dirección del recibo, compara, descarta imagen.
Deploy Cloud Run / Render (backend scale-to-zero) + Supabase Escala a cero en valles de inactividad.

4. Datos provistos (la base real del proyecto)

Nos entregaron 3 archivos JSON que definen el modelo y simplifican la implementación:

rutas.json (15 rutas, usamos 6)

  • Cada ruta tiene un routeId (ej. "RUTA-01"), truckId (101115), y 8 posiciones fijas (positionId 1→8) con lat/lng, velocidad y timestamp.
  • Todas salen y regresan al Relleno Sanitario (20.5111, -100.9037) — posición 1 = posición 8.
  • Velocidad = 0 en posición 5 (punto más lejano / vuelta).
  • Estas coordenadas solo se usan en el panel admin — nunca se mandan al ciudadano.

notificaciones.json (motor de eventos)

Define los 3 eventos que NO se calculan por distancia, sino por cambio de positionId:

Evento Se dispara cuando Mensaje (resumen)
ROUTE_START positionId pasa de 1 → 2 Salió del relleno rumbo a tu sector
TRUCK_PROXIMITY positionId llega a 4 A menos de 15 min; saca tus bolsas
ROUTE_COMPLETED positionId llega a 8 Servicio del día finalizado

colonias-rutas.json (el puente colonia → ruta)

Mapea cada colonia a un routeId + horario. Esto es el "túnel": ciudadano → colonia → routeId → solo eventos de ESA ruta.

Alcance del MVP (7 colonias / 6 rutas):

Colonia routeId Turno
Zona Centro RUTA-01 Matutino
Las Arboledas RUTA-01 Matutino
San Juanico RUTA-03 Matutino
Los Olivos RUTA-04 Matutino
Rancho Seco RUTA-05 Vespertino ← única vespertina; úsenla para demostrar reasignación
Las Insurgentes RUTA-12 Matutino
Trojes RUTA-13 Matutino

En el registro, el ciudadano elige su colonia de un dropdown (no geocodificación). Eso le asigna su routeId y resuelve el túnel sin PostGIS.


5. Modelo de datos (jerarquía: Usuario → Domicilio → Colonia → Ruta)

users            (id, email/phone, role: 'citizen'|'driver'|'admin')
addresses        (id, user_id, label, calle, colonia, route_id,
                  verified BOOL, verified_method, verified_at)
colonias         (id, nombre, route_id, horario_estimado, turno)
routes           (id 'RUTA-01'..'RUTA-13', name, truck_id, 
                  turno: 'matutino'|'vespertino',
                  status: 'pendiente'|'en_ruta'|'completada'|'diferida'|'reasignada',
                  current_position_id INT)   -- el "avance" simulado vive aquí (1-8)
route_positions  (route_id, position_id, lat, lng, speed, ts)  
                 -- de rutas.json; ⚠️ SOLO panel admin (RLS)
units            (id 101.., plate, status)        -- 1:1 con driver y con route
drivers          (id, user_id, unit_id)           -- cada chofer = 1 unidad
collection_events(id, route_id, address_id, status, collected_at)
feedback         (id, user_id, address_id, type, target_unit_id,
                  message, rating, created_at)     -- target = unidad, NO chofer
notifications    (id, user_id, route_id, type, payload, sent_at)
route_changes    (id, route_id, from_unit, to_unit, reason, created_at)

Cambio clave: el estado del camión NO es una coordenada calculada en vivo, sino el campo current_position_id (18). Las coordenadas de route_positions solo las consume el panel admin.


6. Cómo funciona la simulación (el corazón del sistema)

La simulación NO calcula distancias ni ETA por geolocalización. Es un índice que avanza + disparadores de eventos.

El cron job (APScheduler en FastAPI)

# El "estado" de cada ruta es solo en qué positionId va (1-8).
estado = { "RUTA-01": 1, "RUTA-03": 1, "RUTA-04": 1,
           "RUTA-05": 1, "RUTA-12": 1, "RUTA-13": 1 }

def tick():                       # corre cada N segundos (APScheduler)
    for route_id, pos in estado.items():
        if pos < 8:
            nueva = pos + 1
            estado[route_id] = nueva          # = routes.current_position_id en BD
            disparar_si_aplica(route_id, pos, nueva)

def disparar_si_aplica(route_id, antes, ahora):
    # Lee notificaciones.json y dispara según el cambio de positionId
    if antes == 1 and ahora == 2: push(route_id, "ROUTE_START")
    elif ahora == 4:              push(route_id, "TRUCK_PROXIMITY")
    elif ahora == 8:              push(route_id, "ROUTE_COMPLETED")

El túnel vía FCM topics

# La push solo va a quienes están en esa ruta (no broadcast).
def push(route_id, evento):
    payload = NOTIFICACIONES[evento]["pushPayload"]   # de notificaciones.json
    fcm.send_to_topic(f"topic_{route_id}", payload)    # ej. "topic_RUTA-01"
# El ciudadano se suscribe SOLO al topic de su routeId → nunca recibe de otra ruta.
# El payload solo lleva texto (title/body); JAMÁS lat/lng.

ETA sin coordenadas (endpoint ciudadano)

# GET /eta?address_id=123  (ciudadano)
route = get_route_for_address(address_id)   # derivado de colonia
pos = route.current_position_id

if pos < 4:      mensaje = "El camión va en camino a tu sector"
elif pos == 4:   mensaje = "Llega en aproximadamente 15 minutos"
elif pos < 8:    mensaje = "Está atendiendo tu zona; saca tus bolsas"
else:            mensaje = "Servicio del día finalizado"

# Devuelve SOLO texto:
return {"mensaje": mensaje, "status": route.status}
# NUNCA coordenadas.

RLS (el túnel a nivel de BD)

-- El ciudadano SOLO ve sus domicilios (refuerzo del túnel):
CREATE POLICY citizen_own_addresses ON addresses
  FOR SELECT USING (auth.uid() = user_id);

-- route_positions (coordenadas): sin policy para citizen = sin acceso. Solo admin:
CREATE POLICY admin_route_positions ON route_positions
  FOR SELECT USING (
    (SELECT role FROM users WHERE id = auth.uid()) = 'admin'
  );

7. Lógica de turnos y reasignación

De las notas del equipo:

  • Una ruta es matutina O vespertina, nunca ambas.
  • Ruta matutina que no se completó → se difiere al día siguiente + notifica.
  • Ruta vespertina que la unidad no puede atender → la reemplaza una unidad de mañana (ya libre) + notifica. (Rancho Seco / RUTA-05 es el caso de demo.)
  • Cada cambio escribe en route_changes y dispara FCM con evento reasignacion/retraso al topic de la ruta.

8. Reparto del equipo (4 personas)

Persona Bloque Responsabilidad
P1 (Líder) Setup + Backend & Seguridad Inicializar Flutter/backend, arquitectura API REST, JWT, RBAC/RLS, schema, validación de domicilio (OCR + privacidad).
P2 Simulación + Notificaciones + Datos Carga de los 3 JSON, simulación por positionId (cron), motor de eventos/FCM, lógica de turnos/reasignación, endpoint de ETA por texto.
P3 Frontend Ciudadano (Flutter) Registro, alta/validación de domicilios (dropdown colonia), vista ETA, suscripción FCM al topic de su ruta, buzón de retroalimentación, quiz de satisfacción.
P4 Frontend Admin + Chofer + Mascota IA Paneles admin (mapa de posiciones, única vista con coordenadas) y chofer (marcar avance, reportar falla), mascota interactiva (LLM), guía de separación offline (JSON local).

Pair programming: VS Code Live Share. Parejas sugeridas: P1↔P2 (backend/datos) y P3↔P4 (Flutter).


9. Estructura de archivos del monorepo

ONLINESHACK/                      <- raíz del repo (git aquí)
├── .gitignore                    <- raíz (cubre Flutter + Python)
├── README.md
├── PLAN.md                       <- el checklist del proyecto
├── claude.md                     <- este archivo
├── recolecta_app/                <- Flutter
│   ├── lib/
│   │   ├── core/                 (theme, network/dio_client, auth, constants)
│   │   ├── features/             (auth, addresses, eta, notifications,
│   │   │                          separation_guide, feedback, admin, driver)
│   │   └── shared/               (widgets reutilizables)
│   ├── assets/
│   │   └── .env                  <- (opcional) config pública: API_BASE_URL, anon key
│   ├── pubspec.yaml
│   └── ...
└── backend/                      <- FastAPI
    ├── app/
    │   ├── api/                  (routers: auth, addresses, routes, eta, feedback, admin)
    │   ├── core/                 (config, security/jwt, deps)
    │   ├── schemas/              (DTOs Pydantic)
    │   ├── services/             (simulation, notifications, reassignment, ocr)
    │   ├── data/                 <- los 3 JSON provistos
    │   │   ├── colonias-rutas.json
    │   │   ├── notificaciones.json
    │   │   └── rutas.json
    │   └── db/                   (session, seed.py)
    ├── secrets/
    │   └── firebase-adminsdk.json   <- ignorado
    ├── .env                      <- secretos reales (ignorado)
    ├── .env.example              <- plantilla (versionada)
    ├── requirements.txt
    └── main.py

10. Secretos y .env

Backend .env (secretos reales, nunca al cliente):

SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=eyJ...         # llave pública; puede ir al cliente
SUPABASE_SERVICE_ROLE_KEY=eyJ... # llave secreta; SOLO backend
JWT_SECRET=xxx
FIREBASE_CREDENTIALS_PATH=./secrets/firebase-adminsdk.json
SIMULATION_TICK_SECONDS=10

Flutter .env (SOLO config pública, viaja en el .apk/.ipa):

API_BASE_URL=http://10.0.2.2:8000   # 10.0.2.2 = localhost desde emulador Android
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_ANON_KEY=eyJ...            # llave anon: pública por diseño

Principio: cualquier cosa que meta en Flutter es extraíble → no es secreto. Los secretos reales (service_role, admin SDK) viven únicamente en el backend.


11. Decisiones arquitectónicas clave (y por qué)

  1. Supabase RLS en vez de RBAC solo en código: aunque alguien evada la API (bug, exploit), la BD niega el acceso. Es "defense in depth" para Privacidad por Diseño.

  2. Simulación por positionId en vez de cálculo geoespacial: los datos provistos ya definen 8 posiciones fijas con timestamps. Avanzar un índice es trivial, y los eventos salen de notificaciones.json sin cálculos. Evita complejidad innecesaria.

  3. FCM por topic de ruta en vez de push individual: escala mejor (un mensaje llega a todos los ciudadanos de la ruta). El túnel se resuelve en la suscripción: cada quien solo está en topic_{su_routeId}.

  4. Dropdown de colonia en vez de geocodificación: los datos ya mapean colonia→ruta. Geocodificar sería redundante y costaría llamadas a API. El dropdown es más rápido, más barato y suficiente para el MVP.

  5. OCR + borrado del recibo: cumple "Privacidad por Diseño" literalmente — la imagen nunca persiste. Solo queda verified=true + método + timestamp.

  6. Guía offline + chat IA: la guía estática (JSON embebido en Flutter) garantiza que funcione sin red. El chat con LLM es la capa extra cuando hay conexión.


12. Endpoints clave del backend

Ciudadano (autenticado, rol citizen)

  • POST /auth/register — registro con email/teléfono
  • POST /auth/login — devuelve JWT
  • GET /colonias — lista para el dropdown (nombre + horario)
  • POST /addresses — dar de alta domicilio (calle + colonia elegida → deriva route_id)
  • POST /addresses/{id}/verify — sube recibo, OCR extrae dirección, compara, borra imagen, guarda verified=true
  • GET /eta?address_id=X — devuelve {mensaje, status} SIN coordenadas
  • POST /feedback — queja/rating hacia la unidad (NO chofer)

Chofer (rol driver)

  • GET /routes/mine — su ruta/unidad asignada
  • POST /collections/{route_id} — marcar recolección en un domicilio
  • POST /incidents — reportar falla mecánica (dispara reasignación)

Admin (rol admin)

  • GET /routes — todas las rutas + current_position_id
  • GET /routes/{id}/positions — las 8 coordenadas de route_positions (⚠️ única vista con lat/lng)
  • POST /routes/{id}/reassign — reasignar ruta a otra unidad + dispara FCM
  • GET /drivers — lista de choferes + su unidad

13. Checklist de validación de privacidad (antes de cada demo)

Antes de mostrar cualquier feature, revisar:

  • ¿El endpoint del ciudadano devuelve coordenadas? → rechazado
  • ¿Hay un mapa con el camión moviéndose en vista ciudadana? → rechazado
  • ¿El ciudadano puede pedir datos de otra ruta u otro usuario? → debe fallar (RLS)
  • ¿Las quejas exponen el nombre/ID del chofer? → solo target_unit_id
  • ¿El recibo del ciudadano quedó almacenado en BD o storage? → debe estar borrado
  • ¿Los mensajes desalientan sacar basura fuera de horario? → validar textos

14. Comandos útiles de arranque

Flutter

cd recolecta_app
flutter pub get
flutter run   # Android: conectar emulador o dispositivo

Backend

cd backend
python -m venv .venv && source .venv/bin/activate  # (Windows: .venv\Scripts\activate)
pip install -r requirements.txt
# Crear .env copiando .env.example y llenando valores
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

Simulación (una vez el backend esté corriendo)

El cron de APScheduler arranca automáticamente con el backend si está configurado. Para forzar un tick manual desde consola Python:

from app.services.simulation import tick
tick()  # avanza current_position_id de cada ruta y dispara eventos

15. Para la demo (guion de 57 min)

  1. Registro + validación de domicilio (mostrar que el recibo se borra).
  2. Correr la simulación acelerada (tick cada 5 segundos): llega ROUTE_STARTTRUCK_PROXIMITYROUTE_COMPLETED. Resaltar: sin mapa, sin ubicación, solo texto.
  3. Mascota IA enseñando a separar residuos + guía offline.
  4. Falla de unidad en Rancho Seco (RUTA-05 vespertina) → reasignación a unidad matutina → notificación al ciudadano.
  5. Panel admin con el mapa de las 8 posiciones (contraste: solo admin ve coordenadas).
  6. Quiz post-recolección + queja a la unidad (sin exponer chofer).
  7. Diapositiva de arquitectura + cómo cada decisión cumple Privacidad por Diseño.

Fin del contexto. Este documento debe ir al repo como claude.md para que el equipo lo comparta con sus asistentes de IA.