Files
home-control/lib/screens/estadisticas_screen.dart
2026-06-14 15:03:29 -04:00

146 lines
6.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'package:fl_chart/fl_chart.dart';
import '../providers/factura_provider.dart';
import '../providers/configuracion_provider.dart';
class EstadisticasScreen extends StatefulWidget {
const EstadisticasScreen({super.key});
@override
State<EstadisticasScreen> createState() => _EstadisticasScreenState();
}
class _EstadisticasScreenState extends State<EstadisticasScreen> {
late int _year;
@override
void initState() {
super.initState();
_year = DateTime.now().year;
}
@override
Widget build(BuildContext context) {
final facturaProv = context.watch<FacturaProvider>();
final configProv = context.watch<ConfiguracionProvider>();
final moneda = configProv.config?.moneda ?? 'USD';
final symbol = moneda == 'USD' ? '\$' : moneda == 'EUR' ? '' : moneda == 'MXN' ? 'MX\$' : '\$';
return Scaffold(
appBar: AppBar(title: const Text('Estadísticas')),
body: FutureBuilder(
future: Future.wait([
facturaProv.gastoMensual(_year),
facturaProv.gastosPorServicio(_year, DateTime.now().month),
facturaProv.totalGastadoMes(_year, DateTime.now().month),
]),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
final gastoMensual = snapshot.data![0] as List<Map<String, dynamic>>;
final gastoPorServicio = snapshot.data![1] as Map<String, double>;
final totalMes = snapshot.data![2] as double;
return ListView(
padding: const EdgeInsets.all(16),
children: [
Text('Resumen $_year', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Gasto mensual actual', style: Theme.of(context).textTheme.titleSmall),
Text('$symbol${NumberFormat('#,##0.00').format(totalMes)}',
style: Theme.of(context).textTheme.headlineMedium),
],
),
),
),
const SizedBox(height: 20),
Text('Gasto mensual ($_year)', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
SizedBox(
height: 200,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: (gastoMensual.isEmpty ? 1000 : gastoMensual.map((e) => (e['total'] as num).toDouble()).reduce((a, b) => a > b ? a : b)) * 1.2,
barGroups: List.generate(12, (i) {
final mes = (i + 1).toString().padLeft(2, '0');
final data = gastoMensual.where((e) => e['mes'] == mes).firstOrNull;
return BarChartGroupData(
x: i,
barRods: [
BarChartRodData(
toY: data != null ? (data['total'] as num).toDouble() : 0,
color: Theme.of(context).colorScheme.primary,
width: 16,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
),
],
);
}),
titlesData: FlTitlesData(
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, reservedSize: 40, getTitlesWidget: (v, _) => Text('$symbol${v.toInt()}', style: const TextStyle(fontSize: 10)))),
bottomTitles: AxisTitles(sideTitles: SideTitles(showTitles: true, getTitlesWidget: (v, _) {
const meses = ['E', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'];
return Text(meses[v.toInt()], style: const TextStyle(fontSize: 10));
})),
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
gridData: FlGridData(show: true, drawVerticalLine: false, horizontalInterval: (gastoMensual.isEmpty ? 1000 : gastoMensual.map((e) => (e['total'] as num).toDouble()).reduce((a, b) => a > b ? a : b)) / 4),
borderData: FlBorderData(show: false),
),
),
),
const SizedBox(height: 20),
Text('Distribución por servicio', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
if (gastoPorServicio.isEmpty)
Card(child: Padding(padding: const EdgeInsets.all(16), child: Text('Sin datos este mes')))
else
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: gastoPorServicio.entries.map((e) {
final total = gastoPorServicio.values.fold(0.0, (a, b) => a + b);
final pct = total > 0 ? e.value / total * 100 : 0.0;
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
children: [
SizedBox(width: 100, child: Text(e.key, style: const TextStyle(fontSize: 13))),
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: pct / 100,
backgroundColor: Colors.grey[200],
minHeight: 12,
),
),
),
const SizedBox(width: 8),
SizedBox(width: 70, child: Text('$symbol${NumberFormat('#,##0.00').format(e.value)}', style: const TextStyle(fontSize: 12), textAlign: TextAlign.right)),
],
),
);
}).toList(),
),
),
),
],
);
},
),
);
}
}