import 'package:flutter/material.dart'; import '../../core/app_colors.dart'; // ── Árbol de respuestas predefinidas ────────────────────────────────────── class _ChatNode { final String text; final List<_ChatOption> options; final bool isAnswer; const _ChatNode(this.text, this.options, {this.isAnswer = false}); } class _ChatOption { final String label; final _ChatNode next; const _ChatOption(this.label, this.next); } final _chatTree = _ChatNode('Hola, soy el asistente de Celaya Limpia. ¿En que te puedo ayudar?', [ _ChatOption('Separacion de residuos', _ChatNode('¿Que quieres saber sobre separacion?', [ _ChatOption('Como separo mi basura', _ChatNode( 'Separa tus residuos en 3 grupos:\n\n' 'ORGANICOS (bolsa verde):\nRestos de comida, cascara de huevo, pasto, hojas.\n\n' 'INORGANICOS reciclables (bolsa azul):\nPET, latas, carton limpio, vidrio.\n\n' 'NO reciclables (bolsa negra):\nPanales, papel sanitario, colillas, chicles.', [], isAnswer: true)), _ChatOption('Que NO debo mezclar', _ChatNode( 'NUNCA mezcles:\n\n' '- Pilas o baterias con basura comun\n' '- Aceite de cocina (contamina el agua)\n' '- Medicamentos vencidos\n' '- Jeringas o material medico\n' '- Electronicos con basura doméstica\n\n' 'Estos requieren manejo especial.', [], isAnswer: true)), _ChatOption('Que hago con el aceite', _ChatNode( 'El aceite de cocina usado NO va a la basura ni al drenaje.\n\n' '1. Dejalo enfriar completamente\n' '2. Guardalo en botella de PET cerrada\n' '3. Llevalo a los puntos de acopio del Dir. Gral. Servicios Municipales\n\n' 'El aceite reciclado se convierte en biodiesel.', [], isAnswer: true)), ])), _ChatOption('Residuos especiales', _ChatNode('¿Que tipo de residuo especial tienes?', [ _ChatOption('Donde dejo electronicos', _ChatNode( 'Los aparatos electronicos (celulares, computadoras, focos ahorradores) ' 'son residuos RAEE.\n\n' 'Puntos de acopio en Celaya:\n' '- Tiendas de electronica\n' '- Centros comerciales con contenedores especiales\n' '- Eventos de recoleccion del municipio\n\n' 'NUNCA los tires a la basura comun.', [], isAnswer: true)), _ChatOption('Que hago con medicamentos', _ChatNode( 'Los medicamentos vencidos son residuos peligrosos.\n\n' '- Llevalos a farmacias que tengan programa de devolucion\n' '- Algunos hospitales los reciben\n' '- Nunca los tires al drenaje ni a la basura comun\n\n' 'Contaminar el agua con medicamentos afecta a toda la comunidad.', [], isAnswer: true)), _ChatOption('Que hago con pilas y baterias', _ChatNode( 'Las pilas y baterias contienen metales pesados toxicos.\n\n' 'Depositalas en:\n' '- Supermercados (contenedores naranjas)\n' '- Tiendas de electronica\n' '- Oficinas del Dir. Gral. Servicios Municipales\n\n' '1 pila puede contaminar 600,000 litros de agua.', [], isAnswer: true)), ])), _ChatOption('Sobre el servicio de recoleccion', _ChatNode('¿Que necesitas saber?', [ _ChatOption('Cuando debo sacar la basura', _ChatNode( 'Celaya Limpia te notificara:\n\n' '1. Cuando el camion salga del relleno sanitario\n' '2. Cuando este a 30 minutos\n' '3. A 15 minutos: este es el momento de sacar tus bolsas\n\n' 'NO saques la basura antes del aviso de 15 minutos. ' 'Atrae fauna nociva y obstruye la acera.', [], isAnswer: true)), _ChatOption('El camion no paso', _ChatNode( 'Si el camion no paso en tu horario habitual:\n\n' '1. Revisa las alertas en la app (puede haber un retraso o incidente)\n' '2. Guarda tu basura bien cerrada\n' '3. Reporta la incidencia desde la seccion "Reportar"\n\n' 'El administrador revisara tu reporte y te mantendra informado.', [], isAnswer: true)), _ChatOption('Como califico el servicio', _ChatNode( 'Despues de que el camion pase por tu zona, ' 'la app te mostrara una notificacion para calificar.\n\n' 'Puedes dar de 1 a 5 estrellas y dejar un comentario.\n\n' 'Tus calificaciones ayudan a la Direccion General a identificar ' 'colonias con problemas y mejorar el servicio.', [], isAnswer: true)), ])), _ChatOption('Denuncia o emergencia', _ChatNode( 'Para situaciones urgentes:\n\n' '- Reporte de incidencias: usa la seccion "Reportar" en la app\n' '- Emergencias: llama al 911\n' '- Dir. Gral. Servicios Municipales: (461) 614-8000\n' '- SEMARNAT Guanajuato: (477) 717-2600\n\n' 'Para basura clandestina o tiraderos ilegales, reportalo al municipio.', [], isAnswer: true)), ]); // ── Pantalla del chatbot ────────────────────────────────────────────────── class ChatbotScreen extends StatefulWidget { const ChatbotScreen({super.key}); @override State createState() => _ChatbotScreenState(); } class _ChatbotScreenState extends State { final List<_Message> _messages = []; _ChatNode _current = _chatTree; final _scroll = ScrollController(); @override void initState() { super.initState(); // Mensaje inicial _messages.add(_Message(text: _chatTree.text, isBot: true)); } void _handleOption(_ChatOption option) { setState(() { // Mensaje del usuario _messages.add(_Message(text: option.label, isBot: false)); // Ir al siguiente nodo _current = option.next; _messages.add(_Message(text: _current.text, isBot: true, isAnswer: _current.isAnswer)); }); Future.delayed(const Duration(milliseconds: 100), () { _scroll.animateTo(_scroll.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); }); } void _reset() { setState(() { _messages.clear(); _current = _chatTree; _messages.add(_Message(text: _chatTree.text, isBot: true)); }); } @override Widget build(BuildContext context) => Scaffold( backgroundColor: AppColors.grisFondo, appBar: AppBar( backgroundColor: AppColors.guindaPrimary, foregroundColor: Colors.white, title: const Row(children: [ CircleAvatar(radius: 14, backgroundColor: Colors.white24, child: Icon(Icons.smart_toy, color: AppColors.dorado, size: 18)), SizedBox(width: 8), Text('Asistente Celaya Limpia'), ]), bottom: PreferredSize(preferredSize: const Size.fromHeight(4), child: Container(height: 4, color: AppColors.dorado)), actions: [ IconButton(icon: const Icon(Icons.refresh), tooltip: 'Reiniciar', onPressed: _reset), ], ), body: Column(children: [ // Mensajes Expanded( child: ListView.builder( controller: _scroll, padding: const EdgeInsets.all(12), itemCount: _messages.length, itemBuilder: (_, i) => _MessageBubble(msg: _messages[i]), ), ), // Opciones del nodo actual if (_current.options.isNotEmpty) Container( color: Colors.white, padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Selecciona una opcion:', style: TextStyle(fontSize: 11, color: AppColors.grisTexto, fontWeight: FontWeight.w500)), const SizedBox(height: 8), Wrap(spacing: 8, runSpacing: 8, children: _current.options.map((opt) => ActionChip( label: Text(opt.label, style: const TextStyle(fontSize: 12)), backgroundColor: AppColors.guindaPrimary.withOpacity(0.1), side: const BorderSide(color: AppColors.guindaPrimary), labelStyle: const TextStyle(color: AppColors.guindaPrimary), onPressed: () => _handleOption(opt), )).toList()), ], ), ) else // Botón de reiniciar al llegar a una respuesta final Container( color: Colors.white, padding: const EdgeInsets.all(12), child: SizedBox(width: double.infinity, child: OutlinedButton.icon( onPressed: _reset, style: OutlinedButton.styleFrom( foregroundColor: AppColors.guindaPrimary, side: const BorderSide(color: AppColors.guindaPrimary)), icon: const Icon(Icons.arrow_back, size: 16), label: const Text('Hacer otra pregunta'))), ), ]), ); @override void dispose() { _scroll.dispose(); super.dispose(); } } class _Message { final String text; final bool isBot; final bool isAnswer; const _Message({required this.text, required this.isBot, this.isAnswer = false}); } class _MessageBubble extends StatelessWidget { final _Message msg; const _MessageBubble({super.key, required this.msg}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: msg.isBot ? MainAxisAlignment.start : MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (msg.isBot) ...[ CircleAvatar(radius: 16, backgroundColor: AppColors.guindaPrimary, child: const Icon(Icons.smart_toy, color: Colors.white, size: 16)), const SizedBox(width: 8), ], Flexible(child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: msg.isBot ? (msg.isAnswer ? Colors.green.shade50 : Colors.white) : AppColors.guindaPrimary, borderRadius: BorderRadius.only( topLeft: const Radius.circular(16), topRight: const Radius.circular(16), bottomLeft: Radius.circular(msg.isBot ? 4 : 16), bottomRight: Radius.circular(msg.isBot ? 16 : 4), ), border: msg.isBot ? Border.all( color: msg.isAnswer ? Colors.green.shade200 : Colors.grey.shade200) : null, boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.06), blurRadius: 4, offset: const Offset(0, 2))], ), child: Text(msg.text, style: TextStyle(fontSize: 13, height: 1.5, color: msg.isBot ? AppColors.negroTexto : Colors.white)), )), if (!msg.isBot) const SizedBox(width: 8), ], ), ); } }