Añadir 02-frontend.md
442
02-frontend.md.-.md
Normal file
442
02-frontend.md.-.md
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
# 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:
|
||||||
|
```bash
|
||||||
|
cd hackathon-acapulquitos-boys
|
||||||
|
git pull origin dev
|
||||||
|
cd basura_app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Si Persona D entrega su módulo por separado:
|
||||||
|
```bash
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fvm flutter pub get
|
||||||
|
```
|
||||||
|
|
||||||
|
Deberías ver:
|
||||||
|
```
|
||||||
|
Running "flutter pub get" in basura_app...
|
||||||
|
Resolving dependencies...
|
||||||
|
Got dependencies!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Verificar la estructura de carpetas
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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`
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
1. Habilita **USB Debugging** en el teléfono
|
||||||
|
2. Conecta el cable
|
||||||
|
3. `fvm flutter run`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Verificar que funciona
|
||||||
|
|
||||||
|
### Módulo D (offline — funciona sin backend)
|
||||||
|
|
||||||
|
1. Navega a `/guia` (botón en home o URL directa)
|
||||||
|
2. Deberías ver 4 categorías: Orgánicos, Reciclables, Sanitarios, Especiales
|
||||||
|
3. Prueba el buscador: escribe "pila" → debe mostrar "Especiales"
|
||||||
|
4. Toca una categoría → pantalla de detalle con items ✓ y ✗
|
||||||
|
|
||||||
|
### Módulo C (requiere backend corriendo)
|
||||||
|
|
||||||
|
1. **Arranca el backend primero:**
|
||||||
|
```bash
|
||||||
|
cd basura_backend
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Arranca el simulador:**
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8000/admin/route/RUTA-01/start
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 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"
|
||||||
|
|
||||||
|
4. 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.
|
||||||
|
```bash
|
||||||
|
# En la terminal donde corre flutter run, presiona:
|
||||||
|
r
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hot Restart (R)
|
||||||
|
Reinicia la app desde cero.
|
||||||
|
```bash
|
||||||
|
R
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Debugging
|
||||||
|
|
||||||
|
### Ver logs en consola
|
||||||
|
```bash
|
||||||
|
fvm flutter run --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flutter DevTools
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```bash
|
||||||
|
fvm flutter build apk --release
|
||||||
|
```
|
||||||
|
|
||||||
|
El APK estará en: `build/app/outputs/flutter-apk/app-release.apk`
|
||||||
|
|
||||||
|
### iOS (requiere macOS + Xcode)
|
||||||
|
```bash
|
||||||
|
fvm flutter build ios --release
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Troubleshooting
|
||||||
|
|
||||||
|
### "Waiting for another flutter command to release the startup lock"
|
||||||
|
```bash
|
||||||
|
# Borrar el lock file
|
||||||
|
rm ~/.dart_tool/dart_tool.lock
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Gradle build failed"
|
||||||
|
Java incorrecto o problema de versión.
|
||||||
|
|
||||||
|
**Solución:**
|
||||||
|
```bash
|
||||||
|
# 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:**
|
||||||
|
```bash
|
||||||
|
# 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:**
|
||||||
|
```yaml
|
||||||
|
flutter:
|
||||||
|
assets:
|
||||||
|
- assets/recycling_guide.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Luego:
|
||||||
|
```bash
|
||||||
|
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:**
|
||||||
|
```bash
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
1. **Agregar el JWT token al Dio:**
|
||||||
|
```dart
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Proteger las rutas:**
|
||||||
|
```dart
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
redirect: (context, state) {
|
||||||
|
final isLoggedIn = ref.read(authProvider).isAuthenticated;
|
||||||
|
return isLoggedIn ? null : '/login';
|
||||||
|
},
|
||||||
|
builder: (_, __) => const ETAHomeScreen(),
|
||||||
|
),
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Siguiente paso
|
||||||
|
|
||||||
|
- [Variables de entorno](03-env.md) — configurar `.env` para dev/prod
|
||||||
|
- [Contratos de API](../api/01-endpoints.md) — consumir endpoints REST
|
||||||
|
- [WebSocket protocol](../api/02-websocket.md) — conectar en tiempo real
|
||||||
Reference in New Issue
Block a user