Commit final
This commit is contained in:
@@ -62,11 +62,11 @@ class RouteStatus {
|
|||||||
|
|
||||||
static String label(String status) {
|
static String label(String status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case enRuta: return '🚛 En Ruta';
|
case enRuta: return ' En Ruta';
|
||||||
case cancelada: return '❌ Cancelada';
|
case cancelada: return ' Cancelada';
|
||||||
case retrasada: return '⏱️ Retrasada';
|
case retrasada: return ' Retrasada';
|
||||||
case fallaMecanica: return '🔧 Falla Mecánica';
|
case fallaMecanica: return ' Falla Mecánica';
|
||||||
case finalizada: return '✅ Finalizada';
|
case finalizada: return ' Finalizada';
|
||||||
default: return status;
|
default: return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ class _AdminHomeTabState extends State<_AdminHomeTab> {
|
|||||||
updatedAt: DateTime.now().toIso8601String()));
|
updatedAt: DateTime.now().toIso8601String()));
|
||||||
|
|
||||||
if (status == RouteStatus.cancelada || status == RouteStatus.fallaMecanica || status == RouteStatus.retrasada) {
|
if (status == RouteStatus.cancelada || status == RouteStatus.fallaMecanica || status == RouteStatus.retrasada) {
|
||||||
final emoji = status == RouteStatus.cancelada ? '❌'
|
final emoji = status == RouteStatus.cancelada ? ''
|
||||||
: status == RouteStatus.fallaMecanica ? '🔧' : '⏱️';
|
: status == RouteStatus.fallaMecanica ? '' : '';
|
||||||
final titulo = status == RouteStatus.cancelada ? 'Ruta Cancelada'
|
final titulo = status == RouteStatus.cancelada ? 'Ruta Cancelada'
|
||||||
: status == RouteStatus.fallaMecanica ? 'Falla Mecánica' : 'Servicio con Retraso';
|
: status == RouteStatus.fallaMecanica ? 'Falla Mecánica' : 'Servicio con Retraso';
|
||||||
final cuerpo = (msg != null && msg.isNotEmpty)
|
final cuerpo = (msg != null && msg.isNotEmpty)
|
||||||
@@ -172,7 +172,7 @@ class _AdminHomeTabState extends State<_AdminHomeTab> {
|
|||||||
final status = _getStatus(r.routeId);
|
final status = _getStatus(r.routeId);
|
||||||
final mensaje = _getMensaje(r.routeId);
|
final mensaje = _getMensaje(r.routeId);
|
||||||
final gpsOk = widget.sim.isGpsActive(r.routeId);
|
final gpsOk = widget.sim.isGpsActive(r.routeId);
|
||||||
final nightIcon = r.turno == 'NOCTURNO' ? '🌙 ' : r.turno == 'VESPERTINO' ? '🌅 ' : '🌄 ';
|
final nightIcon = r.turno == 'NOCTURNO' ? ' ' : r.turno == 'VESPERTINO' ? ' ' : ' ';
|
||||||
final incidentes = _getIncidentesPorRuta(r.routeId);
|
final incidentes = _getIncidentesPorRuta(r.routeId);
|
||||||
|
|
||||||
return Card(margin: const EdgeInsets.only(bottom: 10),
|
return Card(margin: const EdgeInsets.only(bottom: 10),
|
||||||
@@ -190,7 +190,7 @@ class _AdminHomeTabState extends State<_AdminHomeTab> {
|
|||||||
Text(RouteStatus.label(status),
|
Text(RouteStatus.label(status),
|
||||||
style: TextStyle(fontSize: 11, color: RouteStatus.color(status), fontWeight: FontWeight.w600)),
|
style: TextStyle(fontSize: 11, color: RouteStatus.color(status), fontWeight: FontWeight.w600)),
|
||||||
if (!gpsOk)
|
if (!gpsOk)
|
||||||
const Text('📡 Sin GPS', style: TextStyle(fontSize: 10, color: AppColors.rojoError)),
|
const Text(' Sin GPS', style: TextStyle(fontSize: 10, color: AppColors.rojoError)),
|
||||||
Text(nightIcon + r.turno, style: const TextStyle(fontSize: 10, color: AppColors.grisTexto)),
|
Text(nightIcon + r.turno, style: const TextStyle(fontSize: 10, color: AppColors.grisTexto)),
|
||||||
]),
|
]),
|
||||||
trailing: PopupMenuButton<String>(
|
trailing: PopupMenuButton<String>(
|
||||||
@@ -213,13 +213,13 @@ class _AdminHomeTabState extends State<_AdminHomeTab> {
|
|||||||
await _changeStatus(r.routeId, v, msg);
|
await _changeStatus(r.routeId, v, msg);
|
||||||
},
|
},
|
||||||
itemBuilder: (_) => [
|
itemBuilder: (_) => [
|
||||||
const PopupMenuItem(value: 'EN_RUTA', child: Text('✅ En Ruta — Continúa')),
|
const PopupMenuItem(value: 'EN_RUTA', child: Text(' En Ruta — Continúa')),
|
||||||
const PopupMenuItem(value: 'RETRASADA', child: Text('⏱️ Marcar Retrasada')),
|
const PopupMenuItem(value: 'RETRASADA', child: Text('Marcar Retrasada')),
|
||||||
const PopupMenuItem(value: 'CANCELADA', child: Text('❌ Cancelar y Notificar')),
|
const PopupMenuItem(value: 'CANCELADA', child: Text('Cancelar y Notificar')),
|
||||||
const PopupMenuItem(value: 'FALLA_MECANICA', child: Text('🔧 Falla Mecánica')),
|
const PopupMenuItem(value: 'FALLA_MECANICA', child: Text(' Falla Mecánica')),
|
||||||
const PopupMenuDivider(),
|
const PopupMenuDivider(),
|
||||||
const PopupMenuItem(value: 'GPS', child: Text('📡 Simular GPS Perdido')),
|
const PopupMenuItem(value: 'GPS', child: Text('Simular GPS Perdido')),
|
||||||
const PopupMenuItem(value: 'RESTORE', child: Text('📶 Restaurar GPS')),
|
const PopupMenuItem(value: 'RESTORE', child: Text('Restaurar GPS')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -325,10 +325,10 @@ class _AdminHomeTabState extends State<_AdminHomeTab> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Expanded(child: RadioListTile<String>(dense: true, value: 'MATUTINO',
|
Expanded(child: RadioListTile<String>(dense: true, value: 'MATUTINO',
|
||||||
groupValue: turno, title: const Text('🌄 Matutino'),
|
groupValue: turno, title: const Text('Matutino'),
|
||||||
onChanged: (v) => setSt(() => turno = v!))),
|
onChanged: (v) => setSt(() => turno = v!))),
|
||||||
Expanded(child: RadioListTile<String>(dense: true, value: 'VESPERTINO',
|
Expanded(child: RadioListTile<String>(dense: true, value: 'VESPERTINO',
|
||||||
groupValue: turno, title: const Text('🌅 Vespertino'),
|
groupValue: turno, title: const Text('Vespertino'),
|
||||||
onChanged: (v) => setSt(() => turno = v!))),
|
onChanged: (v) => setSt(() => turno = v!))),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -360,10 +360,10 @@ class _AdminHomeTabState extends State<_AdminHomeTab> {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Expanded(child: RadioListTile<String>(dense: true, value: 'MATUTINO',
|
Expanded(child: RadioListTile<String>(dense: true, value: 'MATUTINO',
|
||||||
groupValue: turnoSeleccionado, title: const Text('🌄 Matutino', style: TextStyle(fontSize: 12)),
|
groupValue: turnoSeleccionado, title: const Text('Matutino', style: TextStyle(fontSize: 12)),
|
||||||
onChanged: (v) => setDialogState(() => turnoSeleccionado = v!))),
|
onChanged: (v) => setDialogState(() => turnoSeleccionado = v!))),
|
||||||
Expanded(child: RadioListTile<String>(dense: true, value: 'VESPERTINO',
|
Expanded(child: RadioListTile<String>(dense: true, value: 'VESPERTINO',
|
||||||
groupValue: turnoSeleccionado, title: const Text('🌅 Vespertino', style: TextStyle(fontSize: 12)),
|
groupValue: turnoSeleccionado, title: const Text('Vespertino', style: TextStyle(fontSize: 12)),
|
||||||
onChanged: (v) => setDialogState(() => turnoSeleccionado = v!))),
|
onChanged: (v) => setDialogState(() => turnoSeleccionado = v!))),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -603,8 +603,8 @@ class _AdminRoutesTabState extends State<_AdminRoutesTab> {
|
|||||||
itemCount: _routes.length,
|
itemCount: _routes.length,
|
||||||
itemBuilder: (_, i) {
|
itemBuilder: (_, i) {
|
||||||
final r = _routes[i];
|
final r = _routes[i];
|
||||||
final turnoEmoji = r.turno == 'MATUTINO' ? '🌄'
|
final turnoEmoji = r.turno == 'MATUTINO' ? ''
|
||||||
: r.turno == 'VESPERTINO' ? '🌅' : '🌙';
|
: r.turno == 'VESPERTINO' ? '' : '';
|
||||||
return Card(margin: const EdgeInsets.only(bottom: 10),
|
return Card(margin: const EdgeInsets.only(bottom: 10),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10),
|
||||||
side: BorderSide(color: AppColors.guindaPrimary.withOpacity(0.3))),
|
side: BorderSide(color: AppColors.guindaPrimary.withOpacity(0.3))),
|
||||||
@@ -631,7 +631,7 @@ class _AdminRoutesTabState extends State<_AdminRoutesTab> {
|
|||||||
style: const TextStyle(fontSize: 11, color: AppColors.grisTexto)),
|
style: const TextStyle(fontSize: 11, color: AppColors.grisTexto)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
// Colonias
|
// Colonias
|
||||||
Text('📍 ${r.colonias.length} colonias:',
|
Text(' ${r.colonias.length} colonias:',
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12)),
|
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 12)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Wrap(spacing: 4, runSpacing: 4, children: r.colonias.take(8).map((c) =>
|
Wrap(spacing: 4, runSpacing: 4, children: r.colonias.take(8).map((c) =>
|
||||||
@@ -715,7 +715,7 @@ class _AdminReviewsTabState extends State<_AdminReviewsTab> {
|
|||||||
: prom >= 3.5 ? Colors.amber.shade700
|
: prom >= 3.5 ? Colors.amber.shade700
|
||||||
: prom >= 2.5 ? AppColors.naranjaAlerta
|
: prom >= 2.5 ? AppColors.naranjaAlerta
|
||||||
: AppColors.rojoError;
|
: AppColors.rojoError;
|
||||||
final emoji = prom >= 4.5 ? '🟢' : prom >= 3.5 ? '🟡' : prom >= 2.5 ? '🟠' : '🔴';
|
final emoji = prom >= 4.5 ? 'Excelente' : prom >= 3.5 ? 'Bueno' : prom >= 2.5 ? 'Regular' : 'Deficiente';
|
||||||
return Card(margin: const EdgeInsets.only(bottom: 8),
|
return Card(margin: const EdgeInsets.only(bottom: 8),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10),
|
||||||
side: BorderSide(color: color.withOpacity(0.3))),
|
side: BorderSide(color: color.withOpacity(0.3))),
|
||||||
@@ -757,7 +757,7 @@ class _AdminReviewsTabState extends State<_AdminReviewsTab> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start, children: [
|
crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Row(children: [
|
Row(children: [
|
||||||
CircleAvatar(backgroundColor: AppColors.guindaPrimary.withOpacity(0.1), radius: 18,
|
CircleAvatar(backgroundColor: AppColors.guindaPrimary.withOpacity(0.1), radius: 18,
|
||||||
child: Text('${r.estrellas}⭐', style: const TextStyle(fontSize: 11))),
|
child: Text('${r.estrellas}', style: const TextStyle(fontSize: 11))),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Text(r.nombreUsuario, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)),
|
Text(r.nombreUsuario, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)),
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ class _AdminReporteDetalleScreenState extends State<AdminReporteDetalleScreen>
|
|||||||
|
|
||||||
// Evidencias del admin
|
// Evidencias del admin
|
||||||
Row(children: [
|
Row(children: [
|
||||||
const Expanded(child: Text('Evidencias del Ayuntamiento',
|
const Expanded(child: Text('Evidencias dla Direccion General',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold,
|
style: TextStyle(fontWeight: FontWeight.bold,
|
||||||
color: AppColors.guindaPrimary, fontSize: 14))),
|
color: AppColors.guindaPrimary, fontSize: 14))),
|
||||||
Text('${_evidencias.length}',
|
Text('${_evidencias.length}',
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class _CreateRouteScreenState extends State<CreateRouteScreen> {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: Colors.blue.shade200)),
|
border: Border.all(color: Colors.blue.shade200)),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'📅 Selecciona Grupo A (L/M/V) o Grupo B (M/J/S), o días individuales.',
|
' Selecciona Grupo A (L/M/V) o Grupo B (M/J/S), o días individuales.',
|
||||||
style: TextStyle(fontSize: 12, color: AppColors.azulInfo)),
|
style: TextStyle(fontSize: 12, color: AppColors.azulInfo)),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -265,8 +265,8 @@ class _CreateRouteScreenState extends State<CreateRouteScreen> {
|
|||||||
]),
|
]),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
String _turnoLabel(String t) => t == 'MATUTINO' ? '🌄 Matutino'
|
String _turnoLabel(String t) => t == 'MATUTINO' ? 'Matutino'
|
||||||
: t == 'VESPERTINO' ? '🌅 Vespertino' : '🌙 Nocturno';
|
: t == 'VESPERTINO' ? 'Vespertino' : 'Nocturno';
|
||||||
|
|
||||||
@override void dispose() { _nombreCtrl.dispose(); _routeIdCtrl.dispose(); super.dispose(); }
|
@override void dispose() { _nombreCtrl.dispose(); _routeIdCtrl.dispose(); super.dispose(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ class _ExportPdfScreenState extends State<ExportPdfScreen> {
|
|||||||
header: (ctx) => pw.Column(children: [
|
header: (ctx) => pw.Column(children: [
|
||||||
pw.Row(mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [
|
pw.Row(mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [
|
||||||
pw.Column(crossAxisAlignment: pw.CrossAxisAlignment.start, children: [
|
pw.Column(crossAxisAlignment: pw.CrossAxisAlignment.start, children: [
|
||||||
pw.Text('H. AYUNTAMIENTO DE CELAYA', style: pw.TextStyle(
|
pw.Text('DIR. GRAL. DE SERVICIOS MUNICIPALES', style: pw.TextStyle(
|
||||||
fontSize: 14, fontWeight: pw.FontWeight.bold,
|
fontSize: 14, fontWeight: pw.FontWeight.bold,
|
||||||
color: PdfColor.fromHex('6D1E3A'))),
|
color: PdfColor.fromHex('6D1E3A'))),
|
||||||
pw.Text('Direccion de Servicios Publicos',
|
pw.Text('Direccion de Servicios Municipales',
|
||||||
style: const pw.TextStyle(fontSize: 11, color: PdfColors.grey700)),
|
style: const pw.TextStyle(fontSize: 11, color: PdfColors.grey700)),
|
||||||
pw.Text('Sistema de Recoleccion de Residuos',
|
pw.Text('Sistema de Recoleccion de Residuos',
|
||||||
style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey600)),
|
style: const pw.TextStyle(fontSize: 10, color: PdfColors.grey600)),
|
||||||
@@ -163,7 +163,7 @@ class _ExportPdfScreenState extends State<ExportPdfScreen> {
|
|||||||
],
|
],
|
||||||
|
|
||||||
pw.Divider(color: PdfColor.fromHex('C9A84C')),
|
pw.Divider(color: PdfColor.fromHex('C9A84C')),
|
||||||
pw.Text('Celaya Limpia - H. Ayuntamiento de Celaya, Gto. - ${now.year}',
|
pw.Text('Celaya Limpia - Dir. Gral. Servicios Municipales - ${now.year}',
|
||||||
style: const pw.TextStyle(fontSize: 8, color: PdfColors.grey500),
|
style: const pw.TextStyle(fontSize: 8, color: PdfColors.grey500),
|
||||||
textAlign: pw.TextAlign.center),
|
textAlign: pw.TextAlign.center),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ final _chatTree = _ChatNode('Hola, soy el asistente de Celaya Limpia. ¿En que t
|
|||||||
'El aceite de cocina usado NO va a la basura ni al drenaje.\n\n'
|
'El aceite de cocina usado NO va a la basura ni al drenaje.\n\n'
|
||||||
'1. Dejalo enfriar completamente\n'
|
'1. Dejalo enfriar completamente\n'
|
||||||
'2. Guardalo en botella de PET cerrada\n'
|
'2. Guardalo en botella de PET cerrada\n'
|
||||||
'3. Llevalo a los puntos de acopio del Ayuntamiento de Celaya\n\n'
|
'3. Llevalo a los puntos de acopio del Dir. Gral. Servicios Municipales\n\n'
|
||||||
'El aceite reciclado se convierte en biodiesel.',
|
'El aceite reciclado se convierte en biodiesel.',
|
||||||
[], isAnswer: true)),
|
[], isAnswer: true)),
|
||||||
])),
|
])),
|
||||||
@@ -62,7 +62,7 @@ final _chatTree = _ChatNode('Hola, soy el asistente de Celaya Limpia. ¿En que t
|
|||||||
'Depositalas en:\n'
|
'Depositalas en:\n'
|
||||||
'- Supermercados (contenedores naranjas)\n'
|
'- Supermercados (contenedores naranjas)\n'
|
||||||
'- Tiendas de electronica\n'
|
'- Tiendas de electronica\n'
|
||||||
'- Oficinas del Ayuntamiento de Celaya\n\n'
|
'- Oficinas del Dir. Gral. Servicios Municipales\n\n'
|
||||||
'1 pila puede contaminar 600,000 litros de agua.',
|
'1 pila puede contaminar 600,000 litros de agua.',
|
||||||
[], isAnswer: true)),
|
[], isAnswer: true)),
|
||||||
])),
|
])),
|
||||||
@@ -86,7 +86,7 @@ final _chatTree = _ChatNode('Hola, soy el asistente de Celaya Limpia. ¿En que t
|
|||||||
'Despues de que el camion pase por tu zona, '
|
'Despues de que el camion pase por tu zona, '
|
||||||
'la app te mostrara una notificacion para calificar.\n\n'
|
'la app te mostrara una notificacion para calificar.\n\n'
|
||||||
'Puedes dar de 1 a 5 estrellas y dejar un comentario.\n\n'
|
'Puedes dar de 1 a 5 estrellas y dejar un comentario.\n\n'
|
||||||
'Tus calificaciones ayudan al Ayuntamiento a identificar '
|
'Tus calificaciones ayudan a la Direccion General a identificar '
|
||||||
'colonias con problemas y mejorar el servicio.',
|
'colonias con problemas y mejorar el servicio.',
|
||||||
[], isAnswer: true)),
|
[], isAnswer: true)),
|
||||||
])),
|
])),
|
||||||
@@ -94,7 +94,7 @@ final _chatTree = _ChatNode('Hola, soy el asistente de Celaya Limpia. ¿En que t
|
|||||||
'Para situaciones urgentes:\n\n'
|
'Para situaciones urgentes:\n\n'
|
||||||
'- Reporte de incidencias: usa la seccion "Reportar" en la app\n'
|
'- Reporte de incidencias: usa la seccion "Reportar" en la app\n'
|
||||||
'- Emergencias: llama al 911\n'
|
'- Emergencias: llama al 911\n'
|
||||||
'- Ayuntamiento de Celaya: (461) 614-8000\n'
|
'- Dir. Gral. Servicios Municipales: (461) 614-8000\n'
|
||||||
'- SEMARNAT Guanajuato: (477) 717-2600\n\n'
|
'- SEMARNAT Guanajuato: (477) 717-2600\n\n'
|
||||||
'Para basura clandestina o tiraderos ilegales, reportalo al municipio.',
|
'Para basura clandestina o tiraderos ilegales, reportalo al municipio.',
|
||||||
[], isAnswer: true)),
|
[], isAnswer: true)),
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ class CitizenGuiaScreen extends StatelessWidget {
|
|||||||
const CitizenGuiaScreen({super.key});
|
const CitizenGuiaScreen({super.key});
|
||||||
|
|
||||||
static const _cats = [
|
static const _cats = [
|
||||||
_Cat(Icons.grass,Color(0xFF2E7D32),'Orgánicos','Restos de comida, jardín','🟢 Bolsa Verde',[
|
_Cat(Icons.grass,Color(0xFF2E7D32),'Orgánicos','Restos de comida, jardín',' Bolsa Verde',[
|
||||||
'Frutas y verduras','Cáscaras de huevo','Posos de café y té',
|
'Frutas y verduras','Cáscaras de huevo','Posos de café y té',
|
||||||
'Restos de comida preparada','Pasto y hojas','Cáscaras de semillas'],
|
'Restos de comida preparada','Pasto y hojas','Cáscaras de semillas'],
|
||||||
['Aceites en exceso','Carnes en grandes cantidades']),
|
['Aceites en exceso','Carnes en grandes cantidades']),
|
||||||
_Cat(Icons.recycling,Color(0xFF1565C0),'Reciclables','Papel, plástico, vidrio, metal','🔵 Bolsa Azul',[
|
_Cat(Icons.recycling,Color(0xFF1565C0),'Reciclables','Papel, plástico, vidrio, metal',' Bolsa Azul',[
|
||||||
'Botellas PET','Latas de aluminio','Cartón y papel limpio',
|
'Botellas PET','Latas de aluminio','Cartón y papel limpio',
|
||||||
'Vidrio (botellas, frascos)','Periódico y revistas'],
|
'Vidrio (botellas, frascos)','Periódico y revistas'],
|
||||||
['Vidrio roto sin envolver','Papel sucio o mojado','Unicel']),
|
['Vidrio roto sin envolver','Papel sucio o mojado','Unicel']),
|
||||||
_Cat(Icons.delete,Color(0xFF757575),'No Reciclables','Residuos que no se reusan','⚫ Bolsa Negra',[
|
_Cat(Icons.delete,Color(0xFF757575),'No Reciclables','Residuos que no se reusan','⚫ Bolsa Negra',[
|
||||||
'Pañales desechables','Toallas sanitarias','Papel higiénico usado',
|
'Pañales desechables','Toallas sanitarias','Papel higiénico usado',
|
||||||
'Colillas de cigarro','Cerámica rota'],['Baterías','Medicamentos','Aceite usado']),
|
'Colillas de cigarro','Cerámica rota'],['Baterías','Medicamentos','Aceite usado']),
|
||||||
_Cat(Icons.warning_amber,Color(0xFFC62828),'Peligrosos','Requieren manejo especial','🔴 Separado',[
|
_Cat(Icons.warning_amber,Color(0xFFC62828),'Peligrosos','Requieren manejo especial',' Separado',[
|
||||||
'Agujas y jeringas','Medicamentos vencidos','Pilas y baterías',
|
'Agujas y jeringas','Medicamentos vencidos','Pilas y baterías',
|
||||||
'Aceite de cocina usado','Pintura y solventes'],[],isWarn:true),
|
'Aceite de cocina usado','Pintura y solventes'],[],isWarn:true),
|
||||||
_Cat(Icons.devices_other,Color(0xFFE65100),'Electrónicos (RAEE)','Aparatos electrónicos','🟠 Punto de acopio',[
|
_Cat(Icons.devices_other,Color(0xFFE65100),'Electrónicos (RAEE)','Aparatos electrónicos',' Punto de acopio',[
|
||||||
'Celulares viejos','Computadoras','Televisiones',
|
'Celulares viejos','Computadoras','Televisiones',
|
||||||
'Focos ahorradores','Cables y cargadores'],[],isSpecial:true),
|
'Focos ahorradores','Cables y cargadores'],[],isSpecial:true),
|
||||||
];
|
];
|
||||||
@@ -59,8 +59,8 @@ class CitizenGuiaScreen extends StatelessWidget {
|
|||||||
child:const Column(crossAxisAlignment:CrossAxisAlignment.start, children:[
|
child:const Column(crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||||
Text('¿Por qué separar tu basura?',style:TextStyle(fontWeight:FontWeight.bold,color:Color(0xFF2E7D32))),
|
Text('¿Por qué separar tu basura?',style:TextStyle(fontWeight:FontWeight.bold,color:Color(0xFF2E7D32))),
|
||||||
SizedBox(height:6),
|
SizedBox(height:6),
|
||||||
Text('♻️ El 60% de los residuos en México pueden reciclarse o compostarse, pero solo el 5% lo hace.\n'
|
Text(' El 60% de los residuos en México pueden reciclarse o compostarse, pero solo el 5% lo hace.\n'
|
||||||
'🌱 Separar correctamente reduce la contaminación del suelo y agua, genera empleos verdes '
|
'Separar correctamente reduce la contaminación del suelo y agua, genera empleos verdes '
|
||||||
'y disminuye los gases de efecto invernadero producidos en rellenos sanitarios.',
|
'y disminuye los gases de efecto invernadero producidos en rellenos sanitarios.',
|
||||||
style:TextStyle(fontSize:12,color:Colors.black87)),
|
style:TextStyle(fontSize:12,color:Colors.black87)),
|
||||||
])),
|
])),
|
||||||
@@ -115,14 +115,14 @@ class _CatCardState extends State<_CatCard> {
|
|||||||
if (_open) Padding(padding:const EdgeInsets.fromLTRB(14,0,14,14),
|
if (_open) Padding(padding:const EdgeInsets.fromLTRB(14,0,14,14),
|
||||||
child:Column(crossAxisAlignment:CrossAxisAlignment.start, children:[
|
child:Column(crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||||
const SizedBox(height:8),
|
const SizedBox(height:8),
|
||||||
Text('✅ Qué va aquí:',style:TextStyle(fontWeight:FontWeight.bold,color:c.color,fontSize:12)),
|
Text(' Qué va aquí:',style:TextStyle(fontWeight:FontWeight.bold,color:c.color,fontSize:12)),
|
||||||
const SizedBox(height:4),
|
const SizedBox(height:4),
|
||||||
...c.items.map((e)=>Padding(padding:const EdgeInsets.symmetric(vertical:2),
|
...c.items.map((e)=>Padding(padding:const EdgeInsets.symmetric(vertical:2),
|
||||||
child:Row(children:[Icon(Icons.check_circle_outline,size:13,color:c.color),
|
child:Row(children:[Icon(Icons.check_circle_outline,size:13,color:c.color),
|
||||||
const SizedBox(width:6),Text(e,style:const TextStyle(fontSize:12))]))),
|
const SizedBox(width:6),Text(e,style:const TextStyle(fontSize:12))]))),
|
||||||
if (c.noItems.isNotEmpty) ...[
|
if (c.noItems.isNotEmpty) ...[
|
||||||
const SizedBox(height:8),
|
const SizedBox(height:8),
|
||||||
const Text('❌ NO incluir:',style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.rojoError,fontSize:12)),
|
const Text(' NO incluir:',style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.rojoError,fontSize:12)),
|
||||||
...c.noItems.map((e)=>Padding(padding:const EdgeInsets.symmetric(vertical:2),
|
...c.noItems.map((e)=>Padding(padding:const EdgeInsets.symmetric(vertical:2),
|
||||||
child:Row(children:[const Icon(Icons.cancel_outlined,size:13,color:AppColors.rojoError),
|
child:Row(children:[const Icon(Icons.cancel_outlined,size:13,color:AppColors.rojoError),
|
||||||
const SizedBox(width:6),Text(e,style:const TextStyle(fontSize:12,color:AppColors.rojoError))]))),
|
const SizedBox(width:6),Text(e,style:const TextStyle(fontSize:12,color:AppColors.rojoError))]))),
|
||||||
@@ -132,7 +132,7 @@ class _CatCardState extends State<_CatCard> {
|
|||||||
Container(padding:const EdgeInsets.all(8),
|
Container(padding:const EdgeInsets.all(8),
|
||||||
decoration:BoxDecoration(color:Colors.orange.shade50,borderRadius:BorderRadius.circular(6),
|
decoration:BoxDecoration(color:Colors.orange.shade50,borderRadius:BorderRadius.circular(6),
|
||||||
border:Border.all(color:Colors.orange.shade200)),
|
border:Border.all(color:Colors.orange.shade200)),
|
||||||
child:const Text('📍 Lleva a puntos de acopio autorizados por el municipio.',
|
child:const Text(' Lleva a puntos de acopio autorizados por el municipio.',
|
||||||
style:TextStyle(fontSize:11))),
|
style:TextStyle(fontSize:11))),
|
||||||
],
|
],
|
||||||
if (c.isWarn) ...[
|
if (c.isWarn) ...[
|
||||||
@@ -140,7 +140,7 @@ class _CatCardState extends State<_CatCard> {
|
|||||||
Container(padding:const EdgeInsets.all(8),
|
Container(padding:const EdgeInsets.all(8),
|
||||||
decoration:BoxDecoration(color:Colors.red.shade50,borderRadius:BorderRadius.circular(6),
|
decoration:BoxDecoration(color:Colors.red.shade50,borderRadius:BorderRadius.circular(6),
|
||||||
border:Border.all(color:Colors.red.shade200)),
|
border:Border.all(color:Colors.red.shade200)),
|
||||||
child:const Text('⚠️ NUNCA mezcles residuos peligrosos con basura común.',
|
child:const Text(' NUNCA mezcles residuos peligrosos con basura común.',
|
||||||
style:TextStyle(fontSize:11))),
|
style:TextStyle(fontSize:11))),
|
||||||
],
|
],
|
||||||
])),
|
])),
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ class _ReviewPromptCard extends StatelessWidget {
|
|||||||
side: BorderSide(color: Colors.amber.shade300, width: 1.5)),
|
side: BorderSide(color: Colors.amber.shade300, width: 1.5)),
|
||||||
child: Padding(padding: const EdgeInsets.all(14), child: Column(children: [
|
child: Padding(padding: const EdgeInsets.all(14), child: Column(children: [
|
||||||
const Row(children: [
|
const Row(children: [
|
||||||
Text('⭐', style: TextStyle(fontSize: 24)),
|
Text('', style: TextStyle(fontSize: 24)),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Expanded(child: Text('¿Cómo estuvo el servicio de hoy?',
|
Expanded(child: Text('¿Cómo estuvo el servicio de hoy?',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14))),
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14))),
|
||||||
@@ -296,7 +296,7 @@ class _RouteInfoCard extends StatelessWidget {
|
|||||||
]),
|
]),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
String _turnoLabel(String t) => t=='MATUTINO'?'🌄 Matutino':t=='VESPERTINO'?'🌅 Vespertino':'🌙 Nocturno';
|
String _turnoLabel(String t) => t=='MATUTINO'?'Matutino':t=='VESPERTINO'?'Vespertino':'Nocturno';
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BasicRouteInfo extends StatelessWidget {
|
class _BasicRouteInfo extends StatelessWidget {
|
||||||
@@ -330,9 +330,9 @@ class _WarningNoPursue extends StatelessWidget {
|
|||||||
Icon(Icons.warning_amber_rounded, color: AppColors.rojoError, size: 20),
|
Icon(Icons.warning_amber_rounded, color: AppColors.rojoError, size: 20),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Expanded(child: Text(
|
Expanded(child: Text(
|
||||||
'⚠️ Ya es momento de sacar tu basura.\n'
|
' Ya es momento de sacar tu basura.\n'
|
||||||
'🚫 NO persigas ni interceptes el camión en movimiento.\n'
|
'🚫 NO persigas ni interceptes el camión en movimiento.\n'
|
||||||
'✅ Coloca tus bolsas en la acera y espera.',
|
' Coloca tus bolsas en la acera y espera.',
|
||||||
style: TextStyle(fontSize: 11, color: AppColors.rojoError, fontWeight: FontWeight.w500))),
|
style: TextStyle(fontSize: 11, color: AppColors.rojoError, fontWeight: FontWeight.w500))),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
@@ -417,8 +417,8 @@ class _RouteStatusBanner extends StatelessWidget {
|
|||||||
final isRetrasada = status.status == RouteStatus.retrasada;
|
final isRetrasada = status.status == RouteStatus.retrasada;
|
||||||
final color = isCancelled ? AppColors.rojoError : isFalla ? Colors.red.shade800 : AppColors.naranjaAlerta;
|
final color = isCancelled ? AppColors.rojoError : isFalla ? Colors.red.shade800 : AppColors.naranjaAlerta;
|
||||||
final icon = isCancelled ? Icons.cancel : isFalla ? Icons.build : Icons.access_time;
|
final icon = isCancelled ? Icons.cancel : isFalla ? Icons.build : Icons.access_time;
|
||||||
final titulo = isCancelled ? '❌ Ruta Cancelada Hoy'
|
final titulo = isCancelled ? ' Ruta Cancelada Hoy'
|
||||||
: isFalla ? '🔧 Falla Mecánica en Servicio' : '⏱️ Servicio con Retraso';
|
: isFalla ? ' Falla Mecánica en Servicio' : ' Servicio con Retraso';
|
||||||
|
|
||||||
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
Container(width: double.infinity, padding: const EdgeInsets.all(16),
|
Container(width: double.infinity, padding: const EdgeInsets.all(16),
|
||||||
@@ -447,7 +447,7 @@ class _RouteStatusBanner extends StatelessWidget {
|
|||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(Icons.admin_panel_settings, color: color, size: 16),
|
Icon(Icons.admin_panel_settings, color: color, size: 16),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text('Mensaje del Ayuntamiento', style: TextStyle(
|
Text('Mensaje dla Direccion General', style: TextStyle(
|
||||||
fontWeight: FontWeight.bold, color: color, fontSize: 13)),
|
fontWeight: FontWeight.bold, color: color, fontSize: 13)),
|
||||||
]),
|
]),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
@@ -459,14 +459,14 @@ class _RouteStatusBanner extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8),
|
decoration: BoxDecoration(color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(color: Colors.grey.shade300)),
|
border: Border.all(color: Colors.grey.shade300)),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
const Text('💡 Recomendaciones:', style: TextStyle(fontWeight: FontWeight.bold,
|
const Text('Recomendaciones:', style: TextStyle(fontWeight: FontWeight.bold,
|
||||||
fontSize: 12, color: AppColors.grisTexto)),
|
fontSize: 12, color: AppColors.grisTexto)),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(isCancelled
|
Text(isCancelled
|
||||||
? '• Guarda tus bolsas en lugar cerrado\n• No dejes residuos en la acera\n• Revisa la app mañana'
|
? '• Guarda tus bolsas en lugar cerrado\n• No dejes residuos en la acera\n• Revisa la app mañana'
|
||||||
: isRetrasada
|
: isRetrasada
|
||||||
? '• Espera el aviso de 15 minutos antes de sacar tu basura\n• El camión llegará eventualmente\n• Recibe la notificación en esta app'
|
? '• Espera el aviso de 15 minutos antes de sacar tu basura\n• El camión llegará eventualmente\n• Recibe la notificación en esta app'
|
||||||
: '• Espera confirmación del Ayuntamiento\n• Puede enviarse unidad de reemplazo',
|
: '• Espera confirmación dla Direccion General\n• Puede enviarse unidad de reemplazo',
|
||||||
style: const TextStyle(fontSize: 12, color: AppColors.grisTexto)),
|
style: const TextStyle(fontSize: 12, color: AppColors.grisTexto)),
|
||||||
])),
|
])),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@@ -498,7 +498,7 @@ class _EtaCard extends StatelessWidget {
|
|||||||
Text(sim.getEtaText(routeId),
|
Text(sim.getEtaText(routeId),
|
||||||
style:const TextStyle(color:Colors.white,fontSize:16,fontWeight:FontWeight.bold)),
|
style:const TextStyle(color:Colors.white,fontSize:16,fontWeight:FontWeight.bold)),
|
||||||
const SizedBox(height:6),
|
const SizedBox(height:6),
|
||||||
if (dom!=null) Text('⏰ ${dom.horarioEstimado}',
|
if (dom!=null) Text(' ${dom.horarioEstimado}',
|
||||||
style:const TextStyle(color:Colors.white60,fontSize:11)),
|
style:const TextStyle(color:Colors.white60,fontSize:11)),
|
||||||
const SizedBox(height:10),
|
const SizedBox(height:10),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
@@ -518,7 +518,7 @@ class _PrivacyBanner extends StatelessWidget {
|
|||||||
child:const Row(children:[
|
child:const Row(children:[
|
||||||
Icon(Icons.shield_outlined,color:Colors.amber,size:18),
|
Icon(Icons.shield_outlined,color:Colors.amber,size:18),
|
||||||
SizedBox(width:6),
|
SizedBox(width:6),
|
||||||
Expanded(child:Text('🔒 Solo ves la información de tu ruta asignada.',
|
Expanded(child:Text(' Solo ves la información de tu ruta asignada.',
|
||||||
style:TextStyle(fontSize:11,color:Colors.black87))),
|
style:TextStyle(fontSize:11,color:Colors.black87))),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ class _CitizenReporteScreenState extends State<CitizenReporteScreen> {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
minimumSize: Size.zero),
|
minimumSize: Size.zero),
|
||||||
icon: Icon(isClosed ? Icons.lock : Icons.chat_bubble_outline, size: 14),
|
icon: Icon(isClosed ? Icons.lock : Icons.chat_bubble_outline, size: 14),
|
||||||
label: Text(isClosed ? 'Chat cerrado' : 'Escribir al Ayuntamiento',
|
label: Text(isClosed ? 'Chat cerrado' : 'Escribir a la Direccion General',
|
||||||
style: const TextStyle(fontSize: 11))))),
|
style: const TextStyle(fontSize: 11))))),
|
||||||
]));
|
]));
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ class _CollectionCalendarScreenState extends State<CollectionCalendarScreen> {
|
|||||||
final horario = rd != null ? '${rd.horaInicio}–${rd.horaFin}' : dom.horarioEstimado;
|
final horario = rd != null ? '${rd.horaInicio}–${rd.horaFin}' : dom.horarioEstimado;
|
||||||
|
|
||||||
Share.share(
|
Share.share(
|
||||||
'🗑️ Horario de recolección de basura\n'
|
' Horario de recolección de basura\n'
|
||||||
'📍 Colonia: ${dom.colonia}\n'
|
' Colonia: ${dom.colonia}\n'
|
||||||
'📅 Días: $diasStr\n'
|
' Días: $diasStr\n'
|
||||||
'⏰ Horario: $horario\n'
|
' Horario: $horario\n'
|
||||||
'🚛 Ruta: ${dom.routeId}\n\n'
|
' Ruta: ${dom.routeId}\n\n'
|
||||||
'Descarga Celaya Limpia para recibir avisos en tiempo real.',
|
'Descarga Celaya Limpia para recibir avisos en tiempo real.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
|||||||
),
|
),
|
||||||
body: _sent
|
body: _sent
|
||||||
? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
? Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
const Text('⭐', style: TextStyle(fontSize: 64)),
|
const Text('', style: TextStyle(fontSize: 64)),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
const Text('¡Gracias por tu calificación!',
|
const Text('¡Gracias por tu calificación!',
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
|
||||||
@@ -163,7 +163,7 @@ class _ReviewScreenState extends State<ReviewScreen> {
|
|||||||
SizedBox(width: 6),
|
SizedBox(width: 6),
|
||||||
Expanded(child: Text(
|
Expanded(child: Text(
|
||||||
'Tu calificación es anónima para otros ciudadanos, '
|
'Tu calificación es anónima para otros ciudadanos, '
|
||||||
'pero el Ayuntamiento la usará para mejorar el servicio.',
|
'pero la Direccion General la usará para mejorar el servicio.',
|
||||||
style: TextStyle(fontSize: 11, color: AppColors.azulInfo))),
|
style: TextStyle(fontSize: 11, color: AppColors.azulInfo))),
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ class _DriverMainTabState extends State<_DriverMainTab> {
|
|||||||
Icon(gpsOk?Icons.gps_fixed:Icons.gps_off,
|
Icon(gpsOk?Icons.gps_fixed:Icons.gps_off,
|
||||||
color:gpsOk?AppColors.verdeExito:AppColors.rojoError,size:16),
|
color:gpsOk?AppColors.verdeExito:AppColors.rojoError,size:16),
|
||||||
const SizedBox(width:4),
|
const SizedBox(width:4),
|
||||||
Text(gpsOk?'GPS Activo':'⚠️ GPS Desactivado',
|
Text(gpsOk?'GPS Activo':' GPS Desactivado',
|
||||||
style:TextStyle(color:gpsOk?AppColors.verdeExito:AppColors.rojoError,
|
style:TextStyle(color:gpsOk?AppColors.verdeExito:AppColors.rojoError,
|
||||||
fontWeight:FontWeight.bold,fontSize:12)),
|
fontWeight:FontWeight.bold,fontSize:12)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@@ -160,14 +160,14 @@ class _DriverMainTabState extends State<_DriverMainTab> {
|
|||||||
Text(widget.sim.getEtaText(widget.todayRouteId??''),
|
Text(widget.sim.getEtaText(widget.todayRouteId??''),
|
||||||
style:const TextStyle(fontSize:13,fontWeight:FontWeight.w500)),
|
style:const TextStyle(fontSize:13,fontWeight:FontWeight.w500)),
|
||||||
] else
|
] else
|
||||||
const Text('⚠️ Sin ruta asignada hoy.',style:TextStyle(color:AppColors.rojoError)),
|
const Text(' Sin ruta asignada hoy.',style:TextStyle(color:AppColors.rojoError)),
|
||||||
]))),
|
]))),
|
||||||
const SizedBox(height:10),
|
const SizedBox(height:10),
|
||||||
|
|
||||||
// Instrucciones
|
// Instrucciones
|
||||||
Card(child:Padding(padding:const EdgeInsets.all(12),child:Column(
|
Card(child:Padding(padding:const EdgeInsets.all(12),child:Column(
|
||||||
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
crossAxisAlignment:CrossAxisAlignment.start, children:[
|
||||||
const Text('📋 Instrucciones de Ruta',
|
const Text(' Instrucciones de Ruta',
|
||||||
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.guindaPrimary)),
|
style:TextStyle(fontWeight:FontWeight.bold,color:AppColors.guindaPrimary)),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const Text('• Sigue la ruta asignada sin desviaciones\n'
|
const Text('• Sigue la ruta asignada sin desviaciones\n'
|
||||||
@@ -284,12 +284,12 @@ class _DriverReportesTabState extends State<_DriverReportesTab> {
|
|||||||
List<AlertaModel> _misIncidentes = [];
|
List<AlertaModel> _misIncidentes = [];
|
||||||
|
|
||||||
static const _tipos = {
|
static const _tipos = {
|
||||||
'INCIDENTE_LLANTA': '🔧 Llanta ponchada',
|
'INCIDENTE_LLANTA': ' Llanta ponchada',
|
||||||
'INCIDENTE_MECANICA': '🔥 Falla mecánica',
|
'INCIDENTE_MECANICA': ' Falla mecánica',
|
||||||
'INCIDENTE_ACCIDENTE': '🚑 Accidente',
|
'INCIDENTE_ACCIDENTE': ' Accidente',
|
||||||
'INCIDENTE_CAMINO': '🚧 Camino bloqueado',
|
'INCIDENTE_CAMINO': ' Camino bloqueado',
|
||||||
'INCIDENTE_COMBUSTIBLE':'⛽ Sin combustible',
|
'INCIDENTE_COMBUSTIBLE':' Sin combustible',
|
||||||
'INCIDENTE_OTRO': '📝 Otro',
|
'INCIDENTE_OTRO': 'Otro',
|
||||||
};
|
};
|
||||||
|
|
||||||
@override void initState() { super.initState(); _load(); }
|
@override void initState() { super.initState(); _load(); }
|
||||||
@@ -362,7 +362,7 @@ class _DriverReportesTabState extends State<_DriverReportesTab> {
|
|||||||
Container(margin:const EdgeInsets.only(bottom:12),
|
Container(margin:const EdgeInsets.only(bottom:12),
|
||||||
padding:const EdgeInsets.all(10),
|
padding:const EdgeInsets.all(10),
|
||||||
decoration:BoxDecoration(color:Colors.orange.shade50,borderRadius:BorderRadius.circular(8)),
|
decoration:BoxDecoration(color:Colors.orange.shade50,borderRadius:BorderRadius.circular(8)),
|
||||||
child:const Text('⚠️ No tienes ruta asignada hoy',
|
child:const Text(' No tienes ruta asignada hoy',
|
||||||
style:TextStyle(color:AppColors.naranjaAlerta,fontWeight:FontWeight.bold))),
|
style:TextStyle(color:AppColors.naranjaAlerta,fontWeight:FontWeight.bold))),
|
||||||
|
|
||||||
Card(shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12)),
|
Card(shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12)),
|
||||||
@@ -388,7 +388,7 @@ class _DriverReportesTabState extends State<_DriverReportesTab> {
|
|||||||
borderRadius:BorderRadius.circular(8),
|
borderRadius:BorderRadius.circular(8),
|
||||||
border:Border.all(color:Colors.orange.shade200)),
|
border:Border.all(color:Colors.orange.shade200)),
|
||||||
child:const Text(
|
child:const Text(
|
||||||
'⚠️ El administrador verá este incidente en tu ruta actual '
|
' El administrador verá este incidente en tu ruta actual '
|
||||||
'y decidirá si continúa, se retrasa o se cancela.',
|
'y decidirá si continúa, se retrasa o se cancela.',
|
||||||
style:TextStyle(fontSize:11,color:Colors.black87))),
|
style:TextStyle(fontSize:11,color:Colors.black87))),
|
||||||
const SizedBox(height:14),
|
const SizedBox(height:14),
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
border:Border.all(color:AppColors.dorado,width:2.5)),
|
border:Border.all(color:AppColors.dorado,width:2.5)),
|
||||||
child:const Icon(Icons.delete_sweep_rounded,size:44,color:AppColors.dorado)),
|
child:const Icon(Icons.delete_sweep_rounded,size:44,color:AppColors.dorado)),
|
||||||
const SizedBox(height:14),
|
const SizedBox(height:14),
|
||||||
const Text('H. AYUNTAMIENTO DE CELAYA',
|
const Text('DIR. GRAL. DE SERVICIOS MUNICIPALES',
|
||||||
style:TextStyle(color:Colors.white,fontSize:13,fontWeight:FontWeight.bold,letterSpacing:1.2)),
|
style:TextStyle(color:Colors.white,fontSize:13,fontWeight:FontWeight.bold,letterSpacing:1.2)),
|
||||||
const SizedBox(height:4),
|
const SizedBox(height:4),
|
||||||
const Text('Sistema de Recolección de Residuos',
|
const Text('Sistema de Recolección de Residuos',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
static const _pages = [
|
static const _pages = [
|
||||||
_OnboardPage(icon:Icons.delete_sweep_rounded, color:AppColors.guindaPrimary,
|
_OnboardPage(icon:Icons.delete_sweep_rounded, color:AppColors.guindaPrimary,
|
||||||
title:'Bienvenido a Celaya Limpia',
|
title:'Bienvenido a Celaya Limpia',
|
||||||
subtitle:'El sistema de recoleccion inteligente del H. Ayuntamiento de Celaya.',
|
subtitle:'El sistema de recoleccion inteligente del Direccion General de Servicios Municipales de Celaya.',
|
||||||
desc:'Recibe alertas en tiempo real, conoce tu horario y ayuda a mantener tu ciudad limpia.'),
|
desc:'Recibe alertas en tiempo real, conoce tu horario y ayuda a mantener tu ciudad limpia.'),
|
||||||
_OnboardPage(icon:Icons.notifications_active, color:AppColors.azulInfo,
|
_OnboardPage(icon:Icons.notifications_active, color:AppColors.azulInfo,
|
||||||
title:'Alertas inteligentes',
|
title:'Alertas inteligentes',
|
||||||
@@ -27,7 +27,7 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
_OnboardPage(icon:Icons.star, color:Colors.amber,
|
_OnboardPage(icon:Icons.star, color:Colors.amber,
|
||||||
title:'Tu opinion importa',
|
title:'Tu opinion importa',
|
||||||
subtitle:'Califica el servicio y ayuda a mejorarlo.',
|
subtitle:'Califica el servicio y ayuda a mejorarlo.',
|
||||||
desc:'Despues de cada recoleccion podras:\n\nCalificar de 1 a 5 estrellas\nDejar comentarios al Ayuntamiento\nReportar incidencias con foto\n\nTus reportes son atendidos por el equipo municipal.'),
|
desc:'Despues de cada recoleccion podras:\n\nCalificar de 1 a 5 estrellas\nDejar comentarios a la Direccion General\nReportar incidencias con foto\n\nTus reportes son atendidos por el equipo municipal.'),
|
||||||
];
|
];
|
||||||
|
|
||||||
Future<void> _finish() async {
|
Future<void> _finish() async {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
Text('Acerca de', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
|
Text('Acerca de', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15)),
|
||||||
])),
|
])),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
const ListTile(leading: Icon(Icons.location_city), title: Text('H. Ayuntamiento de Celaya'),
|
const ListTile(leading: Icon(Icons.location_city), title: Text('Direccion General de Servicios Municipales de Celaya'),
|
||||||
subtitle: Text('Guanajuato, Mexico')),
|
subtitle: Text('Guanajuato, Mexico')),
|
||||||
const ListTile(leading: Icon(Icons.code), title: Text('Version 2.0.0'),
|
const ListTile(leading: Icon(Icons.code), title: Text('Version 2.0.0'),
|
||||||
subtitle: Text('Sistema Integral de Recoleccion de Residuos')),
|
subtitle: Text('Sistema Integral de Recoleccion de Residuos')),
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class _ReporteChatScreenState extends State<ReporteChatScreen> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(isAdmin
|
Text(isAdmin
|
||||||
? 'Sin mensajes aún. Inicia la conversación.'
|
? 'Sin mensajes aún. Inicia la conversación.'
|
||||||
: 'Escribe tu mensaje al Ayuntamiento de Celaya.',
|
: 'Escribe tu mensaje al Dir. Gral. Servicios Municipales.',
|
||||||
style: const TextStyle(color: AppColors.grisTexto),
|
style: const TextStyle(color: AppColors.grisTexto),
|
||||||
textAlign: TextAlign.center),
|
textAlign: TextAlign.center),
|
||||||
]))
|
]))
|
||||||
@@ -176,7 +176,7 @@ class _ReporteChatScreenState extends State<ReporteChatScreen> {
|
|||||||
children: [
|
children: [
|
||||||
if (!me) ...[
|
if (!me) ...[
|
||||||
Text(
|
Text(
|
||||||
rol == 'ADMINISTRADOR' ? 'Ayuntamiento de Celaya' : 'Ciudadano',
|
rol == 'ADMINISTRADOR' ? 'Dir. Gral. Servicios Municipales' : 'Ciudadano',
|
||||||
style: const TextStyle(fontSize: 10,
|
style: const TextStyle(fontSize: 10,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: AppColors.guindaPrimary)),
|
color: AppColors.guindaPrimary)),
|
||||||
@@ -214,7 +214,7 @@ class _ReporteChatScreenState extends State<ReporteChatScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: isAdmin
|
hintText: isAdmin
|
||||||
? 'Responde al ciudadano...'
|
? 'Responde al ciudadano...'
|
||||||
: 'Escribe al Ayuntamiento de Celaya...',
|
: 'Escribe al Dir. Gral. Servicios Municipales...',
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
borderSide: BorderSide.none),
|
borderSide: BorderSide.none),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class _SplashScreenState extends State<SplashScreen> {
|
|||||||
const Text('CELAYA LIMPIA',style:TextStyle(color:Colors.white,fontSize:26,
|
const Text('CELAYA LIMPIA',style:TextStyle(color:Colors.white,fontSize:26,
|
||||||
fontWeight:FontWeight.bold,letterSpacing:2)),
|
fontWeight:FontWeight.bold,letterSpacing:2)),
|
||||||
const SizedBox(height:4),
|
const SizedBox(height:4),
|
||||||
const Text('H. Ayuntamiento de Celaya',style:TextStyle(color:Colors.white60,fontSize:13)),
|
const Text('Direccion General de Servicios Municipales de Celaya',style:TextStyle(color:Colors.white60,fontSize:13)),
|
||||||
const SizedBox(height:40),
|
const SizedBox(height:40),
|
||||||
const CircularProgressIndicator(valueColor:AlwaysStoppedAnimation<Color>(AppColors.dorado)),
|
const CircularProgressIndicator(valueColor:AlwaysStoppedAnimation<Color>(AppColors.dorado)),
|
||||||
])),
|
])),
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class RouteSimulatorService extends ChangeNotifier {
|
|||||||
if (diff.inMinutes >= 30 && !state.stoppedAlertSent) {
|
if (diff.inMinutes >= 30 && !state.stoppedAlertSent) {
|
||||||
state.stoppedAlertSent = true;
|
state.stoppedAlertSent = true;
|
||||||
_fireAndSave(event:NotifEvent.truckStopped, routeId:state.routeId,
|
_fireAndSave(event:NotifEvent.truckStopped, routeId:state.routeId,
|
||||||
title:'⚠️ Camión detenido',
|
title:' Camión detenido',
|
||||||
body:'El camión ${state.routeId} lleva +30 min sin moverse. Verifica.',
|
body:'El camión ${state.routeId} lleva +30 min sin moverse. Verifica.',
|
||||||
tipo:'CAMION_DETENIDO');
|
tipo:'CAMION_DETENIDO');
|
||||||
}
|
}
|
||||||
@@ -111,29 +111,29 @@ class RouteSimulatorService extends ChangeNotifier {
|
|||||||
|
|
||||||
if (idx == 1) {
|
if (idx == 1) {
|
||||||
// Ruta iniciada
|
// Ruta iniciada
|
||||||
_fireNotif(NotifEvent.routeStart, '¡Ruta Iniciada! 🚛',
|
_fireNotif(NotifEvent.routeStart, '¡Ruta Iniciada! ',
|
||||||
'El camión ha salido del Relleno Sanitario rumbo a tu sector. '
|
'El camión ha salido del Relleno Sanitario rumbo a tu sector. '
|
||||||
'Prepara tus bolsas pero espera la señal para sacarlas.', state.routeId);
|
'Prepara tus bolsas pero espera la señal para sacarlas.', state.routeId);
|
||||||
} else if (idx == 2) {
|
} else if (idx == 2) {
|
||||||
// ~30 min — aviso preventivo
|
// ~30 min — aviso preventivo
|
||||||
_fireNotif(NotifEvent.truckApproaching15min, '🕐 El camión se acerca',
|
_fireNotif(NotifEvent.truckApproaching15min, ' El camión se acerca',
|
||||||
'Tu camión recolector está en camino. Tendrás otro aviso cuando esté a '
|
'Tu camión recolector está en camino. Tendrás otro aviso cuando esté a '
|
||||||
'15 minutos. ⚠️ No saques la basura todavía — espera el aviso.', state.routeId);
|
'15 minutos. No saques la basura todavía — espera el aviso.', state.routeId);
|
||||||
} else if (idx == 3) {
|
} else if (idx == 3) {
|
||||||
// ~15 min — MOMENTO de sacar la basura
|
// ~15 min — MOMENTO de sacar la basura
|
||||||
_fireNotif(NotifEvent.truckProximity, '⚠️ ¡Saca tus bolsas AHORA!',
|
_fireNotif(NotifEvent.truckProximity, ' ¡Saca tus bolsas AHORA!',
|
||||||
'El camión llega en aprox. 15 minutos a tu colonia. '
|
'El camión llega en aprox. 15 minutos a tu colonia. '
|
||||||
'Este es el momento de sacar tus bolsas a la acera. '
|
'Este es el momento de sacar tus bolsas a la acera. '
|
||||||
'🚫 No persigas ni interceptes la unidad.', state.routeId);
|
'No persigas ni interceptes la unidad.', state.routeId);
|
||||||
} else if (idx == total - 2) {
|
} else if (idx == total - 2) {
|
||||||
// Pasando por la zona
|
// Pasando por la zona
|
||||||
_fireNotif(NotifEvent.truckProximity, '✅ El camión está en tu zona',
|
_fireNotif(NotifEvent.truckProximity, ' El camión está en tu zona',
|
||||||
'El camión recolector está pasando por tu colonia. '
|
'El camión recolector está pasando por tu colonia. '
|
||||||
'Si ya sacaste tus bolsas, el servicio está en curso.', state.routeId);
|
'Si ya sacaste tus bolsas, el servicio está en curso.', state.routeId);
|
||||||
} else if (idx == total - 1) {
|
} else if (idx == total - 1) {
|
||||||
// Servicio finalizado → prompt de reseña
|
// Servicio finalizado → prompt de reseña
|
||||||
state.reviewPromptSent = true;
|
state.reviewPromptSent = true;
|
||||||
_fireNotif(NotifEvent.reviewPrompt, '🌟 ¿Cómo fue el servicio?',
|
_fireNotif(NotifEvent.reviewPrompt, ' ¿Cómo fue el servicio?',
|
||||||
'¡El camión concluyó su jornada! Ayúdanos calificando el servicio '
|
'¡El camión concluyó su jornada! Ayúdanos calificando el servicio '
|
||||||
'de recolección de hoy. Tu opinión mejora el servicio.', state.routeId);
|
'de recolección de hoy. Tu opinión mejora el servicio.', state.routeId);
|
||||||
}
|
}
|
||||||
@@ -168,7 +168,7 @@ class RouteSimulatorService extends ChangeNotifier {
|
|||||||
if (state == null) return;
|
if (state == null) return;
|
||||||
state.gpsActive = false;
|
state.gpsActive = false;
|
||||||
await _fireAndSave(event:NotifEvent.gpsLost, routeId:routeId,
|
await _fireAndSave(event:NotifEvent.gpsLost, routeId:routeId,
|
||||||
title:'📡 GPS Desactivado',
|
title:' GPS Desactivado',
|
||||||
body:'Se perdió la señal GPS del camión $routeId.',
|
body:'Se perdió la señal GPS del camión $routeId.',
|
||||||
tipo:'GPS_PERDIDO');
|
tipo:'GPS_PERDIDO');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -198,20 +198,20 @@ class RouteSimulatorService extends ChangeNotifier {
|
|||||||
String getEtaText(String routeId) {
|
String getEtaText(String routeId) {
|
||||||
final state = _states[routeId];
|
final state = _states[routeId];
|
||||||
if (state == null) return 'Sin información';
|
if (state == null) return 'Sin información';
|
||||||
if (!state.gpsActive) return '📡 Señal GPS perdida';
|
if (!state.gpsActive) return 'Señal GPS perdida';
|
||||||
final route = getRouteById(routeId);
|
final route = getRouteById(routeId);
|
||||||
if (route == null) return 'Ruta no encontrada';
|
if (route == null) return 'Ruta no encontrada';
|
||||||
final idx = state.positionIndex;
|
final idx = state.positionIndex;
|
||||||
if (idx >= route.positions.length) return '✅ Servicio finalizado';
|
if (idx >= route.positions.length) return ' Servicio finalizado';
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case 0: return '🕐 Ruta por iniciar';
|
case 0: return 'Ruta por iniciar';
|
||||||
case 1: return '🚛 Camión en camino — mantén tus bolsas adentro';
|
case 1: return ' Camión en camino — mantén tus bolsas adentro';
|
||||||
case 2: return '🚛 Aprox. 30 min — espera el aviso de 15 min';
|
case 2: return ' Aprox. 30 min — espera el aviso de 15 min';
|
||||||
case 3: return '⚠️ ¡15 min! Saca tus bolsas a la acera ahora';
|
case 3: return ' ¡15 min! Saca tus bolsas a la acera ahora';
|
||||||
case 4: return '🔔 El camión está en tu colonia';
|
case 4: return ' El camión está en tu colonia';
|
||||||
case 5: return '✅ Recogiendo basura en tu zona';
|
case 5: return 'Recogiendo basura en tu zona';
|
||||||
case 6: return '↩️ Regresando al relleno sanitario';
|
case 6: return 'Regresando al relleno sanitario';
|
||||||
default: return '🏁 Servicio del día finalizado';
|
default: return ' Servicio del día finalizado';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user