From d1dcb8254d0b1bc3178a2733b19797858cb51611 Mon Sep 17 00:00:00 2001 From: hack_23031391_8ff9d8 <23031391@itcelaya.edu.mx> Date: Sat, 23 May 2026 02:39:15 +0000 Subject: [PATCH] =?UTF-8?q?A=C3=B1adir=2004-integration.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 04-integration.md.-.md | 454 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 04-integration.md.-.md diff --git a/04-integration.md.-.md b/04-integration.md.-.md new file mode 100644 index 0000000..5e46382 --- /dev/null +++ b/04-integration.md.-.md @@ -0,0 +1,454 @@ +# Integración entre Módulos + +Guía de cómo conectar el trabajo de las 4 personas. + +--- + +## Visión general + +``` +┌──────────────┐ ┌──────────────┐ +│ Persona A │ │ Persona B │ +│ (Auth) │◄────►│ (Simulador) │ +└──────┬───────┘ └──────┬───────┘ + │ │ + │ Backend Python │ + └──────────┬──────────┘ + │ + │ HTTP + WebSocket + │ + ┌──────────▼──────────┐ + │ │ +┌──────▼───────┐ ┌──────────▼─────┐ +│ Persona C │ │ Persona D │ +│ (ETA UI) │ │ (Guía) │ +└──────────────┘ └────────────────┘ + Frontend Flutter +``` + +--- + +## 1. Backend: Integración A + B + +### Responsabilidades compartidas + +| Persona A | Persona B | Compartido | +|-----------|-----------|------------| +| JWT tokens | Simulador | Esquema BD | +| RBAC | WebSocket | truck_status | +| Alta domicilios | ETA cálculo | addresses | +| Validar zona | Notificaciones | notification_preferences | + +### Base de datos unificada + +**Persona A crea:** +- `users` +- `addresses` +- `notification_preferences` +- `notification_templates` + +**Persona B usa (read-only):** +- `addresses.route_id` — para filtrar domicilios por ruta +- `notification_preferences` — antes de enviar WebSocket +- `notification_templates` — para formatear mensajes + +**Persona B crea y gestiona:** +- `truck_status` +- `rutas` +- `puntos_ruta` +- `notificaciones` (log) + +### Endpoint compartido: `/alerts/breakdown` + +**Persona A puede llamarlo** desde su módulo cuando detecta un reporte de usuario. + +**Persona B lo implementa** y maneja: +1. Detener el simulador +2. Actualizar `truck_status` a `AVERIADA` +3. Broadcast WebSocket a todos los domicilios de la ruta + +**Código de integración:** + +```python +# app/api/routes/alerts_router.py (compartido) +from app.services.simulador import Simulador +from app.services.ws_manager import ws_manager + +@router.post("/alerts/breakdown") +async def report_breakdown(payload: BreakdownRequest): + # Persona B: detener simulador + simulador = Simulador() + simulador.forzar_averia(payload.route_id) + + # Persona B: broadcast WebSocket + addresses = obtener_addresses_de_ruta(payload.route_id) + for addr in addresses: + await ws_manager.broadcast_zona( + str(addr.id), + { + "tipo": "falla_mecanica", + "address_id": addr.id, + "mensaje": "...", + } + ) + + return {"message": "Alerta de avería enviada"} +``` + +### Middleware JWT (Persona A → usado por B) + +**Persona A crea:** +```python +# app/core/auth.py +from fastapi import Depends, HTTPException +from fastapi.security import HTTPBearer + +security = HTTPBearer() + +async def verify_token(token: str = Depends(security)): + try: + payload = jwt.decode(token, SECRET_KEY) + return payload + except jwt.ExpiredSignatureError: + raise HTTPException(401, "Token expirado") +``` + +**Persona B lo usa:** +```python +# app/api/routes/eta_router.py +from app.core.auth import verify_token + +@router.get("/eta/{address_id}") +async def get_eta( + address_id: int, + user = Depends(verify_token) # ← agregado por Persona B +): + # Verificar que el address_id pertenece al user + if not address_belongs_to_user(address_id, user['sub']): + raise HTTPException(403, "No autorizado") + + # ... lógica de ETA +``` + +--- + +## 2. Frontend: Integración C + D + +### Estructura de carpetas unificada + +``` +basura_app/lib/ +├── core/ +│ ├── theme/ +│ │ └── app_theme.dart # Persona D crea, Persona C usa +│ └── config/ +│ ├── api_config.dart # Persona C +│ └── router.dart # Persona C +│ +└── features/ + ├── auth/ # Persona C + ├── eta/ # Persona C + └── recycling_guide/ # Persona D +``` + +### Tema compartido + +**Persona D crea:** +```dart +// lib/core/theme/app_theme.dart +class AppTheme { + static const primaryColor = Color(0xFF1B5E20); + static ThemeData get lightTheme => ThemeData(...); +} +``` + +**Persona C usa en main.dart:** +```dart +import 'core/theme/app_theme.dart'; + +MaterialApp( + theme: AppTheme.lightTheme, // ← tema de Persona D + // ... +); +``` + +### Router (Persona C configura) + +```dart +// lib/core/config/router.dart +import 'package:go_router/go_router.dart'; +import '../../features/eta/presentation/screens/eta_home_screen.dart'; +import '../../features/recycling_guide/presentation/screens/recycling_guide_screen.dart'; + +final router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (_, __) => const ETAHomeScreen(), // Persona C + ), + GoRoute( + path: '/guia', + builder: (_, __) => const RecyclingGuideScreen(), // Persona D + ), + ], +); +``` + +### Navegación (desde ETA home a guía) + +**Persona C agrega botón:** +```dart +// lib/features/eta/presentation/screens/eta_home_screen.dart +FloatingActionButton( + child: Icon(Icons.recycling), + onPressed: () => context.go('/guia'), // ← navega a módulo D +) +``` + +### Providers independientes (sin conflictos) + +**Persona C:** +```dart +// lib/features/eta/presentation/providers/eta_provider.dart +final etaProvider = FutureProvider.family(...); +final wsProvider = StreamProvider.family(...); +``` + +**Persona D:** +```dart +// lib/features/recycling_guide/presentation/providers/recycling_provider.dart +final recyclingCategoriesProvider = FutureProvider>(...); +final recyclingSearchProvider = StateNotifierProvider<...>(...); +``` + +**Cero conflictos** — cada uno tiene su namespace de providers. + +--- + +## 3. Flujos completos end-to-end + +### Flujo 1: Usuario se registra y consulta ETA + +``` +1. Usuario abre app → Flutter muestra LoginScreen (Persona C) + +2. Usuario toca "Registrarse" → Flutter llama POST /register (Persona A) + Backend valida, crea usuario, retorna JWT + +3. Usuario ingresa domicilio → Flutter llama POST /addresses (Persona A) + Backend asigna route_id según lat/lng + +4. Flutter navega a ETAHomeScreen (Persona C) + → GET /eta/{address_id} (Persona B) + → Muestra ventana horaria + +5. Flutter conecta WebSocket /ws/{address_id} (Persona B) + → Recibe eventos en tiempo real +``` + +### Flujo 2: Usuario explora la guía offline + +``` +1. Usuario toca ícono de reciclaje → Flutter navega a /guia (Persona D) + +2. RecyclingGuideScreen carga assets/recycling_guide.json (Persona D) + → Muestra 4 categorías + +3. Usuario escribe "pila" en buscador → RecyclingRepository.buscar() (Persona D) + → Muestra "Especiales" + +4. Usuario toca "Especiales" → CategoryDetailScreen (Persona D) + → Muestra items ✓ y ✗ + +5. Todo funciona sin internet — cero llamadas al backend +``` + +### Flujo 3: Simulador notifica proximidad + +``` +1. Admin arranca simulador → POST /admin/route/RUTA-01/start (Persona B) + → APScheduler inicia ticks cada 10s + +2. Cada tick, Persona B: + → Avanza truck_status.current_position_id + → Calcula ETA para cada address en RUTA-01 + → Si ETA <= 10 min: + → Consulta notification_preferences (Persona A) + → Si notify_proximity == True: + → Broadcast WebSocket (Persona B) + +3. Flutter (Persona C) recibe evento "aproximandose" + → Muestra SnackBar: "El camión llega en ~8 minutos" + → Notificación push local +``` + +### Flujo 4: Usuario reporta avería + +``` +1. Usuario toca botón "Reportar problema" (Persona C) + → POST /alerts/breakdown con route_id (endpoint compartido A+B) + +2. Backend (Persona B): + → Detiene simulador + → Actualiza truck_status a AVERIADA + → Broadcast WebSocket a todos los address de RUTA-01 + +3. Flutter (Persona C) recibe evento "falla_mecanica" + → Actualiza badge a "Avería" + → Muestra mensaje de disculpa +``` + +--- + +## 4. Pruebas de integración + +### Test 1: Backend A + B + +```bash +# 1. Registrar usuario (Persona A) +curl -X POST http://localhost:8000/register \ + -H "Content-Type: application/json" \ + -d '{ + "email": "test@ejemplo.com", + "password": "test123", + "phone": "+52 1234567890" + }' + +# Guardar el token retornado + +# 2. Dar de alta domicilio (Persona A) +curl -X POST http://localhost:8000/addresses \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "alias": "Test House", + "lat": 20.5200, + "lng": -100.8100 + }' + +# Guardar el address_id retornado + +# 3. Consultar ETA (Persona B) +curl http://localhost:8000/eta/ \ + -H "Authorization: Bearer " + +# Debería retornar ventana horaria + +# 4. Arrancar simulador (Persona B) +curl -X POST http://localhost:8000/admin/route/RUTA-01/start + +# 5. Conectar WebSocket (Persona B) +wscat -c ws://localhost:8000/ws/ + +# Deberías recibir eventos cada 10s +``` + +### Test 2: Flutter C + D + +```bash +# 1. Correr Flutter +fvm flutter run + +# 2. Login (Persona C) +# → Ingresar credenciales en UI + +# 3. Ver ETA home (Persona C) +# → Debería mostrar ventana horaria + +# 4. Navegar a guía (Persona D) +# → Tocar ícono de reciclaje +# → Verificar que carga 4 categorías + +# 5. Buscar "pila" (Persona D) +# → Verificar que aparece "Especiales" + +# 6. Volver a home y esperar notificación (Persona C) +# → Debería recibir evento WebSocket cada 10s +``` + +--- + +## 5. Checklist de integración + +### Backend (A + B) + +- [ ] Esquema de BD unificado (database.py compartido) +- [ ] Persona A crea tablas de auth +- [ ] Persona B crea tablas de simulador +- [ ] Endpoint `/alerts/breakdown` implementado por B +- [ ] Middleware JWT de A agregado a rutas de B +- [ ] CORS habilitado para desarrollo + +### Frontend (C + D) + +- [ ] `app_theme.dart` de D copiado a `core/theme/` +- [ ] `recycling_guide/` de D copiado a `features/` +- [ ] `assets/recycling_guide.json` agregado y declarado en pubspec +- [ ] Router de C incluye ruta `/guia` → RecyclingGuideScreen +- [ ] main.dart usa `AppTheme.lightTheme` +- [ ] Navegación de home a guía funciona + +### Contratos API + +- [ ] Backend corre en puerto 8000 +- [ ] Flutter usa URL correcta (10.0.2.2 para Android emulator) +- [ ] JWT token se pasa en headers cuando sea necesario +- [ ] WebSocket conecta con address_id válido + +--- + +## 6. Troubleshooting de integración + +### "401 Unauthorized" en GET /eta + +**Causa:** Persona C olvidó agregar el token JWT. + +**Solución:** +```dart +final dio = Dio(BaseOptions( + headers: {'Authorization': 'Bearer $token'}, +)); +``` + +--- + +### "404 Not Found" en POST /addresses + +**Causa:** Persona A no implementó ese endpoint aún. + +**Solución temporal:** +```dart +// Mock el address_id en Persona C +final mockAddressId = 1; // del seed de Persona B +``` + +--- + +### WebSocket no recibe eventos + +**Causa:** El simulador no está corriendo. + +**Solución:** +```bash +curl -X POST http://localhost:8000/admin/route/RUTA-01/start +``` + +--- + +### La guía no carga categorías + +**Causa:** `recycling_guide.json` no está en `pubspec.yaml`. + +**Solución:** +```yaml +flutter: + assets: + - assets/recycling_guide.json +``` + +--- + +## Siguiente paso + +- [Errores comunes](../troubleshooting/01-errores-comunes.md) +- [Problemas de versiones](../troubleshooting/02-versiones.md)