Co-authored-by: MENDOZA BALLARDO GAEL RICARDO <gael-meb123@users.noreply.github.com>

version final final ya enserio la final del proyecto :)
This commit is contained in:
shinra32
2026-05-23 08:42:27 -06:00
parent 92f570294a
commit 56c51378b8
10 changed files with 464 additions and 289 deletions

View File

@@ -52,6 +52,7 @@ class _EtaResult {
mensaje.contains('15 minutos') || mensaje.contains('Está atendiendo');
double get progreso {
if (status == 'diferida' || status == 'reasignada') return 0.0;
if (isNearby) return 0.85;
if (isCompleted) return 1.0;
return 0.35;
@@ -60,6 +61,7 @@ class _EtaResult {
/// Índice para el widget ProgressSteps (0 = inicio, 1 = en ruta, 2 = cerca,
/// 3 = atendiendo, 4 = completado). Ajusta los valores según tu enum real.
int get stepIndex {
if (status == 'diferida' || status == 'reasignada') return 0;
if (isCompleted) return 4;
if (isNearby) return 3;
if (status == 'en_ruta') return 2;
@@ -90,7 +92,13 @@ class _EtaNotifier extends AsyncNotifier<_EtaResult> {
}
Future<void> refresh() async {
state = await AsyncValue.guard(_fetch);
try {
final newData = await _fetch();
state = AsyncValue.data(newData);
} catch (e) {
// HACKATHON: Si hay un micro-corte (backend reiniciando), conservamos los datos previos
if (!state.hasValue) state = const AsyncValue.loading();
}
}
Future<_EtaResult> _fetch() async {
@@ -111,6 +119,23 @@ class _EtaNotifier extends AsyncNotifier<_EtaResult> {
if (items.isEmpty) return const _EtaResult.noAddress();
final addressId = items.first['id'] as String;
final rawRoute = items.first['route_id'] ?? items.first['routeId'] ?? items.first['route'];
String? routeId = rawRoute?.toString();
// 🚨 HACKATHON FALLBACK: Si el backend no envía la ruta, la deducimos por la colonia
if (routeId == null || routeId.isEmpty) {
final col = items.first['colonia']?.toString().toLowerCase() ?? '';
if (col.contains('centro')) routeId = 'RUTA-01';
else if (col.contains('arboledas')) routeId = 'RUTA-03';
else if (col.contains('juanico')) routeId = 'RUTA-04';
else if (col.contains('olivos')) routeId = 'RUTA-05';
else if (col.contains('seco')) routeId = 'RUTA-12';
else if (col.contains('insurgentes')) routeId = 'RUTA-13';
}
Future.microtask(() {
ref.read(activeRouteIdProvider.notifier).set(routeId);
});
final etaResp = await dio.get<dynamic>(
'/eta',
queryParameters: {'address_id': addressId},
@@ -211,10 +236,7 @@ class _CitizenHomeScreenState extends ConsumerState<CitizenHomeScreen>
),
body: etaAsync.when(
loading: () => const _EtaLoading(),
error: (e, _) => _EtaError(
error: e.toString(),
onRetry: () => ref.read(etaProvider.notifier).refresh(),
),
error: (e, _) => const _EtaLoading(), // Si hay error, mostramos carga infinita hasta que el backend despierte
data: (result) => result.hasAddress
? _EtaContent(result: result)
: _NoAddressState(onAdd: () => context.go('/addresses/new')),
@@ -259,8 +281,10 @@ class _EtaContent extends StatelessWidget {
),
const SizedBox(height: 12),
// ── 3. Pasos de progreso (justo debajo del domicilio) ───────────
ProgressSteps(stepIndex: result.stepIndex),
const SizedBox(height: 12),
if (result.status != 'diferida') ...[
ProgressSteps(stepIndex: result.stepIndex),
const SizedBox(height: 12),
],
// ── 4. Banner de prevención ─────────────────────────────────────
const PreventionBanner(),
@@ -413,12 +437,14 @@ class _EtaHeroCard extends StatelessWidget {
Color _bgColor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
if (result.status == 'diferida') return const Color(0xFFFFEBEE); // Alerta roja suave
if (result.isCompleted) return cs.surfaceContainerHighest;
if (result.isNearby) return const Color(0xFFFFF8E1); // amber-50
return const Color(0xFFE8D5DB); // rosa claro institucional
}
Color _accentColor(BuildContext context) {
if (result.status == 'diferida') return AppTheme.danger; // Rojo de alerta
if (result.isCompleted) return Theme.of(context).colorScheme.outline;
if (result.isNearby) return const Color(0xFFC8A36A); // beige dorado
return const Color(0xFF9B1B4A); // vino principal
@@ -496,29 +522,51 @@ class _EtaHeroCard extends StatelessWidget {
const SizedBox(height: 16),
// Barra de progreso
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: result.progreso,
backgroundColor: accent.withOpacity(0.2),
valueColor: AlwaysStoppedAnimation<Color>(accent),
minHeight: 8,
if (result.status != 'diferida') ...[
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: result.progreso,
backgroundColor: accent.withOpacity(0.2),
valueColor: AlwaysStoppedAnimation<Color>(accent),
minHeight: 8,
),
),
),
const SizedBox(height: 6),
Row(
children: [
Text(
'Inicio de ruta',
style: TextStyle(fontSize: 10, color: accent.withOpacity(0.7)),
const SizedBox(height: 6),
Row(
children: [
Text(
'Inicio de ruta',
style: TextStyle(fontSize: 10, color: accent.withOpacity(0.7)),
),
const Spacer(),
Text(
'Tu casa',
style: TextStyle(fontSize: 10, color: accent.withOpacity(0.7)),
),
],
),
] else ...[
Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Icon(Icons.access_time_filled, size: 16, color: accent),
const SizedBox(width: 8),
Expanded(
child: Text(
'Servicio matutino suspendido. Se retomará en la tarde.',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: accent,
),
),
),
],
),
const Spacer(),
Text(
'Tu casa',
style: TextStyle(fontSize: 10, color: accent.withOpacity(0.7)),
),
],
),
),
],
],
),
);
@@ -548,7 +596,11 @@ class _StatusPill extends StatelessWidget {
@override
Widget build(BuildContext context) {
final label = result.isNearby
final label = result.status == 'diferida'
? 'Servicio interrumpido'
: result.status == 'reasignada'
? 'Ruta reasignada'
: result.isNearby
? 'Cerca de tu domicilio'
: result.isCompleted
? 'Servicio completado'
@@ -557,8 +609,8 @@ class _StatusPill extends StatelessWidget {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!result.isCompleted) _PulsingDot(color: accent),
if (!result.isCompleted) const SizedBox(width: 6),
if (!result.isCompleted && result.status != 'diferida') _PulsingDot(color: accent),
if (!result.isCompleted && result.status != 'diferida') const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(