Archivos revisados
lib/features/meal/presentation/
meal_log_page.dart
2 CRITICAL
·
3 HIGH
·
2 MEDIUM
lib/features/meal/bloc/
meal_log_bloc.dart
3 CRITICAL
·
1 HIGH
lib/data/repositories/
nutrition_repository.dart
1 CRITICAL
·
1 HIGH
·
2 MEDIUM
CRITICAL
Bloquean el merge — riesgo de seguridad o corrupción de datos
API key hardcodeada en código fuente
meal_log_page.dart:56
Problema
La clave de API
"sk-nutritrack-prod-abc123xyz" aparece en texto plano en el código fuente. Cualquier persona con acceso al repo (o al APK decomilado) puede extraerla y hacer llamadas autenticadas contra la API de producción.56 final apiKey = "sk-nutritrack-prod-abc123xyz"; // ⚠️ secreto en texto plano 57 await _repo.logMeal(meal, apiKey);
Corrección
Eliminar el argumento
apiKey de la capa de presentación. El repositorio debe leer la clave desde flutter_secure_storage o variables de entorno en build-time (--dart-define). La presentación nunca debe manejar secretos.Accion inmediata
Revocar la clave expuesta en la consola de NutriTrack API. Rotar credenciales antes de continuar el review.
Tráfico HTTP sin cifrar (cleartext)
nutrition_repository.dart:12, 22
Problema
Todas las llamadas usan
http:// en lugar de https://. En Android 9+ el tráfico cleartext está bloqueado por defecto sin android:usesCleartextTraffic="true", lo que causaría fallos en producción. Además expone credenciales y datos de comida en redes no seguras.12 await http.get(Uri.parse('http://api.nutritrack.io/meals')); 22 await http.post(Uri.parse('http://api.nutritrack.io/log'), ...);
Corrección
Cambiar todas las URLs a
https://. Configurar network_security_config.xml para rechazar explícitamente cleartext en Android.
Estado BLoC mutado in-place (violación de inmutabilidad)
meal_log_bloc.dart:17–25
Problema
El handler
LoadMeals muta directamente las propiedades del objeto state actual en lugar de emitir un nuevo estado. flutter_bloc compara referencias de objeto; al mutar el mismo objeto, el BlocConsumer nunca detecta cambios y la UI no se actualiza.17on<LoadMeals>((event, emit) async { 18 state.isLoading = true; // ❌ mutación directa — el BLoC no emite 19 try { 20 final meals = await repo.fetchMeals(); 21 state.meals = meals; // ❌ mutación directa 22 state.isLoading = false; // ❌ 23 } catch (e) { 24 state.isError = true; // ❌ 25 } 26});
Corrección
Hacer
MealLogState inmutable (campos final, añadir copyWith, implementar ==/hashCode con equatable). Usar emit(state.copyWith(isLoading: true)) en cada transición.// Corrección on<LoadMeals>((event, emit) async { emit(state.copyWith(isLoading: true)); try { final meals = await repo.fetchMeals(); emit(state.copyWith(meals: meals, isLoading: false, hasData: true)); } catch (e) { emit(state.copyWith(isLoading: false, isError: true)); } });
Boolean flag soup — estados imposibles en BLoC
meal_log_bloc.dart:3–11
Problema
El estado modela tres booleanos independientes (
isLoading, isError, hasData) que permiten combinaciones imposibles como isLoading=true && isError=true o hasData=true && isLoading=true. Esto genera bugs difíciles de reproducir cuando las transiciones se solapan.Corrección
Reemplazar los tres booleanos con un tipo sellado que modele estados mutuamente excluyentes:
sealed class MealLogState {} class MealLogInitial extends MealLogState {} class MealLogLoading extends MealLogState {} class MealLogSuccess extends MealLogState { const MealLogSuccess(this.meals); final List<String> meals; } class MealLogFailure extends MealLogState { const MealLogFailure(this.message); final String message; }
Repository importa Flutter — violación de capas Clean Architecture
nutrition_repository.dart:3
Problema
La capa de datos importa
package:flutter/material.dart, introduciendo una dependencia del framework en una capa que debe ser Dart puro. Esto impide reutilizar el repositorio en tests sin levantar el motor Flutter y viola la regla de dependencias de Clean Architecture.3import 'package:flutter/material.dart'; // ❌ framework en capa de datos
Corrección
Eliminar el import. Si se necesitan colores o temas para algo, moverlo a la capa de presentación. La capa de datos solo debe importar
dart: y paquetes de Dart puros.
Repositorio instanciado directamente en el BLoC — sin inyección de dependencias
meal_log_bloc.dart:13
Problema
MealLogBloc construye NutritionRepository() internamente. Esto hace imposible mockear el repositorio en tests y crea un acoplamiento fuerte entre capas.13 final NutritionRepository repo = NutritionRepository(); // ❌ instanciación interna
Corrección
Inyectar el repositorio via constructor. Depender de una abstracción (
abstract class INutritionRepository) en lugar de la implementación concreta.MealLogBloc({required INutritionRepository repository}) : _repository = repository, super(MealLogInitial());
HIGH
Deben corregirse antes del merge — bugs funcionales o degradación de rendimiento
StreamSubscription no se cancela en dispose() — memory leak
meal_log_page.dart:18, 43
Problema
_sub se suscribe al stream en initState() pero el widget no implementa dispose(). Al desmontar el widget el listener queda activo, causando leak de memoria y potenciales setState() sobre widget desmontado.19 StreamSubscription? _sub; // declarado pero nunca cancelado 24 _sub = _repo.mealUpdates.listen((update) { 25 setState(() { hasData = true; }); 26 }); // ⚠️ sin @override void dispose() { _sub?.cancel(); super.dispose(); }
Corrección
Añadir
@override void dispose() { _sub?.cancel(); _searchCtrl.dispose(); super.dispose(); }. Además, TextEditingController también debe disponerse.
BuildContext usado tras await sin comprobar mounted
meal_log_page.dart:60
Problema
Tras el
await _repo.logMeal(), el código llama a Navigator.push(context, ...) sin verificar context.mounted. Si el widget se desmontó durante el await, esto lanza una excepción en tiempo de ejecución.60 await _repo.logMeal(meal, apiKey); 61 Navigator.push(context, ...); // ❌ context puede estar desmontado
Corrección
Añadir
if (!context.mounted) return; inmediatamente después del await (Flutter 3.7+).
Ordenación costosa en build() — trabajo en capa de presentación
meal_log_page.dart:37–40, 69
Problema
_getSortedMeals() ordena la lista en cada llamada a build(). Con listas grandes esto degrada el rendimiento de 60fps. La ordenación debe hacerse en el BLoC al emitir el estado.69 final meals = _getSortedMeals(state.meals); // ❌ O(n log n) en cada rebuild
Corrección
Ordenar en el BLoC antes de emitir (
meals.sort() en el handler) y exponer la lista ya ordenada en el estado. El widget solo consume.
BlocConsumer envuelve Scaffold completo — rebuild innecesario de toda la pantalla
meal_log_page.dart:66
Problema
El
BlocConsumer envuelve el Scaffold entero. Cualquier cambio de estado (loading, error) reconstruye toda la pantalla incluyendo el AppBar, el TextField y el botón, que no dependen del estado.Corrección
Acotar el consumer solo al subtree que varía (el
ListView y el indicador de carga). Usar BlocSelector para suscribirse solo a los campos relevantes.
Logging de errores con print() en producción
meal_log_bloc.dart:24
Problema
print("Error loading meals: $e") escribe en la consola en builds de producción. Puede filtrar stack traces con información sensible. Además el error queda silenciado — no se emite estado de error al BLoC.24 print("Error loading meals: $e"); // ❌ print en producción + error silenciado
Corrección
Reemplazar
print() por dart:developer log() o el logger del proyecto. Integrar Sentry/Crashlytics para captura no fatal. El estado de error debe emitirse para que la UI lo gestione.
MEDIUM
Mejoras de mantenibilidad y accesibilidad — recomendadas antes del merge
Colores y estilos hardcodeados — rompe dark mode
meal_log_page.dart:47–48
Problema
Color(0xFF4CAF50) y TextStyle(color: Colors.white, fontSize: 16) están hardcodeados. Si la app soporta dark mode o el usuario cambia el contraste del sistema, estos elementos quedarán ilegibles.Corrección
Usar
Theme.of(context).colorScheme.primary y Theme.of(context).textTheme.bodyMedium. Definir tokens de diseño en el tema global de la app.
Helper method _buildMealItem() — impide optimizaciones del framework
meal_log_page.dart:42–51
Problema
Los métodos privados
_buildMealItem() que retornan widgets impiden que Flutter optimize las reconstrucciones parciales. El framework no puede cachear ni comparar estos subtrees porque son funciones, no clases.Corrección
Extraer a una clase
StatelessWidget con constructor const:class MealListItem extends StatelessWidget { const MealListItem({super.key, required this.meal}); final String meal; @override Widget build(BuildContext context) { /* ... */ } }
BuildContext almacenado en campo de instancia
meal_log_page.dart:30, 68
Problema
_ctx = context almacena el contexto en un campo del State. Si el widget se reasigna a otro árbol de elementos, _ctx apunta a un contexto obsoleto, lo que puede causar crashes o comportamientos inesperados.Corrección
Eliminar el campo
_ctx. Usar siempre el parámetro context del método build() o del callback del BlocConsumer.
ElevatedButton sin semanticLabel — accesibilidad insuficiente
meal_log_page.dart:77–79
Problema
El botón "Registrar" no tiene un
Semantics wrapping descriptivo. Los lectores de pantalla (TalkBack/VoiceOver) leerán solo "Registrar" sin contexto de la acción. El icono de estado de carga (CircularProgressIndicator) tampoco tiene etiqueta semántica.Corrección
Envolver el indicador en
Semantics(label: 'Cargando comidas', child: ...). Añadir tooltip o Semantics(button: true, label: 'Registrar comida seleccionada') al botón.
LOW
Deuda técnica — planificar en próximos sprints
Sin analysis_options.yaml con reglas estrictas
analysis_options.yaml
Problema
El proyecto no tiene
analysis_options.yaml configurado con strict-casts, strict-inference, ni strict-raw-types. Esto permite que muchos de los problemas detectados pasen el análisis estático sin warnings.Corrección
Añadir reglas de análisis estrictas. Comenzar con el paquete
flutter_lints y activar progresivamente las reglas de very_good_analysis o pedantic_mono.# analysis_options.yaml include: package:flutter_lints/flutter.yaml analyzer: language: strict-casts: true strict-inference: true strict-raw-types: true
📊
Resumen de la Revision
| Severidad | Cantidad | Estado |
|---|---|---|
| CRITICAL | 6 | ⛔ BLOQUEA MERGE |
| HIGH | 5 | ⛔ BLOQUEA MERGE |
| MEDIUM | 4 | ⚠️ RECOMENDADO |
| LOW | 1 | ℹ️ DEUDA TECNICA |
⛔
Veredicto: BLOQUEADO
6 issues CRITICAL (incluyendo secreto expuesto y tráfico HTTP) + 5 HIGH deben corregirse antes del merge. Prioridad inmediata: revocar la API key hardcodeada.