Table of Contents
- Configurar Flutter (Módulos C + D)
- 1. Clonar el repo o descargar módulos
- 2. Configurar FVM (recomendado)
- 3. Actualizar pubspec.yaml
- 4. Instalar dependencias
- 5. Verificar la estructura de carpetas
- 6. Configurar main.dart
- 7. Actualizar la URL del backend
- 8. Correr la app
- 9. Verificar que funciona
- 10. Hot Reload y Hot Restart
- 11. Debugging
- 12. Build para producción
- 13. Troubleshooting
- "Waiting for another flutter command to release the startup lock"
- "Gradle build failed"
- "Could not connect to WebSocket"
- "asset not found: assets/recycling_guide.json"
- "Unsupported class file major version 65"
- 14. Integración final con Persona A
- Siguiente paso
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Configurar Flutter (Módulos C + D)
Guía paso a paso para integrar los módulos y correr la app.
1. Clonar el repo o descargar módulos
Si ya está en el repo:
cd hackathon-acapulquitos-boys
git pull origin dev
cd basura_app
Si Persona D entrega su módulo por separado:
# Descomprimir ZIP del módulo D
unzip persona_d_modulo.zip
# Copiar archivos al proyecto principal
cp -r persona_d/lib/core basura_app/lib/
cp -r persona_d/lib/features/recycling_guide basura_app/lib/features/
cp persona_d/assets/recycling_guide.json basura_app/assets/
2. Configurar FVM (recomendado)
cd basura_app
# Instalar la versión acordada por el equipo
fvm install 3.44.0 # o 3.22.0 si bajaron versión
fvm use 3.44.0
# Verificar
fvm flutter --version
Esto crea .fvm/fvm_config.json — debe commitearse al repo.
3. Actualizar pubspec.yaml
Abre basura_app/pubspec.yaml y verifica:
name: basura_app
description: App de notificación de recolección de residuos
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.8.0 <4.0.0' # Dart 3.8 (Flutter 3.44)
flutter: '>=3.44.0' # o '>=3.22.0' si bajaron
dependencies:
flutter:
sdk: flutter
# State management
flutter_riverpod: ^2.6.1
# HTTP
dio: ^5.4.0
# WebSocket
web_socket_channel: ^2.4.0
# Routing (Persona C decide cuál usar)
go_router: ^13.2.0 # Recomendado
# o usa Navigator tradicional
# Utils
intl: ^0.19.0 # Formateo de fechas
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
flutter:
uses-material-design: true
assets:
- assets/recycling_guide.json # ← JSON del módulo D
4. Instalar dependencias
fvm flutter pub get
Deberías ver:
Running "flutter pub get" in basura_app...
Resolving dependencies...
Got dependencies!
5. Verificar la estructura de carpetas
tree -L 4 lib/
Debe verse algo así:
lib/
├── core/
│ └── theme/
│ └── app_theme.dart # Tema compartido (Persona D)
│
├── features/
│ ├── auth/ # Login (Persona C)
│ │ └── presentation/
│ │ └── screens/
│ │ └── login_screen.dart
│ │
│ ├── eta/ # Home + WebSocket (Persona C)
│ │ ├── domain/
│ │ ├── data/
│ │ └── presentation/
│ │ ├── screens/
│ │ │ └── eta_home_screen.dart
│ │ └── providers/
│ │ ├── eta_provider.dart
│ │ └── ws_provider.dart
│ │
│ └── recycling_guide/ # Guía offline (Persona D)
│ ├── domain/
│ ├── data/
│ └── presentation/
│ ├── screens/
│ │ ├── recycling_guide_screen.dart
│ │ └── category_detail_screen.dart
│ └── providers/
│ └── recycling_provider.dart
│
└── main.dart
6. Configurar main.dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'core/theme/app_theme.dart';
import 'features/eta/presentation/screens/eta_home_screen.dart';
import 'features/recycling_guide/presentation/screens/recycling_guide_screen.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Basura App',
theme: AppTheme.lightTheme, // ← Tema de Persona D
routerConfig: _router,
debugShowCheckedModeBanner: false,
);
}
}
// Router (Persona C configura las rutas finales)
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (_, __) => const ETAHomeScreen(), // Persona C
),
GoRoute(
path: '/guia',
builder: (_, __) => const RecyclingGuideScreen(), // Persona D
),
],
);
7. Actualizar la URL del backend
Importante: Por defecto, el backend corre en localhost:8000, pero:
- Emulador Android: usa
10.0.2.2:8000 - Emulador iOS: usa
localhost:8000 - Dispositivo físico: usa la IP de tu PC en la red local (ej:
192.168.1.10:8000)
Crear un provider de configuración
// lib/core/config/api_config.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
// Cambia esto según tu entorno
const _baseUrl = 'http://10.0.2.2:8000'; // Android emulator
// const _baseUrl = 'http://localhost:8000'; // iOS simulator
// const _baseUrl = 'http://192.168.1.10:8000'; // Dispositivo físico
final apiBaseUrlProvider = Provider<String>((ref) => _baseUrl);
final dioProvider = Provider<Dio>((ref) {
return Dio(BaseOptions(
baseUrl: ref.watch(apiBaseUrlProvider),
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 10),
));
});
Persona C importa este provider en sus datasources.
8. Correr la app
En un emulador
# Listar dispositivos disponibles
fvm flutter devices
# Correr en el primer dispositivo
fvm flutter run
# O seleccionar uno específico
fvm flutter run -d <device-id>
En dispositivo físico
- Habilita USB Debugging en el teléfono
- Conecta el cable
fvm flutter run
9. Verificar que funciona
Módulo D (offline — funciona sin backend)
- Navega a
/guia(botón en home o URL directa) - Deberías ver 4 categorías: Orgánicos, Reciclables, Sanitarios, Especiales
- Prueba el buscador: escribe "pila" → debe mostrar "Especiales"
- Toca una categoría → pantalla de detalle con items ✓ y ✗
Módulo C (requiere backend corriendo)
-
Arranca el backend primero:
cd basura_backend uvicorn app.main:app --reload -
Arranca el simulador:
curl -X POST http://localhost:8000/admin/route/RUTA-01/start -
En Flutter, deberías ver:
- Ventana horaria: "7:20 pm – 7:35 pm"
- Mensaje: "El camión está en camino..."
- Badge de estado: "EN_RUTA"
-
Cada ~10 segundos, el ETA se actualiza automáticamente vía WebSocket
10. Hot Reload y Hot Restart
Hot Reload (r)
Actualiza cambios en la UI sin perder el estado.
# En la terminal donde corre flutter run, presiona:
r
Hot Restart (R)
Reinicia la app desde cero.
R
11. Debugging
Ver logs en consola
fvm flutter run --verbose
Flutter DevTools
fvm flutter pub global activate devtools
fvm flutter pub global run devtools
Abre el link que aparece en el navegador.
12. Build para producción
Android APK
fvm flutter build apk --release
El APK estará en: build/app/outputs/flutter-apk/app-release.apk
iOS (requiere macOS + Xcode)
fvm flutter build ios --release
13. Troubleshooting
"Waiting for another flutter command to release the startup lock"
# Borrar el lock file
rm ~/.dart_tool/dart_tool.lock
"Gradle build failed"
Java incorrecto o problema de versión.
Solución:
# Verificar Java 17
java -version
# Limpiar build
cd android
./gradlew clean
cd ..
fvm flutter clean
fvm flutter pub get
"Could not connect to WebSocket"
Backend no está corriendo o URL incorrecta.
Solución:
# Verifica que el backend esté up
curl http://localhost:8000/health
# Ajusta la URL en api_config.dart según tu entorno
"asset not found: assets/recycling_guide.json"
El asset no está declarado en pubspec.yaml.
Solución:
flutter:
assets:
- assets/recycling_guide.json
Luego:
fvm flutter clean
fvm flutter pub get
"Unsupported class file major version 65"
Estás usando Java 21 en lugar de Java 17.
Solución:
# Cambiar a Java 17 (ver setup/00-requisitos.md)
sudo update-alternatives --config java
14. Integración final con Persona A
Cuando Persona A tenga el módulo de Auth listo:
-
Agregar el JWT token al Dio:
final dioProvider = Provider<Dio>((ref) { final dio = Dio(BaseOptions(baseUrl: _baseUrl)); // Interceptor para agregar token dio.interceptors.add(InterceptorsWrapper( onRequest: (options, handler) { final token = ref.read(authTokenProvider); if (token != null) { options.headers['Authorization'] = 'Bearer $token'; } handler.next(options); }, )); return dio; }); -
Proteger las rutas:
GoRoute( path: '/', redirect: (context, state) { final isLoggedIn = ref.read(authProvider).isAuthenticated; return isLoggedIn ? null : '/login'; }, builder: (_, __) => const ETAHomeScreen(), ),
Siguiente paso
- Variables de entorno — configurar
.envpara dev/prod - Contratos de API — consumir endpoints REST
- WebSocket protocol — conectar en tiempo real