import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class HomeScreenPlaceholder extends StatefulWidget { const HomeScreenPlaceholder({super.key}); @override State createState() => _HomeScreenPlaceholderState(); } class _HomeScreenPlaceholderState extends State { // 1. Catálogo de Colonias tipado fuertemente con tu nuevo modelo (Mapeo de tu JSON) final List _coloniasData = [ ColoniaModel(colonia: "Zona Centro", routeId: "RUTA-01", horarioEstimado: "Matutino (06:30 - 07:15)"), ColoniaModel(colonia: "Las Arboledas", routeId: "RUTA-01", horarioEstimado: "Matutino (07:00 - 07:30)"), ColoniaModel(colonia: "Trojes", routeId: "RUTA-13", horarioEstimado: "Matutino (06:40 - 07:10)"), ColoniaModel(colonia: "San Juanico", routeId: "RUTA-03", horarioEstimado: "Matutino (06:45 - 07:15)"), ColoniaModel(colonia: "Los Olivos", routeId: "RUTA-04", horarioEstimado: "Matutino (07:00 - 07:40)"), ColoniaModel(colonia: "Rancho Seco", routeId: "RUTA-05", horarioEstimado: "Vespertino (14:15 - 15:00)"), ColoniaModel(colonia: "Las Insurgentes", routeId: "RUTA-12", horarioEstimado: "Matutino (06:35 - 07:10)") ]; // 2. Diccionario de Telemetría Satelital de Celaya (Mapeo de tus imágenes) final Map> _routesTelemetry = { "RUTA-01": { "name": "Zona Centro - Las Arboledas", "truckId": 101, "positions": [ {"positionId": 1, "lat": 20.5111, "lng": -100.9037, "speed": 0, "desc": "Salida Relleno Sanitario", "mins": "45"}, {"positionId": 2, "lat": 20.5185, "lng": -100.8450, "speed": 45, "desc": "En trayecto principal", "mins": "30"}, {"positionId": 3, "lat": 20.5215, "lng": -100.8142, "speed": 22, "desc": "Ingresando a zona Centro", "mins": "20"}, {"positionId": 4, "lat": 20.5212, "lng": -100.8175, "speed": 15, "desc": "Punto Previo Destino (<15 min)", "mins": "12"}, {"positionId": 5, "lat": 20.5210, "lng": -100.8210, "speed": 0, "desc": "Recolección Activa de Residuos", "mins": "5"}, {"positionId": 6, "lat": 20.5235, "lng": -100.8212, "speed": 18, "desc": "Avanzando sector Arboledas", "mins": "3"}, {"positionId": 7, "lat": 20.5260, "lng": -100.8215, "speed": 20, "desc": "Última parada del circuito", "mins": "1"}, {"positionId": 8, "lat": 20.5111, "lng": -100.9037, "speed": 40, "desc": "Retorno al Basurero Municipal", "mins": "0"} ] }, "RUTA-03": { "name": "Sector Poniente - San Juanico", "truckId": 103, "positions": [ {"positionId": 1, "lat": 20.5111, "lng": -100.9037, "speed": 0, "desc": "Salida Base de Monitoreo", "mins": "40"}, {"positionId": 2, "lat": 20.5250, "lng": -100.8510, "speed": 42, "desc": "Vía Rápida Poniente", "mins": "25"}, {"positionId": 3, "lat": 20.5290, "lng": -100.8320, "speed": 20, "desc": "Eje Norponiente", "mins": "18"}, {"positionId": 4, "lat": 20.5315, "lng": -100.8355, "speed": 15, "desc": "Avenida San Juanico", "mins": "10"}, {"positionId": 5, "lat": 20.5340, "lng": -100.8390, "speed": 0, "desc": "Vaciado de Contenedores Urbano", "mins": "6"}, {"positionId": 8, "lat": 20.5111, "lng": -100.9037, "speed": 35, "desc": "Retorno General", "mins": "0"} ] }, "RUTA-04": { "name": "Oriente - Los Olivos", "truckId": 104, "positions": [ {"positionId": 1, "lat": 20.5111, "lng": -100.9037, "speed": 0, "desc": "Encendido de Unidad", "mins": "50"}, {"positionId": 4, "lat": 20.5320, "lng": -100.7850, "speed": 12, "desc": "Proximidad Los Olivos", "mins": "14"}, {"positionId": 5, "lat": 20.5350, "lng": -100.7790, "speed": 0, "desc": "Recolección Casa por Casa", "mins": "8"}, {"positionId": 8, "lat": 20.5111, "lng": -100.9037, "speed": 48, "desc": "Retorno a Relleno Sanitario", "mins": "0"} ] } }; int _activePositionIndex = 0; // 📍 Historial tipado de forma segura con el modelo de notificaciones push final List _pushNotificationsLog = []; String _userColonia = "Zona Centro"; bool _isInitialized = false; @override void initState() { super.initState(); _userColonia = "Zona Centro"; // Forzado de respaldo seguro _isInitialized = true; WidgetsBinding.instance.addPostFrameCallback((_) { _triggerNotificationCheck(); }); } @override void didChangeDependencies() { super.didChangeDependencies(); try { final uri = GoRouterState.of(context).uri; final String? coloniaParam = uri.queryParameters['colonia']; if (coloniaParam != null && coloniaParam.isNotEmpty && _coloniasData.any((e) => e.colonia.toLowerCase() == coloniaParam.toLowerCase())) { final elementoEncontrado = _coloniasData.firstWhere( (e) => e.colonia.toLowerCase() == coloniaParam.toLowerCase() ); setState(() { _userColonia = elementoEncontrado.colonia; }); } } catch (e) { // Manejo silencioso de GoRouter } } // Lógica interactiva que dispara las notificaciones basadas en tu modelo real void _triggerNotificationCheck() { final String currentRouteId = _getColoniaInfo().routeId; if (!_routesTelemetry.containsKey(currentRouteId)) return; final telemetry = _routesTelemetry[currentRouteId]!; final currentPos = (telemetry["positions"] as List)[_activePositionIndex]; final int pId = currentPos["positionId"]; NotificationModel? newAlert; if (pId == 2) { newAlert = NotificationModel( triggerEvent: "ROUTE_START", condition: "Cuando positionId cambia de 1 a 2", pushPayload: PushPayloadModel( title: "¡Ruta Iniciada!", body: "El camión recolector ha salido del Relleno Sanitario rumbo a tu sector. Asegúrate de tener listos tus residuos." ) ); } else if (pId == 4) { newAlert = NotificationModel( triggerEvent: "TRUCK_PROXIMITY", condition: "Cuando positionId llega a 4 (punto previo al destino)", pushPayload: PushPayloadModel( title: "Camión Cercano", body: "El camión está a menos de 15 minutos de tu domicilio. Es momento de sacar tus bolsas a la acera." ) ); } else if (pId == 8) { newAlert = NotificationModel( triggerEvent: "ROUTE_COMPLETED", condition: "Cuando positionId llega a 8 (retorno al basurero)", pushPayload: PushPayloadModel( title: "Servicio Finalizado", body: "El camión de tu sector ha concluido su jornada de recolección diaria." ) ); } if (newAlert != null) { setState(() { _pushNotificationsLog.insert(0, newAlert!); }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('🔔 Push: ${newAlert.pushPayload.title}'), backgroundColor: pId == 4 ? Colors.amber.shade900 : (pId == 8 ? Colors.blue.shade700 : Colors.green.shade700), duration: const Duration(seconds: 2), ), ); } } ColoniaModel _getColoniaInfo() { return _coloniasData.firstWhere( (element) => element.colonia == _userColonia, orElse: () => _coloniasData.first, ); } void _nextSimulationStep() { final String currentRouteId = _getColoniaInfo().routeId; if (!_routesTelemetry.containsKey(currentRouteId)) return; final positionsList = _routesTelemetry[currentRouteId]!["positions"] as List; if (_activePositionIndex < positionsList.length - 1) { setState(() { _activePositionIndex++; }); _triggerNotificationCheck(); } } @override Widget build(BuildContext context) { final coloniaInfo = _getColoniaInfo(); final String routeId = coloniaInfo.routeId; final telemetry = _routesTelemetry[routeId]!; final currentPositionData = (telemetry["positions"] as List)[_activePositionIndex]; return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( title: Row( children: const [ Icon(Icons.recycling, color: Color(0xFF2E7D32)), SizedBox(width: 8), Text('WasteNotify', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.black)), ], ), backgroundColor: Colors.white, elevation: 0, actions: [ IconButton( icon: const Icon(Icons.logout, color: Colors.black), onPressed: () => context.go('/login'), ) ], ), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // --- 1. TARJETA DE BIENVENIDA --- Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: const Color(0xFF1B5E20), borderRadius: BorderRadius.circular(20), ), child: Row( children: [ const CircleAvatar( backgroundColor: Colors.white24, radius: 24, child: Icon(Icons.person, color: Colors.white, size: 28), ), const SizedBox(width: 16), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('¡Bienvenido!', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration(color: Colors.white24, borderRadius: BorderRadius.circular(12)), child: const Text('Ciudadano', style: TextStyle(color: Colors.white, fontSize: 12)), ), ], ), ], ), ), const SizedBox(height: 16), // --- 2. RELOJ DE TIEMPO ESTIMADO INTERACTIVO --- Card( color: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), elevation: 1, child: Padding( padding: const EdgeInsets.all(20.0), child: Column( children: [ Row( children: const [ Icon(Icons.access_time, color: Color(0xFF2E7D32)), SizedBox(width: 10), Text('Tiempo estimado de llegada', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black)), ], ), const SizedBox(height: 16), Container( width: 110, height: 110, decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(color: const Color(0xFFE0E0E0), width: 3)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${currentPositionData["mins"]}', style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.black)), Text('— min', style: TextStyle(fontSize: 14, color: Colors.grey.shade600)), ], ), ), const SizedBox(height: 12), Text('Estado GPS: ${currentPositionData["desc"]}', textAlign: TextAlign.center, style: TextStyle(fontSize: 13, color: Colors.grey.shade600, fontStyle: FontStyle.italic)), ], ), ), ), const SizedBox(height: 16), // --- 3. BOTÓN DE SIMULACIÓN PARA EL MVP --- ElevatedButton.icon( style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFF2E7D32), padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))), onPressed: _activePositionIndex < (telemetry['positions'] as List).length - 1 ? _nextSimulationStep : null, icon: const Icon(Icons.play_arrow, color: Colors.white), label: const Text('Simular Avance del Camión (GPS)', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)), ), const SizedBox(height: 16), // --- 4. BANDEJA DE ALERTAS REALES RECIBIDAS (PARSED) --- if (_pushNotificationsLog.isNotEmpty) ...[ const Text('🔔 Alertas Push en Vivo', style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black)), const SizedBox(height: 8), ..._pushNotificationsLog.map((log) { final bool isWarning = log.triggerEvent == 'TRUCK_PROXIMITY'; final bool isDone = log.triggerEvent == 'ROUTE_COMPLETED'; final Color cardColor = isWarning ? Colors.amber.shade900 : (isDone ? Colors.blue.shade700 : Colors.green.shade700); return Card( margin: const EdgeInsets.symmetric(vertical: 4), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: ListTile( leading: CircleAvatar(backgroundColor: cardColor.withOpacity(0.12), child: Icon(isWarning ? Icons.notification_important : (isDone ? Icons.check_circle : Icons.local_shipping), color: cardColor)), title: Text(log.pushPayload.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), subtitle: Text(log.pushPayload.body, style: const TextStyle(fontSize: 12)), ), ); }).toList(), const SizedBox(height: 16), ], // --- 5. PANEL DE RECOMENDACIONES --- Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFFFE0B2))), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: const [Icon(Icons.campaign, color: Colors.orange, size: 22), SizedBox(width: 8), Text('Recuerda siempre', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.brown, fontSize: 14))]), const SizedBox(height: 12), _buildBulletRow(Icons.delete_outline, 'Saca la basura SOLO cuando recibas la alerta de "próxima llegada".'), const SizedBox(height: 8), _buildBulletRow(Icons.block, 'Nunca persigas ni te acerques al camión. El sistema te avisará a tiempo.'), const SizedBox(height: 8), _buildBulletRow(Icons.eco_outlined, 'Separar tus residuos hace más eficiente la recolección. ¡Gracias!'), ], ), ), const SizedBox(height: 20), ], ), ), ); } Widget _buildBulletRow(IconData icon, String text) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 16, color: Colors.brown.shade700), const SizedBox(width: 10), Expanded(child: Text(text, style: TextStyle(fontSize: 13, color: Colors.brown.shade900, height: 1.2))), ], ); } } // ========================================================================= // 📍 MODELO 1: Catálogo de Colonias // ========================================================================= class ColoniaModel { String colonia; String routeId; String horarioEstimado; ColoniaModel({required this.colonia, required this.routeId, required this.horarioEstimado}); factory ColoniaModel.fromJson(Map json) => ColoniaModel(colonia: json['colonia'], routeId: json['routeId'], horarioEstimado: json['horarioEstimado']); Map toJson() => {'colonia': colonia, 'routeId': routeId, 'horarioEstimado': horarioEstimado}; } // ========================================================================= // 📍 MODELO 2: Sistema de Notificaciones Alertas Push // ========================================================================= class NotificationModel { String triggerEvent; String condition; PushPayloadModel pushPayload; NotificationModel({required this.triggerEvent, required this.condition, required this.pushPayload}); factory NotificationModel.fromJson(Map json) => NotificationModel(triggerEvent: json['triggerEvent'], condition: json['condition'], pushPayload: PushPayloadModel.fromJson(json['pushPayload'])); Map toJson() => {'triggerEvent': triggerEvent, 'condition': condition, 'pushPayload': pushPayload.toJson()}; } class PushPayloadModel { String title; String body; PushPayloadModel({required this.title, required this.body}); factory PushPayloadModel.fromJson(Map json) => PushPayloadModel(title: json['title'], body: json['body']); Map toJson() => {'title': title, 'body': body}; }