Agrego frontend app

This commit is contained in:
Pablo
2026-08-22 19:08:19 -03:00
parent 824e092b29
commit 2d0797e627
254 changed files with 37231 additions and 0 deletions
@@ -0,0 +1,152 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:gimnasio_soma/features/pagos/presentation/providers/pagos_provider.dart';
import 'package:gimnasio_soma/features/tipos_cuota/presentation/providers/tipos_cuota_provider.dart';
import 'package:gimnasio_soma/features/usuarios/data/repositories/usuarios_repository_impl.dart';
import 'package:gimnasio_soma/features/usuarios/domain/entities/usuario.dart';
import 'package:gimnasio_soma/features/usuarios/domain/repositories/usuarios_repository.dart';
final usuariosRepositoryProvider = Provider<UsuariosRepository>((ref) {
return UsuariosRepositoryImpl();
});
/// Todos los usuarios sin filtro. Se invalida automáticamente cuando usuariosProvider muta.
final allUsuariosProvider = FutureProvider.autoDispose<List<Usuario>>((ref) async {
final repo = ref.read(usuariosRepositoryProvider);
return repo.getUsuarios();
});
/// Mapa DNI → monto de deuda del mes actual.
/// Se recalcula automáticamente cuando allUsuariosProvider o tiposCuotaProvider cambian.
final usuariosDeudaProvider = FutureProvider.autoDispose<Map<String, double>>((ref) async {
final tiposCuota = ref.watch(tiposCuotaProvider).valueOrNull;
if (tiposCuota == null || tiposCuota.isEmpty) return {};
final usuarios = ref.watch(allUsuariosProvider).valueOrNull;
if (usuarios == null || usuarios.isEmpty) return {};
final pagosRepo = ref.read(pagosRepositoryProvider);
final pagos = await pagosRepo.getPagos(cantidad: 500);
final now = DateTime.now();
final mesActual =
'${now.year}-${now.month.toString().padLeft(2, '0')}-01';
final pagosMes = <String, double>{};
for (final p in pagos) {
if (p.anioMesPagado == mesActual && p.cliente != null) {
pagosMes[p.cliente!.dni] =
(pagosMes[p.cliente!.dni] ?? 0) + p.montoTotal;
}
}
final deuda = <String, double>{};
for (final u in usuarios) {
if (u.tipoCuota == null || !u.isActive) continue;
final tc =
tiposCuota.where((t) => t.id == u.tipoCuota).firstOrNull;
if (tc == null) continue;
double esperado = tc.precio;
if (tc.recargo != null &&
tc.recargo! > 0 &&
now.day > tc.diaDePago) {
esperado += tc.recargo!;
}
final pagado = pagosMes[u.dni] ?? 0;
deuda[u.dni] = esperado - pagado;
}
return deuda;
});
/// Helper compartido: retorna null si el usuario no tiene plan o está inactivo,
/// 0.0 si está al día, >0 si debe.
double? getUsuarioDeuda(Usuario u, Map<String, double> deudaMap) {
if (u.tipoCuota == null || !u.isActive) return null;
return deudaMap[u.dni] ?? 0.0;
}
final usuariosProvider =
StateNotifierProvider.autoDispose<UsuariosNotifier, AsyncValue<List<Usuario>>>((ref) {
return UsuariosNotifier(ref.read(usuariosRepositoryProvider), ref);
});
class UsuariosNotifier extends StateNotifier<AsyncValue<List<Usuario>>> {
final UsuariosRepository _repository;
final Ref _ref;
UsuariosNotifier(this._repository, this._ref) : super(const AsyncValue.loading()) {
loadUsuarios();
}
/// Invalida providers derivados para que refetcheen con datos frescos.
void _invalidateDerived() {
_ref.invalidate(allUsuariosProvider);
// usuariosDeudaProvider y pagosEstadoProvider se recalculan solos
// porque hacen ref.watch(allUsuariosProvider)
}
Future<void> loadUsuarios() async {
state = const AsyncValue.loading();
try {
final usuarios = await _repository.getUsuarios();
state = AsyncValue.data(usuarios);
_invalidateDerived();
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
Future<String?> insertUsuario(Map<String, dynamic> datos) async {
try {
await _repository.insertUsuario(datos);
await loadUsuarios();
return null;
} catch (e) {
return e.toString().replaceFirst('Exception: ', '');
}
}
Future<String?> updateUsuario(Map<String, dynamic> datos) async {
try {
await _repository.updateUsuario(datos);
await loadUsuarios();
return null;
} catch (e) {
return e.toString().replaceFirst('Exception: ', '');
}
}
Future<String?> toggleStatus(String id, bool estado) async {
try {
await _repository.toggleUsuarioStatus(id, estado);
await loadUsuarios();
return null;
} catch (e) {
return e.toString().replaceFirst('Exception: ', '');
}
}
Future<String?> deleteUsuario(String dni) async {
try {
await _repository.deleteUsuario(dni);
await loadUsuarios();
return null;
} catch (e) {
return e.toString().replaceFirst('Exception: ', '');
}
}
/// Reset administrativo de contraseña. No refetchea la lista: no cambia
/// ningún dato visible en ella.
Future<String?> resetearContrasena(String usuarioId, String passwordNueva) async {
try {
await _repository.resetearContrasena(usuarioId, passwordNueva);
return null;
} catch (e) {
return e.toString().replaceFirst('Exception: ', '');
}
}
}
@@ -0,0 +1,48 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:gimnasio_soma/core/config/app_constants.dart';
enum UsuariosViewMode { overview, cards, table }
final usuariosViewModeProvider =
StateNotifierProvider<UsuariosViewModeNotifier, UsuariosViewMode>((ref) {
return UsuariosViewModeNotifier();
});
class UsuariosViewModeNotifier extends StateNotifier<UsuariosViewMode> {
UsuariosViewModeNotifier() : super(UsuariosViewMode.overview) {
_loadViewMode();
}
Future<void> _loadViewMode() async {
final prefs = await SharedPreferences.getInstance();
final stored = prefs.getString(AppConstants.usuariosViewModeKey);
if (stored == 'table') {
state = UsuariosViewMode.table;
} else if (stored == 'cards') {
state = UsuariosViewMode.cards;
} else {
state = UsuariosViewMode.overview;
}
}
Future<void> toggle() async {
final newMode = switch (state) {
UsuariosViewMode.overview => UsuariosViewMode.cards,
UsuariosViewMode.cards => UsuariosViewMode.table,
UsuariosViewMode.table => UsuariosViewMode.overview,
};
await setMode(newMode);
}
Future<void> setMode(UsuariosViewMode mode) async {
state = mode;
final prefs = await SharedPreferences.getInstance();
final stored = switch (mode) {
UsuariosViewMode.table => 'table',
UsuariosViewMode.cards => 'cards',
UsuariosViewMode.overview => 'overview',
};
await prefs.setString(AppConstants.usuariosViewModeKey, stored);
}
}