Clone
1
04-integration.md
hack_23031391_8ff9d8 edited this page 2026-05-23 02:39:15 +00:00
Table of Contents
- Integración entre Módulos
- Visión general
- 1. Backend: Integración A + B
- Responsabilidades compartidas
- Base de datos unificada
- Endpoint compartido: /alerts/breakdown
- Middleware JWT (Persona A → usado por B)
- 2. Frontend: Integración C + D
- Estructura de carpetas unificada
- Tema compartido
- Router (Persona C configura)
- Navegación (desde ETA home a guía)
- Providers independientes (sin conflictos)
- 3. Flujos completos end-to-end
- Flujo 1: Usuario se registra y consulta ETA
- Flujo 2: Usuario explora la guía offline
- Flujo 3: Simulador notifica proximidad
- Flujo 4: Usuario reporta avería
- 4. Pruebas de integración
- 5. Checklist de integración
- 6. Troubleshooting de integración
- "401 Unauthorized" en GET /eta
- "404 Not Found" en POST /addresses
- WebSocket no recibe eventos
- La guía no carga categorías
- Siguiente paso
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:
usersaddressesnotification_preferencesnotification_templates
Persona B usa (read-only):
addresses.route_id— para filtrar domicilios por rutanotification_preferences— antes de enviar WebSocketnotification_templates— para formatear mensajes
Persona B crea y gestiona:
truck_statusrutaspuntos_rutanotificaciones(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:
- Detener el simulador
- Actualizar
truck_statusaAVERIADA - Broadcast WebSocket a todos los domicilios de la ruta
Código de integración:
# 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:
# 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:
# 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:
// lib/core/theme/app_theme.dart
class AppTheme {
static const primaryColor = Color(0xFF1B5E20);
static ThemeData get lightTheme => ThemeData(...);
}
Persona C usa en main.dart:
import 'core/theme/app_theme.dart';
MaterialApp(
theme: AppTheme.lightTheme, // ← tema de Persona D
// ...
);
Router (Persona C configura)
// 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:
// 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:
// lib/features/eta/presentation/providers/eta_provider.dart
final etaProvider = FutureProvider.family<ETAInfo, int>(...);
final wsProvider = StreamProvider.family<NotificationEvent, int>(...);
Persona D:
// lib/features/recycling_guide/presentation/providers/recycling_provider.dart
final recyclingCategoriesProvider = FutureProvider<List<RecyclingCategory>>(...);
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
# 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 <TOKEN>" \
-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/<ADDRESS_ID> \
-H "Authorization: Bearer <TOKEN>"
# 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/<ADDRESS_ID>
# Deberías recibir eventos cada 10s
Test 2: Flutter C + D
# 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/breakdownimplementado por B - Middleware JWT de A agregado a rutas de B
- CORS habilitado para desarrollo
Frontend (C + D)
app_theme.dartde D copiado acore/theme/recycling_guide/de D copiado afeatures/assets/recycling_guide.jsonagregado 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:
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:
// 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:
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:
flutter:
assets:
- assets/recycling_guide.json