112 lines
3.4 KiB
Dart
112 lines
3.4 KiB
Dart
// lib/features/routes/data/repositories/routes_repository.dart
|
||
|
||
import '../datasources/routes_local_datasource.dart';
|
||
import '../../domain/entities/route_entities.dart';
|
||
import '../../domain/entities/haversine.dart';
|
||
|
||
class RoutesRepository {
|
||
final RoutesLocalDatasource _datasource;
|
||
|
||
RoutesRepository({RoutesLocalDatasource? datasource})
|
||
: _datasource = datasource ?? RoutesLocalDatasource();
|
||
|
||
/// Obtiene todas las rutas disponibles
|
||
Future<List<TruckRoute>> obtenerRutas() => _datasource.cargarRutas();
|
||
|
||
/// Obtiene una ruta por su ID
|
||
Future<TruckRoute?> obtenerRutaPorId(String routeId) async {
|
||
final rutas = await obtenerRutas();
|
||
try {
|
||
return rutas.firstWhere((r) => r.routeId == routeId);
|
||
} catch (_) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// Asigna la ruta más cercana a un domicilio usando Haversine.
|
||
/// Cero Google Maps — motor espacial propio.
|
||
Future<RouteAssignmentResult> asignarRuta(
|
||
double userLat,
|
||
double userLng,
|
||
) async {
|
||
final centroides = await _datasource.generarCentroides();
|
||
return Haversine.findNearestRoute(userLat, userLng, centroides);
|
||
}
|
||
|
||
/// Calcula ETA en minutos desde la posición actual del camión
|
||
/// hasta el punto más cercano al domicilio del usuario.
|
||
Future<ETAResult> calcularETA(
|
||
String routeId,
|
||
double userLat,
|
||
double userLng,
|
||
) async {
|
||
final ruta = await obtenerRutaPorId(routeId);
|
||
if (ruta == null) throw Exception('Ruta $routeId no encontrada');
|
||
|
||
// Encontrar posición más cercana al usuario en la ruta
|
||
double minDist = double.infinity;
|
||
int nearestIndex = 0;
|
||
|
||
for (int i = 0; i < ruta.positions.length - 1; i++) {
|
||
final pos = ruta.positions[i];
|
||
final d = Haversine.distanceKm(userLat, userLng, pos.lat, pos.lng);
|
||
if (d < minDist) {
|
||
minDist = d;
|
||
nearestIndex = i;
|
||
}
|
||
}
|
||
|
||
final posicionCercana = ruta.positions[nearestIndex];
|
||
final ahora = DateTime.now();
|
||
|
||
// Simular cuándo llegará el camión a esa posición
|
||
final horaLlegada = posicionCercana.timestamp;
|
||
final etaMinutos = horaLlegada.difference(ahora).inMinutes;
|
||
|
||
return ETAResult(
|
||
routeId: routeId,
|
||
routeName: ruta.name,
|
||
etaMinutos: etaMinutos.clamp(0, 999),
|
||
distanciaKm: minDist,
|
||
posicionActual: posicionCercana,
|
||
ventanaInicio: _formatHora(horaLlegada),
|
||
ventanaFin: _formatHora(horaLlegada.add(const Duration(minutes: 15))),
|
||
);
|
||
}
|
||
|
||
String _formatHora(DateTime dt) {
|
||
final hora = dt.hour > 12 ? dt.hour - 12 : dt.hour;
|
||
final minutos = dt.minute.toString().padLeft(2, '0');
|
||
final periodo = dt.hour >= 12 ? 'pm' : 'am';
|
||
return '$hora:$minutos $periodo';
|
||
}
|
||
}
|
||
|
||
class ETAResult {
|
||
final String routeId;
|
||
final String routeName;
|
||
final int etaMinutos;
|
||
final double distanciaKm;
|
||
final RoutePosition posicionActual;
|
||
final String ventanaInicio;
|
||
final String ventanaFin;
|
||
|
||
const ETAResult({
|
||
required this.routeId,
|
||
required this.routeName,
|
||
required this.etaMinutos,
|
||
required this.distanciaKm,
|
||
required this.posicionActual,
|
||
required this.ventanaInicio,
|
||
required this.ventanaFin,
|
||
});
|
||
|
||
String get ventana => '$ventanaInicio – $ventanaFin';
|
||
|
||
String get mensaje {
|
||
if (etaMinutos <= 0) return 'El camión está pasando ahora. ¡Saca tu basura!';
|
||
if (etaMinutos <= 10) return 'El camión llega en ~$etaMinutos min. ¡Saca tu basura!';
|
||
return 'El camión llega entre $ventana.';
|
||
}
|
||
}
|