diff --git a/lib/main.dart b/lib/main.dart index 3e1c1e1..42565c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,3 @@ - import 'dart:async'; import 'dart:convert'; @@ -10,7 +9,7 @@ void main() { runApp(const RecolectorApp()); } -/* ======================= JSON LOCAL DE RUTAS ======================= */ +// ======================= JSON LOCAL DE RUTAS ======================= const String rutasJson = ''' [ @@ -72,7 +71,7 @@ const String rutasJson = ''' ] '''; -/* ======================= MODELOS ======================= */ +// ======================= MODELOS ======================= class Ruta { final String id; @@ -99,15 +98,15 @@ class Ruta { factory Ruta.fromJson(Map json) { return Ruta( - id: json['id'], - zona: json['zona'], - keywords: List.from(json['keywords']), - dias: List.from(json['dias']), - inicio: json['inicio'], - fin: json['fin'], - proxima: json['proxima'], - eta: json['eta'], - consejo: json['consejo'], + id: json['id'] ?? '', + zona: json['zona'] ?? '', + keywords: List.from(json['keywords'] ?? []), + dias: List.from(json['dias'] ?? []), + inicio: json['inicio'] ?? '', + fin: json['fin'] ?? '', + proxima: json['proxima'] ?? '', + eta: json['eta'] ?? '', + consejo: json['consejo'] ?? '', ); } @@ -178,7 +177,7 @@ class Servicio { } } -/* ======================= PERSISTENCIA Y RUTAS ======================= */ +// ======================= REPOSITORIO ======================= class Repo { static List rutas() { @@ -240,7 +239,6 @@ class Repo { return list.map((e) => Domicilio.fromJson(Map.from(e))).toList(); } - // Compatibilidad con versiones anteriores del proyecto. final principal = prefs.getString('domicilio') ?? ''; final colonia = prefs.getString('colonia') ?? ''; final negocio = prefs.getString('negocio') ?? ''; @@ -305,7 +303,7 @@ class Repo { } } -/* ======================= ESTILO ======================= */ +// ======================= ESTILO ======================= class AppColors { static const green = Color(0xFF2E7D32); @@ -392,7 +390,7 @@ class AppCard extends StatelessWidget { } } -/* ======================= LOGIN ======================= */ +// ======================= LOGIN ======================= class LoginPage extends StatefulWidget { const LoginPage({super.key}); @@ -504,7 +502,7 @@ class _LoginPageState extends State { } } -/* ======================= HOME ======================= */ +// ======================= HOME ======================= class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -516,7 +514,6 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { List domicilios = []; List servicios = []; - final sugerencia = TextEditingController(); @override void initState() { @@ -536,26 +533,6 @@ class _HomePageState extends State { }); } - Future enviarSugerencia() async { - final texto = sugerencia.text.trim(); - - if (texto.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Escribe una queja o sugerencia')), - ); - return; - } - - await Repo.guardarSugerencia(texto); - sugerencia.clear(); - - if (!mounted) return; - - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Sugerencia enviada correctamente')), - ); - } - Widget menuCard({ required IconData icon, required String title, @@ -578,12 +555,6 @@ class _HomePageState extends State { ); } - @override - void dispose() { - sugerencia.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { final principal = domicilios.isEmpty ? null : domicilios.first; @@ -687,35 +658,13 @@ class _HomePageState extends State { Navigator.push(context, MaterialPageRoute(builder: (_) => const GuiaPage())); }, ), - const SectionTitle( - 'Buzón de sugerencias', - subtitle: 'Widget principal para quejas y comentarios.', - ), - AppCard( - child: Column( - children: [ - TextField( - controller: sugerencia, - minLines: 3, - maxLines: 5, - decoration: const InputDecoration( - labelText: 'Queja o sugerencia', - hintText: 'Ejemplo: el camión pasó fuera del horario indicado', - prefixIcon: Icon(Icons.feedback), - ), - ), - const SizedBox(height: 12), - SizedBox( - height: 52, - width: double.infinity, - child: FilledButton.icon( - onPressed: enviarSugerencia, - icon: const Icon(Icons.send), - label: const Text('Enviar sugerencia', style: TextStyle(fontSize: 17)), - ), - ), - ], - ), + menuCard( + icon: Icons.feedback, + title: 'Buzón de sugerencias', + subtitle: 'Envía quejas, reportes o comentarios del servicio.', + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) => const BuzonPage())); + }, ), if (servicios.isNotEmpty) ...[ const SectionTitle('Último servicio'), @@ -734,7 +683,143 @@ class _HomePageState extends State { } } -/* ======================= DATOS ======================= */ +// ======================= BUZÓN ======================= + +class BuzonPage extends StatefulWidget { + const BuzonPage({super.key}); + + @override + State createState() => _BuzonPageState(); +} + +class _BuzonPageState extends State { + final comentario = TextEditingController(); + String tipoReporte = 'Sugerencia'; + + Future enviar() async { + final texto = comentario.text.trim(); + + if (texto.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Escribe tu comentario antes de enviarlo')), + ); + return; + } + + await Repo.guardarSugerencia('[$tipoReporte] $texto'); + comentario.clear(); + + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Reporte enviado correctamente')), + ); + } + + void limpiar() { + setState(() { + tipoReporte = 'Sugerencia'; + comentario.clear(); + }); + } + + @override + void dispose() { + comentario.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Buzón de sugerencias'), + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + AppCard( + color: AppColors.softGreen, + child: Column( + children: const [ + Icon(Icons.feedback, size: 72, color: AppColors.green), + SizedBox(height: 12), + Text( + 'Tu opinión ayuda a mejorar el servicio', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900), + ), + SizedBox(height: 8), + Text( + 'Envía reportes, quejas o sugerencias sobre la recolección de basura.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16.5), + ), + ], + ), + ), + const SectionTitle('Tipo de reporte'), + DropdownButtonFormField( + value: tipoReporte, + decoration: const InputDecoration( + labelText: 'Selecciona una opción', + prefixIcon: Icon(Icons.category), + ), + items: const [ + DropdownMenuItem(value: 'Sugerencia', child: Text('Sugerencia')), + DropdownMenuItem(value: 'Queja', child: Text('Queja')), + DropdownMenuItem(value: 'Retraso del camión', child: Text('Retraso del camión')), + DropdownMenuItem(value: 'Camión no pasó', child: Text('Camión no pasó')), + DropdownMenuItem(value: 'Basura no recolectada', child: Text('Basura no recolectada')), + ], + onChanged: (value) { + setState(() { + tipoReporte = value ?? 'Sugerencia'; + }); + }, + ), + const SectionTitle('Describe el problema'), + TextField( + controller: comentario, + minLines: 5, + maxLines: 8, + decoration: const InputDecoration( + labelText: 'Comentario', + hintText: 'Ejemplo: el camión no pasó por mi domicilio el día indicado', + prefixIcon: Icon(Icons.edit_note), + ), + ), + const SizedBox(height: 16), + SizedBox( + height: 54, + child: FilledButton.icon( + onPressed: enviar, + icon: const Icon(Icons.send), + label: const Text( + 'Enviar reporte', + style: TextStyle(fontSize: 17), + ), + ), + ), + const SizedBox(height: 10), + SizedBox( + height: 54, + child: OutlinedButton.icon( + onPressed: limpiar, + icon: const Icon(Icons.cleaning_services), + label: const Text( + 'Limpiar formulario', + style: TextStyle(fontSize: 17), + ), + ), + ), + ], + ), + ); + } +} + +// ======================= DATOS ======================= class DatosPage extends StatefulWidget { const DatosPage({super.key}); @@ -1063,7 +1148,7 @@ class _DatosPageState extends State with SingleTickerProviderStateMix } } -/* ======================= SEGUIMIENTO ======================= */ +// ======================= SEGUIMIENTO ======================= enum EventoCamion { normal, retraso, averia } @@ -1543,7 +1628,7 @@ class _SeguimientoPageState extends State { } } -/* ======================= GUÍA ======================= */ +// ======================= GUÍA ======================= class GuiaPage extends StatelessWidget { const GuiaPage({super.key});