Avance del programa

This commit is contained in:
2026-05-23 07:39:29 -06:00
parent c6a1a67469
commit ebce0badde
6 changed files with 1098 additions and 430 deletions

View File

@@ -35,6 +35,7 @@ class _CitizenHomeScreenState extends State<CitizenHomeScreen> {
_HomeTab(auth: auth, sim: sim),
const CitizenGuiaScreen(),
const CitizenReporteScreen(),
const ChatbotScreen(),
];
return Scaffold(
@@ -60,6 +61,8 @@ class _CitizenHomeScreenState extends State<CitizenHomeScreen> {
selectedIcon:Icon(Icons.eco,color:AppColors.guindaPrimary),label:'Guía'),
NavigationDestination(icon:Icon(Icons.report_outlined),
selectedIcon:Icon(Icons.report,color:AppColors.guindaPrimary),label:'Reportar'),
NavigationDestination(icon:Icon(Icons.support_agent_outlined),
selectedIcon:Icon(Icons.support_agent,color:AppColors.guindaPrimary),label:'Asistente'),
],
),
);

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:image_picker/image_picker.dart';
import '../../core/app_colors.dart';
import '../shared/reporte_chat_screen.dart';
import '../../database/db_helper.dart';
import '../../models/models.dart';
import '../../services/auth_service.dart';
@@ -66,32 +67,44 @@ class _CitizenReporteScreenState extends State<CitizenReporteScreen> {
Future<void> _send() async {
final auth = context.read<AuthService>();
if (auth.currentUser == null || _desc.text.trim().isEmpty) {
if (auth.currentUser == null) return;
if (_desc.text.trim().isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Describe el problema'),
content: Text('Describe el problema para poder enviar el reporte'),
backgroundColor: AppColors.rojoError));
return;
}
setState(() => _loading = true);
final db = await DbHelper.database;
await db.insert('reportes', {
'user_id': auth.currentUser!.id,
'tipo': _tipo,
'descripcion': _desc.text.trim(),
'colonia': auth.primaryDomicilio?.colonia ?? '',
'route_id': auth.primaryDomicilio?.routeId ?? '',
'fecha': DateTime.now().toIso8601String(),
'estado': 'PENDIENTE',
'calificacion': _calif,
'foto_path': _foto?.path,
});
try {
// Insertar reporte directo en la BD
final db = await DbHelper.database;
final id = await db.insert('reportes', {
'user_id': auth.currentUser!.id,
'tipo': _tipo,
'descripcion': _desc.text.trim(),
'colonia': auth.primaryDomicilio?.colonia ?? 'Sin colonia',
'route_id': auth.primaryDomicilio?.routeId ?? '',
'fecha': DateTime.now().toIso8601String(),
'estado': 'PENDIENTE',
'calificacion': _calif,
'foto_path': _foto?.path,
});
await _load();
if (!mounted) return;
setState(() { _loading = false; _sent = true; _desc.clear(); _foto = null; });
await Future.delayed(const Duration(seconds: 2));
if (mounted) setState(() => _sent = false);
if (id <= 0) throw Exception('No se pudo guardar el reporte');
await _load();
_desc.clear();
setState(() { _foto = null; _loading = false; _sent = true; });
await Future.delayed(const Duration(seconds: 3));
if (mounted) setState(() => _sent = false);
} catch (e) {
if (!mounted) return;
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Error al enviar: $e'),
backgroundColor: AppColors.rojoError));
}
}
@override
@@ -104,12 +117,18 @@ class _CitizenReporteScreenState extends State<CitizenReporteScreen> {
child: Container(height: 4, color: AppColors.dorado))),
body: _sent
? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
const Icon(Icons.check_circle, color: AppColors.verdeExito, size: 64),
const SizedBox(height: 12),
const Text('Reporte enviado', style: TextStyle(fontSize: 20,
const Icon(Icons.check_circle, color: AppColors.verdeExito, size: 72),
const SizedBox(height: 16),
const Text('Reporte enviado', style: TextStyle(fontSize: 22,
fontWeight: FontWeight.bold, color: AppColors.verdeExito)),
const Text('El Ayuntamiento lo revisara pronto.',
const SizedBox(height: 8),
const Text('El Ayuntamiento revisara tu reporte pronto.',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.grisTexto)),
const SizedBox(height: 8),
const Text('Podras chatear con ellos desde "Mis Reportes".',
textAlign: TextAlign.center,
style: TextStyle(color: AppColors.grisTexto, fontSize: 12)),
]))
: SingleChildScrollView(padding: const EdgeInsets.all(16), child: Column(children: [
Card(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -190,21 +209,45 @@ class _CitizenReporteScreenState extends State<CitizenReporteScreen> {
child: Text('Mis Reportes', style: TextStyle(fontWeight: FontWeight.bold,
color: AppColors.guindaPrimary, fontSize: 15))),
const SizedBox(height: 8),
..._reportes.map((r) => Card(margin: const EdgeInsets.only(bottom: 6),
child: ListTile(dense: true,
..._reportes.map((r) {
final isClosed = r.estado == 'COMPLETADO';
final id = r.id ?? 0;
final folio = 'RPT-${id.toString().padLeft(5, "0")}';
return Card(margin: const EdgeInsets.only(bottom: 6),
child: Column(children: [
ListTile(dense: true,
leading: CircleAvatar(backgroundColor: AppColors.guindaPrimary, radius: 16,
child: const Icon(Icons.report, color: Colors.white, size: 16)),
title: Text(_tipos[r.tipo] ?? r.tipo,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
subtitle: Text(r.descripcion, maxLines: 1,
overflow: TextOverflow.ellipsis,
title: Row(children: [
Expanded(child: Text(_tipos[r.tipo] ?? r.tipo,
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600))),
Text(folio, style: const TextStyle(fontSize: 9, color: AppColors.grisTexto)),
]),
subtitle: Text(r.descripcion, maxLines: 1, overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 11)),
trailing: Container(padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: _estadoColor(r.estado).withOpacity(0.15),
decoration: BoxDecoration(color: _estadoColor(r.estado).withOpacity(0.15),
borderRadius: BorderRadius.circular(10)),
child: Text(r.estado, style: TextStyle(fontSize: 9,
color: _estadoColor(r.estado), fontWeight: FontWeight.bold)))))),
child: Text(r.estado.replaceAll('_',' '),
style: TextStyle(fontSize: 9, color: _estadoColor(r.estado),
fontWeight: FontWeight.bold)))),
if (r.id != null) Padding(
padding: const EdgeInsets.fromLTRB(14, 0, 14, 8),
child: SizedBox(width: double.infinity,
child: OutlinedButton.icon(
onPressed: () => Navigator.push(context, MaterialPageRoute(
builder: (_) => ReporteChatScreen(
reporteId: r.id!, folio: folio, isClosed: isClosed))),
style: OutlinedButton.styleFrom(
foregroundColor: isClosed ? AppColors.grisTexto : AppColors.guindaPrimary,
side: BorderSide(color: isClosed ? AppColors.grisTexto : AppColors.guindaPrimary),
padding: const EdgeInsets.symmetric(vertical: 6),
minimumSize: Size.zero),
icon: Icon(isClosed ? Icons.lock : Icons.chat_bubble_outline, size: 14),
label: Text(isClosed ? 'Chat cerrado' : 'Escribir al Ayuntamiento',
style: const TextStyle(fontSize: 11))))),
]));
}),
],
])),
);