Actualiza buzon de sugerencias

This commit is contained in:
Erick Cesar Mondragon Palacios
2026-05-22 19:49:02 -06:00
parent 99cd398189
commit 9e9d423b74

View File

@@ -1,4 +1,3 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
@@ -10,7 +9,7 @@ void main() {
runApp(const RecolectorApp()); runApp(const RecolectorApp());
} }
/* ======================= JSON LOCAL DE RUTAS ======================= */ // ======================= JSON LOCAL DE RUTAS =======================
const String rutasJson = ''' const String rutasJson = '''
[ [
@@ -72,7 +71,7 @@ const String rutasJson = '''
] ]
'''; ''';
/* ======================= MODELOS ======================= */ // ======================= MODELOS =======================
class Ruta { class Ruta {
final String id; final String id;
@@ -99,15 +98,15 @@ class Ruta {
factory Ruta.fromJson(Map<String, dynamic> json) { factory Ruta.fromJson(Map<String, dynamic> json) {
return Ruta( return Ruta(
id: json['id'], id: json['id'] ?? '',
zona: json['zona'], zona: json['zona'] ?? '',
keywords: List<String>.from(json['keywords']), keywords: List<String>.from(json['keywords'] ?? []),
dias: List<String>.from(json['dias']), dias: List<String>.from(json['dias'] ?? []),
inicio: json['inicio'], inicio: json['inicio'] ?? '',
fin: json['fin'], fin: json['fin'] ?? '',
proxima: json['proxima'], proxima: json['proxima'] ?? '',
eta: json['eta'], eta: json['eta'] ?? '',
consejo: json['consejo'], consejo: json['consejo'] ?? '',
); );
} }
@@ -178,7 +177,7 @@ class Servicio {
} }
} }
/* ======================= PERSISTENCIA Y RUTAS ======================= */ // ======================= REPOSITORIO =======================
class Repo { class Repo {
static List<Ruta> rutas() { static List<Ruta> rutas() {
@@ -240,7 +239,6 @@ class Repo {
return list.map((e) => Domicilio.fromJson(Map<String, dynamic>.from(e))).toList(); return list.map((e) => Domicilio.fromJson(Map<String, dynamic>.from(e))).toList();
} }
// Compatibilidad con versiones anteriores del proyecto.
final principal = prefs.getString('domicilio') ?? ''; final principal = prefs.getString('domicilio') ?? '';
final colonia = prefs.getString('colonia') ?? ''; final colonia = prefs.getString('colonia') ?? '';
final negocio = prefs.getString('negocio') ?? ''; final negocio = prefs.getString('negocio') ?? '';
@@ -305,7 +303,7 @@ class Repo {
} }
} }
/* ======================= ESTILO ======================= */ // ======================= ESTILO =======================
class AppColors { class AppColors {
static const green = Color(0xFF2E7D32); static const green = Color(0xFF2E7D32);
@@ -392,7 +390,7 @@ class AppCard extends StatelessWidget {
} }
} }
/* ======================= LOGIN ======================= */ // ======================= LOGIN =======================
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({super.key}); const LoginPage({super.key});
@@ -504,7 +502,7 @@ class _LoginPageState extends State<LoginPage> {
} }
} }
/* ======================= HOME ======================= */ // ======================= HOME =======================
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({super.key}); const HomePage({super.key});
@@ -516,7 +514,6 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
List<Domicilio> domicilios = []; List<Domicilio> domicilios = [];
List<Servicio> servicios = []; List<Servicio> servicios = [];
final sugerencia = TextEditingController();
@override @override
void initState() { void initState() {
@@ -536,26 +533,6 @@ class _HomePageState extends State<HomePage> {
}); });
} }
Future<void> 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({ Widget menuCard({
required IconData icon, required IconData icon,
required String title, required String title,
@@ -578,12 +555,6 @@ class _HomePageState extends State<HomePage> {
); );
} }
@override
void dispose() {
sugerencia.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final principal = domicilios.isEmpty ? null : domicilios.first; final principal = domicilios.isEmpty ? null : domicilios.first;
@@ -687,35 +658,13 @@ class _HomePageState extends State<HomePage> {
Navigator.push(context, MaterialPageRoute(builder: (_) => const GuiaPage())); Navigator.push(context, MaterialPageRoute(builder: (_) => const GuiaPage()));
}, },
), ),
const SectionTitle( menuCard(
'Buzón de sugerencias', icon: Icons.feedback,
subtitle: 'Widget principal para quejas y comentarios.', title: 'Buzón de sugerencias',
), subtitle: 'Envía quejas, reportes o comentarios del servicio.',
AppCard( onTap: () {
child: Column( Navigator.push(context, MaterialPageRoute(builder: (_) => const BuzonPage()));
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)),
),
),
],
),
), ),
if (servicios.isNotEmpty) ...[ if (servicios.isNotEmpty) ...[
const SectionTitle('Último servicio'), const SectionTitle('Último servicio'),
@@ -734,7 +683,143 @@ class _HomePageState extends State<HomePage> {
} }
} }
/* ======================= DATOS ======================= */ // ======================= BUZÓN =======================
class BuzonPage extends StatefulWidget {
const BuzonPage({super.key});
@override
State<BuzonPage> createState() => _BuzonPageState();
}
class _BuzonPageState extends State<BuzonPage> {
final comentario = TextEditingController();
String tipoReporte = 'Sugerencia';
Future<void> 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<String>(
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 { class DatosPage extends StatefulWidget {
const DatosPage({super.key}); const DatosPage({super.key});
@@ -1063,7 +1148,7 @@ class _DatosPageState extends State<DatosPage> with SingleTickerProviderStateMix
} }
} }
/* ======================= SEGUIMIENTO ======================= */ // ======================= SEGUIMIENTO =======================
enum EventoCamion { normal, retraso, averia } enum EventoCamion { normal, retraso, averia }
@@ -1543,7 +1628,7 @@ class _SeguimientoPageState extends State<SeguimientoPage> {
} }
} }
/* ======================= GUÍA ======================= */ // ======================= GUÍA =======================
class GuiaPage extends StatelessWidget { class GuiaPage extends StatelessWidget {
const GuiaPage({super.key}); const GuiaPage({super.key});