Files
sf-app-platform/docs/location-module-review.md

6.8 KiB

Location Module — Code Review

Resumen Ejecutivo

~6000 líneas de Dart. Arquitectura limpia pero con 3 bugs críticos y varias oportunidades de mejora.


Bugs Críticos (HIGH)

1. Crash por array out of bounds en el playback del historial

Archivo: location_map.dart:753

currentPosition: widget.positionHistory[mapState.historyNavigationIndex]

Si el historial se limpia mientras el playback está activo, historyNavigationIndex puede apuntar fuera del array → crash.

2. El historial NO se limpia al cambiar de dispositivo

Archivo: location_map.dart:126-133 Cuando el usuario cambia de dispositivo, la ruta histórica del dispositivo anterior sigue visible en el mapa. Confusión garantizada.

3. Rebuilds excesivos de marcadores con historial grande

Archivo: location_map.dart:796-882 _buildMarkers() se llama en cada build. Con 1000+ posiciones de historial, recrea la lista completa de markers cada vez → UI laggy/janky.


Bugs Medios (MEDIUM)

4. Race condition en animación de historial

Archivo: location_map.dart:141-152 Si se carga nuevo historial mientras la animación sigue, pueden haber conflictos de estado. No hay guard contra animaciones concurrentes.

5. Memory leak con timers

Archivo: location_map.dart:99-109 _followTimer se recrea en didUpdateWidget sin garantías de que el callback anterior terminó su ejecución.

6. Errores silenciosos en carga inicial

Archivo: location_controller.dart:31-43 Si geofences Y frequent places fallan al cargar, el catch-all devuelve estado vacío sin notificar al usuario.

7. Paginación de historial sin límite de memoria

Archivo: location_remote_datasource_impl.dart:128-150 pageSize=1000 con loop hasta totalPages. Datasets enormes pueden causar OOM al acumular todas las posiciones en memoria.

8. Cast inseguro en markers

Archivo: location_map.dart:812

(historyLayer.build(context) as MarkerLayer).markers

Puede crashear si cambia el tipo de retorno de RouteHistoryLayer.build().

9. Clustering O(n²)

Archivo: route_history_layer.dart:160-182 Algoritmo de clustering compara cada punto con todos los demás. Lento para datasets grandes.


Bugs Menores (LOW)

10. Filtro de coordenadas (0,0)

Archivo: location_remote_datasource_impl.dart:152-154 latitude != 0 || longitude != 0 podría filtrar posiciones válidas cerca del ecuador/meridiano de Greenwich.

11. Sin backoff en polling

Archivo: location_map.dart:103-108 Timer sigue disparando cada N segundos aunque el dispositivo esté desconectado. Solo hace return temprano, sin reducir frecuencia.

12. Tracking unawaited sin error handling

Las llamadas de analytics (unawaited(tracking.event())) no manejan errores. Difícil de debuggear si el tracking falla silenciosamente.


Lo que funciona bien

  • CRUD completo de geofences y frequent places con buen manejo de 404
  • Tests sólidos: 577 + 312 líneas cubriendo todos los CRUD y transiciones de estado
  • Animaciones del mapa (reveal, playback, move) bien implementadas
  • Separación de controllers: LocationController (datos) vs LocationMapController (UI del mapa)
  • Error propagation con enums tipados y keys i18n para cada tipo de error
  • Tracking analytics exhaustivo en todas las acciones del usuario
  • Paginación en historial de posiciones (loop hasta totalPages)
  • orderBy correcto en historial (positionDate ASC) y posiciones actuales (positionDate DESC)

Lo que falta

  • Sin WebSocket para posiciones en tiempo real (solo HTTP polling cada 60s+)
  • Sin widget tests ni integration tests
  • Sin tests de performance con datasets grandes
  • Sin tests de concurrencia (CRUD simultáneo, device switch durante playback)
  • LocationMap tiene 1063 líneas — debería dividirse en subwidgets

Arquitectura

Data Flow

API (SaveFamilyRepository)
  → LocationRemoteDatasource (HTTP calls)
  → LocationRepository (error mapping, 404 handling)
  → LocationController (state management, business logic)
  → LocationScreen → LocationMap → Widgets

State Management

  • LocationController (keepAlive: true) — geofences, frequent places, position history, CRUD operations
  • LocationMapController — UI state: placing mode, selections, animations, playback, reveal
  • LocationListFilterController — simple enum filter for position history type

Map Layers (bottom to top)

  1. TileLayer (estilo del mapa)
  2. CircleLayer (geofences activos)
  3. PolylineLayer (ruta del historial)
  4. CircleLayer (preview de geofence durante creación)
  5. MarkerLayer (dispositivo, geofences, frequent places, posiciones del historial)

Fixes recomendados por prioridad

Prioridad 1 — Prevenir crashes

  1. Bounds check en playback — Verificar historyNavigationIndex < positionHistory.length antes de acceder
  2. Limpiar historial al cambiar dispositivo — Llamar clearPositionHistory() en device switch

Prioridad 2 — Mejorar rendimiento

  1. Memoizar markers — Cache del resultado de _buildMarkers(), solo recalcular cuando cambian los datos
  2. Limitar posiciones en memoria — Cap de posiciones o streaming en lugar de acumular todo

Prioridad 3 — Mejorar robustez

  1. Guard de animación concurrente — Flag _isAnimating para prevenir inicio de nueva animación mientras otra corre
  2. Mostrar error si carga inicial falla — Propagar error al usuario en lugar de silenciar

Prioridad 4 — Mejorar UX

  1. WebSocket para posiciones — Reemplazar polling HTTP por push via WebSocket
  2. Dividir LocationMap — Extraer subwidgets para controles, markers, info cards

Archivos del módulo

Core (Data Layer)

  • src/core/data/datasource/location_remote_datasource.dart — interfaz abstracta
  • src/core/data/datasource/location_remote_datasource_impl.dart — 157 líneas, HTTP calls
  • src/core/data/repositories/location_repository_impl.dart — 102 líneas, error mapping
  • src/core/domain/entities/geofence_entity.dart — Freezed entity
  • src/core/domain/entities/frequent_place_entity.dart — Freezed entity
  • src/core/data/models/ — 14 DTOs con json_serializable
  • src/core/providers/ — 2 providers de DI

Presentation (Feature Layer)

  • location_controller.dart — 431 líneas, business logic principal
  • location_map_controller.dart — 328 líneas, state machine del mapa
  • location_state.dart — 66 líneas, Freezed state
  • location_map_state.dart — 37 líneas, Freezed UI state
  • location_screen.dart — 82 líneas, entry point
  • location_map.dart1063 líneas, widget principal del mapa
  • widgets/ — 8 controles de mapa + 4 info cards + 4 widgets de soporte

Tests

  • location_controller_test.dart — 577 líneas, CRUD completo
  • location_map_controller_test.dart — 312 líneas, transiciones de estado