Initial commit

This commit is contained in:
marianesaldana
2026-05-23 08:59:34 -06:00
commit 80dbd947e5
36446 changed files with 3729147 additions and 0 deletions

261
docs/API.md Normal file
View File

@@ -0,0 +1,261 @@
# API REST — Referencia
> Base URL: `http://10.82.68.125:8000/api/v1` · Docs interactivos: `http://localhost:8000/docs`
Todos los endpoints (excepto `/auth/*`) requieren header:
```
Authorization: Bearer <JWT_TOKEN>
```
---
## 🔐 Autenticación
### POST `/auth/register`
Crea un usuario nuevo (rol CIUDADANO por defecto).
**Request:**
```json
{ "full_name": "Juan Pérez", "email": "juan@example.com", "password": "secret123" }
```
**Response 201:**
```json
{ "access_token": "eyJ...", "token_type": "bearer", "role": "CIUDADANO" }
```
### POST `/auth/login`
**Request:**
```json
{ "email": "demo@celaya.gob.mx", "password": "Celaya2026" }
```
**Response 200:**
```json
{ "access_token": "eyJ...", "token_type": "bearer", "role": "CIUDADANO" }
```
**Response 401:** `{"detail": "Credenciales inválidas"}`
### POST `/auth/oauth`
Login/registro vía OAuth (Google, Facebook, Apple).
```json
{ "provider": "google", "oauth_id": "abc123", "email": "juan@gmail.com", "full_name": "Juan", "push_token": "..." }
```
### GET `/me`
Devuelve el usuario actual.
**Response:** `{ "id": 1, "full_name": "...", "email": "...", "role": "CIUDADANO" }`
---
## 🏠 Domicilios (`/addresses`)
### GET `/addresses/`
Lista tus domicilios.
**Response:** `[{"id":1, "label":"Casa", "street":"...", "lat":20.5, "lng":-100.8, "route_id":"RUTA-01", "is_default":true}, ...]`
### POST `/addresses/`
Crea un domicilio. Si pasas `lat`/`lng`, se asigna ruta automáticamente.
```json
{ "label": "Casa", "street": "Hidalgo 245", "colony": "Centro", "lat": 20.5215, "lng": -100.8142, "is_default": true }
```
### PATCH `/addresses/{id}`
Actualiza un domicilio. Solo campos modificados.
```json
{ "is_default": true }
```
### DELETE `/addresses/{id}` → 204
---
## ⏰ ETA (`/eta`)
### GET `/eta/address/{id}` — núcleo del sistema
**Privacy-by-design:** nunca devuelve coordenadas del camión.
**Response 200:**
```json
{
"status": "EN_CAMINO",
"message": "El camión llegará a tu zona entre las 7:20 y 7:35",
"eta_minutes": 12,
"window_start": "07:20",
"window_end": "07:35",
"progress": 45.2,
"route_name": "Zona Centro - Las Arboledas",
"passes_today": true
}
```
**Estados posibles:**
- `PROGRAMADO` — la ruta aún no empieza hoy
- `EN_CAMINO` — el camión va en ruta
- `LLEGANDO` — faltan ≤10 minutos
- `PASO` — ya pasó por la zona
- `NO_SERVICIO` — no aplica hoy
### GET `/eta/schedule/{address_id}`
Devuelve días y horario aproximado.
```json
{ "route_id":"RUTA-01", "route_name":"Zona Centro - Las Arboledas",
"days_of_week":["Lunes","Miércoles","Viernes"], "approximate_time":"06:00 - 07:40",
"truck_id": 101 }
```
### POST `/eta/rate`
Califica el servicio.
```json
{ "address_id": 1, "rating": 5, "comment": "Excelente puntualidad" }
```
---
## 📋 Reportes ciudadanos (`/reports`)
### POST `/reports/`
Crea un reporte con folio único.
```json
{ "address_id": 1, "report_type": "NO_PASO", "description": "El camión no pasó hoy" }
```
**Response 201:**
```json
{ "id":5, "folio":"MRL-20260523-F6EC92", "status":"PENDIENTE",
"report_type":"NO_PASO", "description":"...", "created_at":"...", "address_label":"Casa" }
```
**Tipos:** `NO_PASO` · `RETRASO` · `ACUMULACION` · `OTRO`
### GET `/reports/`
Tus reportes (no los de otros usuarios).
### GET `/reports/{id}`
Detalle con la etiqueta del domicilio.
---
## 👮 Staff (`/staff`) — solo EMPLEADO o ADMIN
### GET `/staff/dashboard`
Métricas motivacionales del empleado.
```json
{
"employee_name": "Carlos Hernández",
"streak_days": 19,
"punctuality_pct": 88,
"bonus_accumulated_mxn": 950,
"next_milestone_days": 11,
"next_milestone_mxn": 500,
"reports_generated": 5,
"motivation_quote": "Tu puntualidad permite que miles de familias planifiquen su día",
"rating_label": "BUENO"
}
```
### GET `/staff/schedule`
Horario preestablecido con descansos.
### GET `/staff/categories`
Las 8 categorías disponibles para reportes operativos.
### POST `/staff/operational-reports`
Crea un reporte operativo (folio `OP-...`).
```json
{ "category": "FALLA_MECANICA", "severity": "MEDIA", "description": "Frenos haciendo ruido", "route_id": "RUTA-01", "truck_id": 12 }
```
**Categorías:** `NO_ARRANQUE` · `FALLA_MECANICA` · `ACCIDENTE` · `OBSTACULO` · `TRAFICO` · `COMBUSTIBLE` · `CLIMA` · `OTRO`
### GET `/staff/operational-reports`
Lista tus reportes operativos.
---
## 🛡️ Admin (`/admin`) — solo ADMIN
### GET `/admin/stats`
KPIs globales del sistema.
```json
{
"total_ciudadanos": 185, "total_domicilios": 302, "total_reportes": 280,
"reportes_24h": 8, "promedio_calificacion": 4.07,
"reportes_por_estado": {"PENDIENTE": 84, "EN_PROCESO": 56, "RESUELTO": 112, "CERRADO": 28},
"reportes_por_tipo": {"NO_PASO": 98, "RETRASO": 84, ...},
"rutas_activas": 15
}
```
### GET `/admin/reports?status=PENDIENTE&report_type=NO_PASO`
Lista TODOS los reportes ciudadanos (no solo los tuyos). Filtros opcionales.
### PATCH `/admin/reports/{id}/status?status=EN_PROCESO`
Cambia el estado de un reporte. Estados válidos: `PENDIENTE | EN_PROCESO | RESUELTO | CERRADO`.
### GET `/admin/routes`
Lista todas las rutas con su estado actual.
### GET `/admin/users?role=CIUDADANO`
Lista usuarios con conteos de domicilios y reportes.
### PATCH `/admin/users/{id}/role?role=EMPLEADO`
Cambia el rol de un usuario. Roles: `CIUDADANO | EMPLEADO | ADMIN`.
### GET `/admin/feedback`
Últimas 50 calificaciones del servicio.
---
## 🔒 Códigos de respuesta
| Código | Significado |
|--------|-------------|
| 200 | OK |
| 201 | Creado |
| 204 | Sin contenido (DELETE exitoso) |
| 400 | Bad request (validación falló) |
| 401 | No autenticado / token inválido |
| 403 | Sin permisos para este recurso |
| 404 | Recurso no encontrado o no te pertenece |
| 500 | Error del servidor |
---
## 🧪 Ejemplos con curl
### Flujo completo: ciudadano → reporte → admin lo resuelve
```bash
BASE=http://10.82.68.125:8000/api/v1
# 1. Login ciudadano
CTOKEN=$(curl -s -X POST $BASE/auth/login -H "Content-Type: application/json" \
-d '{"email":"demo@celaya.gob.mx","password":"Celaya2026"}' | jq -r .access_token)
# 2. Listar domicilios
curl -s $BASE/addresses/ -H "Authorization: Bearer $CTOKEN" | jq
# 3. Ver ETA del domicilio 1
curl -s $BASE/eta/address/1 -H "Authorization: Bearer $CTOKEN" | jq
# 4. Crear reporte
REPORT=$(curl -s -X POST $BASE/reports/ -H "Authorization: Bearer $CTOKEN" \
-H "Content-Type: application/json" \
-d '{"address_id":1,"report_type":"NO_PASO","description":"Hoy no pasó"}')
echo $REPORT | jq
REPORT_ID=$(echo $REPORT | jq -r .id)
# 5. Login admin
ATOKEN=$(curl -s -X POST $BASE/auth/login -H "Content-Type: application/json" \
-d '{"email":"admin@celaya.gob.mx","password":"Admin2026"}' | jq -r .access_token)
# 6. Admin ve TODOS los reportes
curl -s $BASE/admin/reports -H "Authorization: Bearer $ATOKEN" | jq '. | length'
# 7. Admin cambia el estado a EN_PROCESO
curl -s -X PATCH "$BASE/admin/reports/$REPORT_ID/status?status=EN_PROCESO" \
-H "Authorization: Bearer $ATOKEN" | jq
# 8. El ciudadano vuelve a consultar y ve el cambio
curl -s $BASE/reports/$REPORT_ID -H "Authorization: Bearer $CTOKEN" | jq .status
# → "EN_PROCESO" ✓
```

318
docs/ARQUITECTURA.md Normal file
View File

@@ -0,0 +1,318 @@
# Arquitectura técnica
> Documento técnico para evaluación. Decisiones de diseño, algoritmos clave y consideraciones de privacidad/seguridad.
---
## 1. Visión general
**Mi Ruta Limpia** es una arquitectura **clienteservidor REST** con simulación de eventos en el backend para emular telemetría de la flotilla, sin requerir GPS real.
```
[App móvil RN] ↔ HTTPS/REST + JWT ↔ [FastAPI] ↔ SQLite + routes.json (mock)
```
### Por qué esta arquitectura
| Decisión | Razón |
|----------|-------|
| **FastAPI** (Python) | Validación automática con Pydantic, OpenAPI docs gratis, async-ready, productivo en hackathon |
| **SQLite** (no PostgreSQL) | Zero-config, suficiente para hackathon, migración a PG es trivial (cambiar `DATABASE_URL`) |
| **JSON mock para rutas** | Cumple el requisito de "simular eventos sin telemetría real" sin complejidad innecesaria |
| **JWT** stateless | No requiere sesión en servidor, escalable horizontalmente |
| **React Native + Expo** | Multiplataforma (iOS, Android, web) con un solo código |
| **fetch sobre axios** | Axios rompe en RN nueva arquitectura por `URL.protocol` strict mode |
---
## 2. Backend — Estructura por capas
```
app/
├── config.py ← Settings con pydantic-settings (env vars)
├── database.py ← SQLAlchemy engine + sessionmaker
├── models/ ← ORM (capa de datos)
├── schemas/ ← Pydantic (capa de validación / serialización)
├── routers/ ← Endpoints HTTP (capa de presentación)
└── services/ ← Lógica de negocio (capa de dominio)
```
**Patrón:** los routers solo orquestan. Toda la lógica real vive en `services/`. Los modelos no contienen lógica.
### Inyección de dependencias (FastAPI `Depends`)
```python
# Cada endpoint declara qué necesita:
@router.get("/eta/address/{id}")
def get_eta(
id: int,
db: Session = Depends(get_db), BD sesión
user = Depends(get_current_user), JWT validado
):
...
```
Esto permite testear cada endpoint con mocks fácilmente.
---
## 3. Modelo de datos
```
users (id, email, phone, full_name, hashed_password, role, oauth_*, push_token, is_active)
├──< addresses (id, user_id, label, street, colony, lat, lng, route_id, is_default)
│ │
│ ├──< reports (id, user_id, address_id, folio, report_type, description, status, created_at)
│ │
│ └──< service_ratings (id, user_id, address_id, rating, comment, created_at)
└──< operational_reports (id, employee_id, folio, category, severity, route_id, truck_id, status, ...)
trucks (id, unit_number, plate, model, year, status, route_id, base, odometer_km, fuel_level_pct, ...)
```
### Roles (RBAC)
- **`CIUDADANO`** — default. Solo accede a `/auth`, `/addresses` (suyos), `/eta` (suyos), `/reports` (suyos)
- **`EMPLEADO`** — accede a `/staff/*` (su propio dashboard, sus reportes operativos). NO accede a `/admin/*`
- **`ADMIN`** — accede a todo, incluyendo `/admin/*` (gestión de reportes ciudadanos y usuarios)
### Migraciones
Por simplicidad de hackathon, las migraciones se hacen con `Base.metadata.create_all()` + `ALTER TABLE` inline en `seed.py`. En producción se debe usar **Alembic**.
---
## 4. Algoritmo ETA — núcleo del sistema
### Problema
Dado un domicilio (lat, lng) y la hora actual, calcular cuándo pasará el camión **sin exponer su ubicación real**.
### Datos de entrada
[`backend/app/data/routes.json`](../backend/app/data/routes.json) — 15 rutas, cada una con 8 waypoints, cada waypoint con:
- `lat`, `lng`
- `timestamp` (template, se reinterpreta cada día)
- `speed`
### Algoritmo (`app/services/eta_service.py`)
```python
def get_eta(route_id, user_lat, user_lng):
route = load_route(route_id)
now = datetime.now(CELAYA_TZ)
# 1. Construir horario del día con base en los timestamps templates
first_t = today_at(parse(route.positions[0].timestamp))
last_t = today_at(parse(route.positions[-1].timestamp))
# 2. Determinar fase
if now < first_t: return PROGRAMADO # no ha empezado
if now > last_t: return PASO # ya terminó
# 3. Encontrar entre qué dos waypoints está el camión ahora
current_idx = find_segment(route, now)
# 4. Encontrar el waypoint más cercano al domicilio del usuario
closest_idx = argmin(haversine(user, w) for w in route.positions)
# 5. Comparar
if current_idx > closest_idx: return PASO # ya pasó
if current_idx == closest_idx: return LLEGANDO # muy cerca
# 6. Calcular ventana horaria
user_time = today_at(parse(route.positions[closest_idx].timestamp))
eta_minutes = (user_time - now).minutes
return {
'status': 'EN_CAMINO',
'message': f'Entre las {user_time - 8min} y {user_time + 8min}',
'eta_minutes': eta_minutes,
'progress': elapsed_pct(now, first_t, last_t),
}
```
### Por qué este enfoque
- **No requiere telemetría real:** los timestamps son el "schedule" diario; la posición actual se infiere de la hora
- **Privacy by design built-in:** la API jamás devuelve `lat`/`lng` del camión
- **Predecible:** el ciudadano sabe el rango horario, no un punto exacto
- **Tolerante a tráfico:** las ventanas (`±8 minutos`) absorben la variabilidad
### Geo-routing automático
Cuando un ciudadano registra un domicilio sin `route_id`, `assign_route(lat, lng)` calcula el waypoint más cercano de todas las rutas y asigna la ruta correspondiente (algoritmo: distancia Haversine en O(n) con n=15·8=120 waypoints).
---
## 5. Seguridad
### Autenticación
- **bcrypt** con cost factor 12 para hashing
- **JWT** firmado con HS256, expira en 7 días
- El token incluye `sub` (user_id) y `role`
- Los endpoints sensibles validan el token con `HTTPBearer` + dependency
### Autorización (RBAC)
Tres dependencies en `routers/deps.py`:
```python
get_current_user cualquier rol autenticado
require_staff EMPLEADO o ADMIN (devuelve 403 si no)
require_admin solo ADMIN (devuelve 403 si no)
```
Probado con curl:
```bash
# Ciudadano → /admin/stats → HTTP 403 ✓
# Empleado → /admin/reports → HTTP 403 ✓
# Empleado → /staff/dashboard → HTTP 200 ✓
```
### Pasos adicionales para producción
- [ ] Refresh tokens
- [ ] Rate limiting (slowapi)
- [ ] HTTPS obligatorio
- [ ] CORS estricto (no `*`)
- [ ] Logs auditables (sin PII)
- [ ] Rotación de `SECRET_KEY`
- [ ] 2FA opcional
---
## 6. Privacidad por Diseño
| Requisito hackathon | Cómo se cumple |
|---------------------|----------------|
| "**PROHIBIDO el seguidor de ruta activo**" | El endpoint `/eta/address/:id` **nunca** devuelve `lat`/`lng` del camión. Solo: status, mensaje, ventana horaria, progreso% |
| "**PROHIBIDO el snooping**" | El backend valida `Address.user_id == current_user.id` en cada petición. Si intentas consultar `/eta/address/999` y no te pertenece → 404 |
| "**Visión de túnel: solo tu ruta**" | El cliente solo recibe `route_name` (texto), nunca el polyline de la ruta |
| "**Mensajería preventiva**" | Banner explícito en HomeScreen: *"Por tu seguridad, no persigas al camión. ¡Saca tu basura con tiempo!"* |
### Verificación
```bash
# Login como demo (id=1)
curl -X POST .../auth/login -d '{"email":"demo@celaya.gob.mx","password":"Celaya2026"}'
# Intentar acceder a un domicilio ajeno (id=999)
curl -H "Authorization: Bearer $TOKEN" .../eta/address/999
# → HTTP 404 "Domicilio no encontrado"
```
---
## 7. Simulación de eventos
Cumple el requisito *"Dado que no tendrán acceso a la telemetría real, deberán simular el avance de la ruta"*.
### Estrategia
1. **`routes.json`** contiene 15 rutas con 8 waypoints + timestamps cada una
2. Los timestamps son **templates relativos**: se reinterpretan cada día
3. **`eta_service.py`** los proyecta a la fecha actual y calcula la posición simulada por interpolación temporal
4. **No hay cron job ni websockets** — la simulación es **stateless y on-demand**
### Ventaja
Cualquier petición al backend, en cualquier momento del día, devuelve un ETA coherente con la "hora del camión" sin necesidad de procesos en background.
### Migración a telemetría real
Para producción con GPS real, solo cambiar `eta_service.get_eta()` para que:
1. Lea la posición actual del camión desde una tabla `truck_positions` (alimentada por GPS real)
2. Compare contra el waypoint del usuario
3. Devuelva la misma estructura de respuesta — **el cliente no cambia**
---
## 8. Frontend — Arquitectura
### Routing por rol
```
LoginScreen
↓ (login exitoso)
AuthContext.user.role
├── 'CIUDADANO' → CitizenTabs (5 pestañas)
├── 'EMPLEADO' → EmployeeTabs (3 pestañas)
└── 'ADMIN' → AdminTabs (4 pestañas)
```
### Estado
- **AuthContext** global (React Context) — guarda `user` + métodos `login/logout/register`
- **AsyncStorage** persistente — guarda el JWT entre sesiones
- **Estado local por pantalla** (`useState`) — listas, modales, formularios
- **No usamos Redux/Zustand** — overkill para este alcance
### Networking
- **`fetch`** nativo en lugar de axios (compatibilidad con RN nueva arq.)
- **Interceptor de auth**: `services/api.js` añade el `Authorization: Bearer` automáticamente y limpia el token en 401
- **No hay polling agresivo**: el cliente solo refresca al pull-to-refresh o navegar
### Polyfills críticos
- `react-native-url-polyfill/auto` — reemplaza `global.URL` con la implementación WHATWG completa **antes** de que Expo cargue. Solución al bug *"URL.protocol is not implemented"* en RN 0.74
---
## 9. Decisiones de UX
### Tipografía y color
- **Color guinda institucional** `#7B1A2E` (extraído del logo del Gobierno de Celaya)
- **Acento dorado** `#C8960C` para etiquetas de sección, evocando el slogan "ES GRANDE POR TI"
- **Encabezado guinda detrás de la dynamic island** en todas las pestañas — efecto de marca premium
- Tipografía: pesos 800-900 para títulos, 600 para subtítulos, 400 para cuerpo
### Patrón de pantalla repetido
Todas las pantallas post-login siguen:
```
AppHeader (guinda + "Mi Ruta Limpia")
Hero (gradient o imagen con título grande)
[Etiqueta dorada UPPERCASE] + [Título grande sección]
Cards / contenido
```
### Microinteracciones
- Pull-to-refresh en listas
- Confirmación "ya pasó" con opción **deshacer** (persistente por día/domicilio en AsyncStorage)
- Modales bottom-sheet para formularios largos
- Toast/Alert nativo para feedback
---
## 10. Limitaciones conocidas
| Limitación | Por qué | Mitigación |
|------------|---------|------------|
| Sin push notifications reales | Requiere Firebase Cloud Messaging y certificados | El esquema `push_token` en User está listo; solo falta integrar FCM |
| Sin OAuth real | Requiere SDK de Google/Facebook/Apple | El endpoint `/auth/oauth` está implementado; solo falta integrar el SDK del lado del cliente |
| Sin PDF de reportes | Requiere `expo-print` o servicio externo | Implementamos compartir como texto formateado con `Share` nativo |
| BD no escala a millones | SQLite no es para producción | Cambiar `DATABASE_URL` a PostgreSQL — toda la app sigue funcionando |
| Simulación, no GPS real | Cumple el requisito del hackathon | Endpoint listo para reemplazar con telemetría real |
---
## 11. Sugerencias del brief que SÍ cumplimos
| Sugerencia del reto | Estado |
|--------------------|:------:|
| Consumo inteligente de APIs geográficas (geocodificación solo en registro, cálculos internos) | ✅ |
| Cálculo interno de distancias (Haversine en Python) | ✅ |
| Notificaciones asíncronas listas para FCM | ✅ (esquema preparado) |
| Arquitectura por capas (lógica/datos separados) | ✅ |
| API REST limpia | ✅ |
| Multiplataforma (iOS, Android, Web) | ✅ |
| Manejo eficiente de estado | ✅ (sin polling, refresh manual) |
| Indexación geoespacial básica | ✅ (assign_route por proximidad) |
| Autenticación JWT | ✅ |
| RBAC | ✅ (3 niveles) |
---
## 12. Métricas del proyecto
| Métrica | Valor |
|---------|------:|
| Archivos backend | 27 |
| Archivos frontend (src/) | 22 |
| Líneas de código backend | ~1,800 |
| Líneas de código frontend | ~3,500 |
| Endpoints REST | 28 |
| Modelos de datos | 6 |
| Pantallas | 14 (5 ciudadano + 3 empleado + 4 admin + 2 auth) |
| Componentes reusables | 2 (AppHeader, GoogleGIcon) |
| Datos en BD (seed_massive) | 202 usuarios · 302 domicilios · 280 reportes · 150 op-reportes · 62 camiones · 220 calificaciones |

193
docs/INSTALACION.md Normal file
View File

@@ -0,0 +1,193 @@
# Guía de instalación
> Paso a paso para correr Mi Ruta Limpia en macOS / Linux.
---
## 1. Requisitos previos
| Software | Versión | Verificar con |
|----------|---------|---------------|
| **Node.js** | 20 LTS (**no 22+**) | `node --version` |
| **Python** | 3.11 - 3.13 | `python3 --version` |
| **Xcode** (solo iOS) | 15+ | App Store |
| **Watchman** (opcional pero recomendado) | última | `brew install watchman` |
### Instalar Node 20 si tienes versión nueva
```bash
# Con nvm (recomendado)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20
nvm use 20
# O con Homebrew
brew install node@20
brew link --overwrite node@20
```
---
## 2. Backend
```bash
cd backend
# Crear entorno virtual
python3 -m venv venv
source venv/bin/activate
# Instalar dependencias
pip install -r requirements.txt
# Configurar variables de entorno
cp .env.example .env
# Edita .env si quieres cambiar SECRET_KEY (opcional para hackathon)
# Poblar BD con datos demo + masivos
python seed_massive.py
# Esto crea ~200 usuarios, 62 camiones, 280 reportes, etc.
```
### Arrancar el servidor
```bash
# Con --host 0.0.0.0 para que el simulador iOS lo alcance
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
Verifica que esté corriendo:
- Local: `curl http://localhost:8000/health`
- Docs OpenAPI: abre `http://localhost:8000/docs` en el navegador
---
## 3. Frontend
```bash
cd frontend
# Aumentar límite de file descriptors (macOS)
ulimit -n 65536
# Instalar dependencias
npm install
# Configurar IP del backend
# Edita: frontend/src/constants/config.js
# Cambia la línea:
# export const API_BASE_URL = 'http://10.82.68.125:8000/api/v1';
# por la IP de TU Mac (obténla con `ipconfig getifaddr en0`)
```
### Arrancar Metro y la app
```bash
npx expo start --clear
```
Cuando aparezca el menú:
- **`i`** → iOS Simulator
- **`a`** → Android Emulator
- **`w`** → Navegador web
- Escanea el **QR** con la app **Expo Go** (App Store / Play Store) si quieres usar tu celular físico
> ⚠️ Para celular físico, debes estar en la **misma red Wi-Fi** que la Mac.
---
## 4. Credenciales para probar
Después de `seed_massive.py`, usa cualquiera de estas:
| Rol | Email | Contraseña |
|-----|-------|-----------|
| 🧑 Ciudadano | `demo@celaya.gob.mx` | `Celaya2026` |
| 👮 Empleado | `empleado@celaya.gob.mx` | `Empleado2026` |
| 🛡️ Admin | `admin@celaya.gob.mx` | `Admin2026` |
---
## 5. Troubleshooting
### "Credenciales inválidas" pero las escribí bien
- Verifica que el backend esté corriendo con `--host 0.0.0.0`
- Verifica que la IP en `config.js` coincida con tu IP local actual
- Reinicia Metro con `npx expo start --clear`
### "EMFILE: too many open files"
```bash
ulimit -n 65536
brew install watchman
```
### "URL.protocol is not implemented"
Verifica que `index.js` (en la raíz de `frontend/`) sea:
```js
import 'react-native-url-polyfill/auto';
import 'expo/AppEntry';
```
Y que `package.json` tenga `"main": "index.js"`.
### "main has not been registered"
Causa más común: un módulo falló al cargar silenciosamente. Reinicia con:
```bash
npx expo start --clear
```
### "Cannot find module .../something.types"
Tu Node.js es muy nuevo (22+). Cambia a Node 20 LTS:
```bash
nvm use 20
rm -rf node_modules package-lock.json
npm install
```
### Backend dice "passlib bcrypt error"
```bash
pip install 'bcrypt==4.0.1'
```
### El simulador no encuentra el servidor
Confirma la IP correcta con:
```bash
ipconfig getifaddr en0
```
Y actualiza `frontend/src/constants/config.js` con esa IP.
---
## 6. Reiniciar todo desde cero
Si algo se rompe completamente:
```bash
# Frontend
cd frontend
rm -rf node_modules package-lock.json
npm install
npx expo start --clear
# Backend
cd ../backend
source venv/bin/activate
rm -f mi_ruta_limpia.db # ¡Cuidado! Borra todos los datos
python seed_massive.py
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
---
## 7. Cómo configurar para presentar
### Pre-checklist (5 min antes del pitch):
- [ ] Backend corriendo con `--host 0.0.0.0`
- [ ] Frontend corriendo con `npx expo start`
- [ ] iOS Simulator abierto en iPhone 17 Pro (Cmd+R recargado)
- [ ] Probar login con `demo@celaya.gob.mx` → debe entrar a la vista ciudadana
- [ ] Probar login con `admin@celaya.gob.mx` → debe ver el dashboard con stats
- [ ] WiFi estable
- [ ] Pantalla de Mac compartida en el proyector
### Para la demo:
Sigue el script en [`docs/PRESENTACION.md`](PRESENTACION.md).

446
docs/MANUAL_USUARIO.md Normal file
View File

@@ -0,0 +1,446 @@
# Manual de usuario
> Guía visual de Mi Ruta Limpia para los tres perfiles: ciudadano, empleado operativo y administrador.
---
## 1. Antes de empezar
### ¿Cómo se entra a la app?
1. Abre **Mi Ruta Limpia** desde el ícono en tu teléfono
2. En la pantalla de inicio de sesión, escribe tu **correo** y tu **contraseña**
3. Toca **Iniciar sesión**
> 💡 También puedes entrar con tu cuenta de **Google** o **Facebook** si las vinculaste al registrarte.
### ¿No tienes cuenta?
Toca **"¿No tienes cuenta? Regístrate"** en la pantalla de login.
Necesitarás:
- Tu nombre completo
- Un correo o teléfono
- Una contraseña segura (mínimo 6 caracteres)
### ¿Olvidaste tu contraseña?
Toca **"¿Olvidaste tu contraseña?"** y sigue las instrucciones que llegarán a tu correo.
---
## 2. Vista CIUDADANO 🧑
Después de iniciar sesión, verás 5 pestañas en la parte inferior:
```
[ Inicio ] [ Horarios ] [ 🌱 ] [ Reportes ] [ Perfil ]
```
### 2.1 Pestaña INICIO
Es la pantalla principal. Muestra:
- **Cabecera guinda** con "Mi Ruta Limpia"
- **Logo del Gobierno de Celaya** centrado
- **Selector de domicilio** (chips horizontales): toca el lugar del que quieres ver información
- **Tarjeta de ETA**: tu próxima recolección
- Mensaje con la ventana horaria (ej. *"Entre las 7:20 y 7:35"*)
- Si faltan menos de 30 min, también muestra los minutos exactos
- Barra de progreso de la ruta
- Ícono de camión
- **Botón "¿Ya pasó la basura? Sí"**: tócalo cuando confirmes que pasó
#### Al confirmar que pasó:
1. Aparece un **modal de calificación** (1-5 estrellas) con opción de comentario
2. Al enviar, la tarjeta cambia a verde con **"Ruta completada para hoy"**
3. Aparece un enlace **"↩ Deshacer confirmación"** por si te equivocaste
> ⚠️ Mensaje de seguridad: *"Por tu seguridad, no persigas al camión. ¡Saca tu basura con tiempo!"*
#### Generar reporte rápido
Más abajo hay un botón **"Generación de reportes..."** que te lleva a la pestaña Reportes.
#### Horarios de recolección
Al final ves el horario aproximado de tu domicilio (días y rango horario).
---
### 2.2 Pestaña HORARIOS
Muestra:
- **Hero guinda** con el día actual: *"Sábado 23 de mayo"*
- **Tarjeta por cada uno de tus domicilios** con:
- Etiqueta (Casa, Trabajo, etc.) y dirección
- Badge "Principal" si es tu domicilio default
- Días de la semana de recolección (Lunes · Miércoles · Viernes)
- Horario aproximado (06:00 - 08:00)
- Nombre de la ruta asignada
- **Sección "Recuerda"** con 4 buenas prácticas:
- Saca tu basura máximo 30 min antes
- Separa orgánicos, reciclables y sanitarios
- En días festivos el servicio puede modificarse
- No persigas al camión
---
### 2.3 Pestaña EDUCACIÓN (la hojita verde central)
La pestaña más rica en contenido. Tiene:
#### Hero con foto eco
Imagen borrosa de naturaleza con mensaje *"Tu basura, nuestro futuro"*
#### Por qué importa
4 tarjetas explicando el impacto:
- 🌍 **Ambiental** — separar evita metano en rellenos
- ❤️ **Salud pública** — evita fauna nociva y enfermedades
- 💰 **Económico** — Celaya ahorra hasta $1,400 MXN por tonelada reciclada
- 🏛️ **Celaya limpio** — calles limpias y vecinos saludables
#### Tipos de residuos
4 categorías que puedes tocar para ver detalle:
| Categoría | Color | Bolsa |
|-----------|-------|-------|
| 🌱 **Orgánicos** | Verde | Bolsa verde |
| ♻️ **Reciclables** | Azul | Bolsa azul/transparente |
| 🩹 **Sanitarios** | Naranja | Bolsa roja/negra |
| ⚡ **Especiales/RAEE** | Morado | Punto de acopio |
Al tocar una categoría se abre un **modal con detalle**:
- Descripción larga
- ✅ Lista de "Sí va aquí"
- ❌ Lista de "No va aquí"
- 💡 Consejo práctico
- 📊 Dato de impacto
#### Datos que impactan
4 estadísticas con números fuertes:
- 60% es orgánico
- 30% es reciclable
- 450 años tarda una botella en degradarse
- 17 árboles se salvan por tonelada de papel reciclado
#### Tips prácticos
5 consejos para implementar hoy:
1. En casa: ten 2 botes mínimo
2. Enjuaga envases primero
3. Saca a tiempo (no antes, no después)
4. No mezcles (pilas, medicamentos a puntos de acopio)
5. Composta casera si tienes patio
#### Puntos de acopio en Celaya
Lista con lugares reales para:
- 🔋 Pilas y baterías (Walmart, Soriana, Liverpool)
- 📱 Electrónicos (Office Depot, Best Buy)
- 💊 Medicamentos (SINGREM en farmacias)
- 🛢️ Aceite de cocina (Punto Limpio Municipal)
#### Preguntas frecuentes
7 preguntas plegables (toca para abrir).
#### Compromiso final
Banner guinda: *"Si todos los hogares de Celaya separaran su basura, reduciríamos el relleno sanitario en un 70%"*
---
### 2.4 Pestaña REPORTES
Para levantar quejas formales con folio oficial.
#### Pantalla principal
- **Hero guinda con foto borrosa** y mensaje motivador
- **Estadísticas rápidas**: número de pendientes, en proceso, resueltos
- **Botón "Levantar un reporte nuevo"** destacado
- **Historial filtrable**: Todos / Pendientes / En proceso / Resueltos
#### Crear un reporte (3 pasos)
1. **Domicilio afectado** (chips para elegir)
2. **Tipo de incidencia** (4 opciones):
- 🚛 El camión no pasó
- ⏰ Retraso en la ruta
- 🗑️ Acumulación de basura
- … Otro
3. **Descripción** (opcional, 500 caracteres máx)
Al enviar, recibes un **folio único** tipo `MRL-20260523-A4B5C6`.
#### Confirmación
Aparece una pantalla verde con:
- ✓ "Reporte enviado con éxito"
- Folio destacado
- Estado (PENDIENTE)
- Tipo de incidencia
- Domicilio
- Fecha
- Tu descripción
- Botón **"Compartir reporte"** que abre el menú nativo (WhatsApp, correo, copiar, etc.)
#### Ver reportes anteriores
- Toca cualquier reporte de la lista para ver el detalle
- Puedes compartirlos cuando quieras desde el detalle
---
### 2.5 Pestaña PERFIL
Para gestionar tus datos y domicilios.
#### Hero guinda con tu identidad
- Avatar circular con tu inicial
- Tu nombre y correo
#### Domicilios
- Lista de tus lugares registrados
- Cada uno con etiqueta, dirección y badge "Principal" si aplica
- **Botón ⭐** para marcar como principal
- **Botón 🗑️** para eliminar
- **Botón "Agregar"** para añadir uno nuevo
#### Agregar domicilio
Modal con:
- Etiqueta (Casa, Trabajo, Familia, Otro)
- Calle y número
- Colonia
- Coordenadas (opcional, para asignar ruta automáticamente)
#### Configuración
Tres opciones (próximamente conectadas):
- 🔔 Notificaciones
- 🛡️ Privacidad
- ❓ Ayuda y soporte
#### Cerrar sesión
Botón rojo al final.
---
## 3. Vista EMPLEADO 👮
Después de iniciar sesión con una cuenta `EMPLEADO`, verás solo **3 pestañas**:
```
[ Inicio ] [ Reportes ] [ Cuenta ]
```
> ❌ Los empleados **NO** pueden ver ni gestionar reportes ciudadanos. Solo pueden levantar reportes operativos sobre el funcionamiento del camión y la ruta.
### 3.1 Pestaña INICIO (Dashboard del empleado)
#### Hero guinda con tu identidad
- Avatar con casco
- "¡Buen turno!" y tu nombre
- Badge con tu desempeño: **EXCELENTE / MUY BUENO / BUENO**
#### Racha de puntualidad
Tarjeta naranja con:
- 🔥 Días consecutivos puntuales (ej. *"19 días seguidos"*)
- Mensaje motivador: *"¡Sigue así! No rompas la racha."*
#### KPIs
Dos tarjetas:
- 🎯 **Puntualidad mensual** (porcentaje)
- 💰 **Bono acumulado** (en pesos)
#### Próximo bono
Tarjeta amarilla destacada:
- *"Te faltan **N días** puntuales para desbloquear **+$500 MXN**"*
- Trofeo dorado
#### Tu horario
Tarjeta con:
- Nombre del turno (Turno Matutino)
- Horario completo (05:30 → 09:00)
- Bloque de ruta destacado (06:00 - 08:00)
- **Descansos preestablecidos**:
- ☕ Descanso técnico: 07:00 · 10 min
- 🧘 Pausa estiramiento: 08:00 · 5 min
- Días de la semana (Lunes a sábado) y descanso (Domingo)
- Nota motivacional sobre puntualidad
#### Mensaje motivacional del día
Tarjeta verde con una frase rotativa:
- *"Tu puntualidad permite que miles de familias planifiquen su día"*
- *"Cada salida a tiempo es un acto de servicio que transforma a Celaya"*
- *"Eres la cara del Gobierno de Celaya en cada colonia"*
#### Por qué importa tu puntualidad
4 cards explicando el impacto cívico:
- Cientos de vecinos planifican su día
- Evita basura en la calle y fauna nociva
- Hace más confiables las predicciones de la app
- Eres la imagen del Gobierno de Celaya
---
### 3.2 Pestaña REPORTES (operativos)
Para reportar incidentes con el camión o la ruta.
#### Pantalla principal
- Hero guinda con mensaje
- Botón "Nuevo reporte operativo"
- Lista de tus reportes anteriores con: folio, categoría, severidad, ruta, fecha y estado
#### Crear un reporte operativo (4 pasos)
**1. ¿Qué pasó?** Grid de 8 categorías con icono:
- 🔧 No arrancó (camión)
- ⚙️ Falla mecánica
- 🚨 Accidente vial
- 🚧 Obstáculo bloqueando ruta
- 🚦 Tráfico intenso
- ⛽ Combustible bajo
- 🌧️ Clima adverso
- ··· Otro
**2. Severidad**:
- 🟢 Baja
- 🟡 Media
- 🔴 Alta
**3. Ruta y unidad** (opcional)
- RUTA-XX
- Número de unidad
**4. Descripción** (500 caracteres)
Al enviar, recibes un **folio operativo** tipo `OP-20260523-CC35B3`.
#### Compartir reporte
Igual que los reportes ciudadanos: botón **"Compartir folio"** abre el menú nativo del sistema.
---
### 3.3 Pestaña CUENTA
Vista de perfil con:
- Tarjeta de identidad con badge "EMPLEADO OPERATIVO"
- **Tus permisos** (lista con ✓ verdes y ✗ grises):
- ✓ Ver dashboard y estadísticas (las tuyas)
- ✓ Gestionar reportes ciudadanos → **✗** (no permitido)
- ✓ Cambiar estado de incidencias → **✗** (no permitido)
- ✓ Gestionar usuarios y roles → **✗**
- **Sistema** (API conectada, BD SQLite, versión)
- Botón cerrar sesión
---
## 4. Vista ADMINISTRADOR 🛡️
Después de iniciar sesión con una cuenta `ADMIN`, verás **4 pestañas**:
```
[ Dashboard ] [ Reportes ] [ Usuarios ] [ Cuenta ]
```
### 4.1 Pestaña DASHBOARD
KPIs en vivo de toda la ciudad:
#### Encabezado
Logo de Celaya, tu nombre, badge "🛡️ Administrador"
#### Resumen general
6 tarjetas con números:
- 👥 Ciudadanos totales
- 🏠 Domicilios registrados
- 📋 Reportes totales
- ⏰ Reportes en últimas 24h
- 🚛 Rutas activas
- ⭐ Calificación promedio
#### Reportes por estado
Gráfica de barras con conteo y porcentaje:
- PENDIENTE (naranja)
- EN_PROCESO (azul)
- RESUELTO (verde)
- CERRADO (gris)
#### Reportes por tipo
Lista con conteo:
- ACUMULACION
- NO_PASO
- OTRO
- RETRASO
---
### 4.2 Pestaña REPORTES
Gestión completa de reportes ciudadanos.
#### Filtros (chips horizontales)
- Todos | Pendientes | En proceso | Resueltos | Cerrados
#### Lista de reportes
Cada tarjeta muestra:
- Folio
- Tipo (ej. "El camión no pasó")
- Badge de estado con color
- 👤 Nombre del ciudadano
- 📍 Domicilio + calle
- 🚛 Ruta asignada
- 💬 Descripción del ciudadano
- 📅 Fecha y hora
- **Botones de acción** (transición de estado):
- PENDIENTE → EN_PROCESO / CERRADO
- EN_PROCESO → RESUELTO / CERRADO
- RESUELTO → CERRADO
Al tocar un botón, el estado cambia inmediatamente y el ciudadano ve la actualización en su app.
---
### 4.3 Pestaña USUARIOS (solo ADMIN)
Para gestionar toda la base de usuarios.
#### Filtros por rol
- Todos | Ciudadanos | Empleados | Administradores
#### Lista de usuarios
Cada tarjeta muestra:
- Avatar con ícono según rol
- Nombre completo
- Email
- Badge con su rol
- 🏠 Total domicilios · 📋 Total reportes
#### Cambiar rol
Al tocar un usuario (que no sea tú mismo), aparece un modal:
- Su nombre y email
- 3 opciones de rol con su ícono y color
- Botón "Confirmar" para aplicar el cambio
- Confirmación con alerta nativa
> ⚠️ No puedes cambiar tu propio rol (medida de seguridad)
---
### 4.4 Pestaña CUENTA
Igual que la del empleado pero con badge **ADMINISTRADOR**:
- Todos los permisos en ✓ verde
- Sistema (API, BD, versión)
- Cerrar sesión
---
## 5. Recomendaciones finales
### Para ciudadanos
- 📅 Saca la basura **máximo 30 min antes** del horario indicado
- 🌱 Separa **orgánicos, reciclables y sanitarios** en bolsas distintas
- 🔋 Lleva pilas, electrónicos y medicamentos a **puntos de acopio**
- 🚫 No persigas al camión ni saques basura fuera del horario
- 📝 Si hay un problema, **levanta un reporte**: ayuda a mejorar el servicio
### Para empleados
- ⏰ Sé puntual: cada minuto cuenta para los vecinos
- 🚧 Reporta cualquier incidencia (mecánica, vial, climática) al instante
- 🤝 Eres la cara del Gobierno de Celaya — trato amable hace la diferencia
### Para administradores
- 📊 Revisa el dashboard diariamente
- ⚡ Atiende los reportes pendientes en máximo 24-48h
- 👥 Gestiona los roles con cuidado, no degradas administradores por error

190
docs/PRESENTACION.md Normal file
View File

@@ -0,0 +1,190 @@
# Guion de presentación — 5 a 7 minutos
> Script para el pitch del hackathon. Cubre los puntos clave que los jueces evalúan.
---
## ⏱️ Distribución del tiempo
| Sección | Duración | Objetivo |
|---------|----------|----------|
| 1. Problema | 30s | Sembrar el dolor del usuario |
| 2. Solución | 30s | Decir qué construimos |
| 3. Demo ciudadano | 2 min | Mostrar el flujo principal |
| 4. Demo empleado | 1 min | RBAC + motivación |
| 5. Demo admin | 1 min | Gestión y métricas |
| 6. Arquitectura técnica | 1 min | Convencer en lo técnico |
| 7. Privacy by Design | 30s | Punto innegociable del reto |
| 8. Cierre | 30s | Llamado a la acción |
---
## 🎬 Guion completo
### 1. Problema (30 segundos)
> **"En Celaya, todos hemos sacado la basura demasiado temprano… o demasiado tarde. La basura termina en la calle, atrae fauna nociva, y los vecinos se molestan. La solución obvia sería ponerle un GPS al camión, pero eso es peligroso: la gente lo persigue, expone información operativa, y rompe la privacidad."**
Mostrar imagen o slide con el problema.
---
### 2. Solución (30 segundos)
> **"Mi Ruta Limpia: una app que te dice cuándo viene el camión a tu casa, sin nunca mostrarte dónde está. Privacy by Design, multiplataforma, con folio oficial en cada reporte, y tres vistas: ciudadano, empleado operativo y administrador."**
Mostrar la pantalla principal en el simulador.
---
### 3. Demo CIUDADANO (2 minutos)
**Login:**
- *"Entro como `demo@celaya.gob.mx`."*
**Inicio:**
- *"Esta es mi pantalla principal. Veo mi domicilio asignado a la **RUTA-01 (Zona Centro)**, y el camión llegará entre las 7:20 y 7:35. Si me faltan menos de 30 minutos, me muestra los minutos exactos."*
- *"Atrás de esta sencilla pantalla hay un cálculo geo-espacial: cuando registro mi domicilio, el backend usa **distancia Haversine** para asignarme automáticamente la ruta más cercana."*
- *"Importante: NO veo dónde está el camión. Solo veo MI ventana horaria. Esto es privacy by design real."*
**Confirmar ya pasó:**
- *"Cuando el camión pasa, confirmo con esta calificación. La tarjeta se pone verde. Si me equivoqué, tengo un **botón de deshacer**."*
**Educación:**
- *"Toco la hojita y entro a la sección educativa: 4 categorías de residuos, datos de impacto, puntos de acopio reales en Celaya y un FAQ extenso. Cada categoría tiene un modal con qué SÍ y qué NO va, y un dato de impacto."*
**Reportes:**
- *"Si el camión no pasó, levanto un reporte. Recibo un **folio oficial** tipo `MRL-20260523-A4B5C6` y puedo **compartirlo por WhatsApp, correo, o lo que sea** — porque es mi comprobante."*
---
### 4. Demo EMPLEADO (1 minuto)
**Logout y login como empleado:**
- *"Cierro sesión y entro como empleado: `empleado@celaya.gob.mx`. **El navegador cambia: ahora solo tengo 3 pestañas**."*
**Dashboard del empleado:**
- *"Esta es la vista del operador del camión. Aquí está el corazón motivacional: **mi racha de puntualidad** — 19 días seguidos. **Mi bono acumulado** — $950 MXN. Me faltan 11 días para desbloquear el siguiente bono de $500."*
- *"También veo mi horario con descansos preestablecidos: 5 min de estiramiento a las 8 AM. Esto es bienestar laboral integrado."*
- *"Y arriba hay una **frase motivacional que rota cada día**: 'Tu puntualidad permite que miles de familias planifiquen su día.'"*
**Reportes operativos:**
- *"Si algo pasa en mi turno — falla mecánica, accidente, tráfico — lo reporto. Tengo **8 categorías** con iconos. Esto le permite al equipo central reaccionar inmediatamente."*
**RBAC en acción:**
- *"Y muy importante: **el empleado NO puede ver los reportes ciudadanos**. Está en su pantalla de Cuenta: hay permisos en gris bloqueados. Si intento llamar a `/admin/reports` desde mi token de empleado, el backend responde **403 Forbidden**."*
---
### 5. Demo ADMIN (1 minuto)
**Login admin:**
- *"Cierro sesión, entro como admin: `admin@celaya.gob.mx`. Ahora tengo 4 pestañas."*
**Dashboard:**
- *"KPIs en vivo de toda la ciudad: 185 ciudadanos activos, 302 domicilios, 280 reportes totales, 8 en las últimas 24h, calificación promedio 4.07/5."*
- *"Gráficas por estado y por tipo de reporte. Veo de un vistazo que hay 84 pendientes."*
**Gestión de reportes:**
- *"Aquí veo TODOS los reportes ciudadanos de la ciudad. Filtro por estado. Puedo cambiar el estado con un botón: **PENDIENTE → EN_PROCESO → RESUELTO**. Esto cierra el loop con el ciudadano, que ve la actualización en su app."*
**Gestión de usuarios:**
- *"Y solo el admin puede ver esta pestaña: 202 usuarios, filtrables por rol. Puedo promover a alguien a empleado o demovrer si es necesario."*
---
### 6. Arquitectura técnica (1 minuto)
> **"Backend en FastAPI con SQLAlchemy. JWT con RBAC de 3 niveles. SQLite para el hackathon, pero migración a PostgreSQL es trivial."**
> **"Frontend en React Native con Expo, multiplataforma — corre en iOS, Android y Web con un solo código."**
> **"La simulación de eventos es elegante: no usamos cron jobs ni websockets. Los timestamps del JSON son templates que se reinterpretan cada día, y el ETA se calcula on-demand con interpolación temporal. Cero infraestructura, máxima escalabilidad."**
> **"Tenemos 28 endpoints REST documentados en OpenAPI."**
(Opcional: mostrar `localhost:8000/docs` con Swagger)
---
### 7. Privacy by Design (30 segundos)
> **"El reto fue tajante: PROHIBIDO el seguidor activo, PROHIBIDO el snooping. Lo cumplimos así:"**
> **"Primero, el endpoint `/eta/address/:id` **nunca** devuelve coordenadas del camión. Solo: status, mensaje, ventana, progreso porcentual."**
> **"Segundo, el backend valida en cada petición que el `address_id` pertenezca al usuario autenticado. Si intento consultar el domicilio de otro, **404**."**
> **"Tercero, la interfaz tiene mensajes preventivos: 'Por tu seguridad, no persigas al camión'."**
> **"Privacy by Design no es un slogan, es nuestra arquitectura."**
---
### 8. Cierre (30 segundos)
> **"Mi Ruta Limpia es: una app que **resuelve el problema real** del ciudadano sin romper la privacidad."**
> **"Tiene **arquitectura productiva** lista para escalar."**
> **"Da **dignidad al trabajador** con motivación y bonos, no solo control."**
> **"Y le da al gobierno **datos accionables** para gestionar la ciudad."**
> **"Es lo que Celaya necesita. Gracias."**
---
## 🎯 Posibles preguntas de los jueces
### "¿Cómo escalan a 50,000 usuarios?"
- Cambiar SQLite por PostgreSQL (solo cambia `DATABASE_URL`)
- Containerizar con Docker, desplegar en AWS ECS / Cloud Run
- Cache con Redis para `/eta/*` (ya está stateless)
- Notificaciones via Firebase Cloud Messaging (esquema `push_token` ya en User)
- CDN para el bundle de Expo Web
### "¿Cómo manejan días festivos?"
- El simulador permite ajustar las rutas por fecha
- Endpoint admin podría exponer `/admin/holidays` para excluir días
- La notificación push avisaría al ciudadano
### "¿Cómo validan que la ruta es la correcta para mi casa?"
- El algoritmo `assign_route` usa Haversine con O(120) waypoints; en producción se usaría PostGIS con polígonos reales de zonas de cobertura
### "¿Y si tengo Android?"
- React Native + Expo compila a iOS, Android y Web con un solo código
- La demo es en iOS pero funciona idéntico en Android
### "¿OAuth real?"
- El endpoint `/auth/oauth` ya recibe datos del provider; falta integrar el SDK del cliente. Es 1-2 horas de trabajo.
### "¿Por qué SQLite y no Postgres?"
- Hackathon: zero-config, portable. Para producción, cambio trivial.
### "¿La simulación es real?"
- Es un mock realista que cumple el requisito del reto. En producción se reemplaza el `eta_service.get_eta()` para leer de una tabla de telemetría real, sin cambiar nada del cliente.
### "¿Mostraron el código?"
- Todo el código está en estructura limpia: 27 archivos backend, 22 archivos frontend src, todo en GitHub (o donde lo tengas).
---
## 📝 Notas para el presentador
### Antes de iniciar:
1. Verifica que backend y frontend estén corriendo
2. Cierra cualquier app que muestre notificaciones en pantalla
3. Limpia las pestañas de Safari/Chrome (que no se vea historial sensible)
4. Pon el simulador en modo de "Demo" si quieres ocultar status bar
### Durante la demo:
- **No tartamudees**: si no sabes algo, di "buena pregunta, eso lo cubrimos en la documentación técnica"
- **Mira a los jueces**, no al simulador
- **Habla en futuro presente**: "esta app permite", no "esta app permitirá"
- **Energía**: convierte el problema en algo emocional ("todos hemos sacado la basura")
### Si algo falla:
- "El simulador se está reiniciando, mientras tanto les muestro el código..."
- Ten capturas de pantalla en backup por si el simulador muere
- Ten el OpenAPI docs (`/docs`) abierto en una pestaña como respaldo