Files
home-control/lib/screens/servicios_screen.dart
Super User bbd20f66da Facturas recurrentes, edición, padding, y política de privacidad
- Servicio: nuevo campo esRecurrente + migración DB v2
- Al marcar pagada una factura recurrente se genera la siguiente
- Editar facturas existentes desde CrearFacturaScreen
- Nuevo método actualizarFactura en FacturaProvider
- padding inferior en listas de facturas y servicios (último item visible)
- Nota de factura visible en subtítulo
- Filtro Vencidas corregido
- Monto pendiente total visible en dashboard
- Botón undo para revertir factura pagada
- Configuración de release signing (keystore)
- Pantalla de política de privacidad en la app + PRIVACY_POLICY.md
2026-06-15 21:45:59 -04:00

103 lines
4.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/servicio_provider.dart';
import 'crear_servicio_screen.dart';
class ServiciosScreen extends StatelessWidget {
const ServiciosScreen({super.key});
@override
Widget build(BuildContext context) {
final provider = context.watch<ServicioProvider>();
return Scaffold(
appBar: AppBar(title: const Text('Servicios')),
body: provider.cargando
? const Center(child: CircularProgressIndicator())
: provider.servicios.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.electrical_services, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text('No hay servicios registrados', style: Theme.of(context).textTheme.bodyLarge),
const SizedBox(height: 8),
Text('Agrega tu primer servicio', style: Theme.of(context).textTheme.bodyMedium),
],
),
)
: ListView.builder(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 80),
itemCount: provider.servicios.length,
itemBuilder: (_, i) {
final s = provider.servicios[i];
return TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: Duration(milliseconds: 300 + i * 60),
curve: Curves.easeOutCubic,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.translate(
offset: Offset(0, 20 * (1 - value)),
child: child,
),
);
},
child: Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: CircleAvatar(
backgroundColor: Color(s.color),
child: Icon(_getIcon(s.icono), color: Colors.white),
),
title: Text(s.nombre, style: const TextStyle(fontWeight: FontWeight.w500)),
subtitle: Text('Vence día ${s.diaVencimiento}'),
trailing: IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red),
onPressed: () async {
final ok = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: const Text('Eliminar servicio'),
content: Text('¿Eliminar ${s.nombre}? Se eliminarán sus facturas.'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancelar')),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Eliminar')),
],
),
);
if (ok == true) provider.eliminarServicio(s.id!);
},
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => CrearServicioScreen(servicio: s)),
).then((_) => provider.cargarServicios()),
),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const CrearServicioScreen())).then((_) => provider.cargarServicios()),
child: const Icon(Icons.add),
),
);
}
IconData _getIcon(String name) {
switch (name) {
case 'electricidad': return Icons.bolt;
case 'agua': return Icons.water_drop;
case 'internet': return Icons.wifi;
case 'cable': return Icons.tv;
case 'telefono': return Icons.phone;
case 'gas': return Icons.local_fire_department;
case 'alquiler': return Icons.home;
default: return Icons.receipt;
}
}
}