Co-authored-by: MENDOZA BALLARDO GAEL RICARDO <gael-meb123@users.noreply.github.com> configuracion para firebase
367 lines
18 KiB
Markdown
367 lines
18 KiB
Markdown
# 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:20–7: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` (101–115), 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)
|
||
|
||
```sql
|
||
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` (1–8). 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)
|
||
```python
|
||
# 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
|
||
```python
|
||
# 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)
|
||
```python
|
||
# 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)
|
||
```sql
|
||
-- 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):**
|
||
```bash
|
||
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):**
|
||
```bash
|
||
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
|
||
```bash
|
||
cd recolecta_app
|
||
flutter pub get
|
||
flutter run # Android: conectar emulador o dispositivo
|
||
```
|
||
|
||
### Backend
|
||
```bash
|
||
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:
|
||
```python
|
||
from app.services.simulation import tick
|
||
tick() # avanza current_position_id de cada ruta y dispara eventos
|
||
```
|
||
|
||
---
|
||
|
||
## 15. Para la demo (guion de 5–7 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_START` → `TRUCK_PROXIMITY` → `ROUTE_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.**
|