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,140 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:gimnasio_soma/features/huerfanas/data/repositories/huerfanas_repository_impl.dart';
import 'package:gimnasio_soma/features/huerfanas/domain/entities/reserva_huerfana.dart';
import 'package:gimnasio_soma/features/huerfanas/domain/repositories/huerfanas_repository.dart';
import 'package:gimnasio_soma/features/turnos/presentation/providers/turnos_provider.dart';
final huerfanasRepositoryProvider = Provider<HuerfanasRepository>((ref) {
return HuerfanasRepositoryImpl();
});
String _errorMessage(Object e) {
if (e is PostgrestException) return e.message;
return e.toString().replaceFirst('Exception: ', '');
}
final huerfanasProvider =
StateNotifierProvider<HuerfanasNotifier, AsyncValue<List<ReservaHuerfana>>>(
(ref) {
return HuerfanasNotifier(ref, ref.read(huerfanasRepositoryProvider));
});
class HuerfanasNotifier
extends StateNotifier<AsyncValue<List<ReservaHuerfana>>> {
final Ref _ref;
final HuerfanasRepository _repository;
String? _currentEstado = 'pendiente';
HuerfanasNotifier(this._ref, this._repository)
: super(const AsyncValue.loading()) {
load();
}
Future<void> load() async {
state = const AsyncValue.loading();
try {
final data = await _repository.obtenerHuerfanas(estado: _currentEstado);
state = AsyncValue.data(data);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
Future<void> filtrar(String? estado) async {
_currentEstado = estado;
await load();
}
String? get currentEstado => _currentEstado;
/// Retorna null si tuvo éxito, o un mensaje de error.
Future<String?> resolver(String huerfanaId, String nuevoEstado) async {
try {
await _repository.resolverHuerfana(huerfanaId, nuevoEstado);
await load();
return null;
} catch (e) {
return _errorMessage(e);
}
}
/// Reserva el turno y marca la huérfana como 'reubicado' atómicamente.
/// Retorna null si tuvo éxito, o un mensaje de error.
///
/// La reubicación ocupa un cupo en [turnoId]. Invalidamos turnosProvider
/// para que la pantalla de Turnos no muestre un cupo desactualizado si ya
/// tenía esa semana cacheada de antes.
Future<String?> mover(String huerfanaId, String turnoId) async {
try {
await _repository.moverHuerfana(huerfanaId, turnoId);
await load();
_ref.invalidate(turnosProvider);
return null;
} catch (e) {
return _errorMessage(e);
}
}
/// Marca todas las huérfanas del conjunto como 'resuelta' (best-effort).
Future<void> notificarLote(Iterable<String> ids) async {
for (final id in ids) {
try {
await _repository.resolverHuerfana(id, 'resuelta');
} catch (_) {
// best-effort: continúa con las demás aunque alguna falle
}
}
await load();
}
}
// ── Selección múltiple ────────────────────────────────────────────────────────
final huerfanasModoSeleccionProvider = StateProvider<bool>((ref) => false);
class _SeleccionNotifier extends StateNotifier<Set<String>> {
_SeleccionNotifier() : super({});
void toggle(String id) {
final next = {...state};
if (next.contains(id)) {
next.remove(id);
} else {
next.add(id);
}
state = next;
}
void limpiar() => state = {};
}
final huerfanasSeleccionProvider =
StateNotifierProvider<_SeleccionNotifier, Set<String>>(
(ref) => _SeleccionNotifier());
// ── Badge sidebar ─────────────────────────────────────────────────────────────
/// Cantidad de reservas huérfanas pendientes — usado para el badge en sidebar.
///
/// Observa [huerfanasProvider] para recomputarse tras cualquier mutación.
/// Si el filtro activo es 'pendiente' o null derivamos el conteo en memoria
/// (sin RPC extra). Si el filtro es otro, hacemos una consulta independiente.
final huerfanasPendienteCountProvider =
FutureProvider.autoDispose<int>((ref) async {
final state = ref.watch(huerfanasProvider);
final notifier = ref.read(huerfanasProvider.notifier);
final lista = state.valueOrNull;
if (lista != null) {
if (notifier.currentEstado == 'pendiente') return lista.length;
if (notifier.currentEstado == null) {
return lista.where((r) => r.estado == EstadoHuerfana.pendiente).length;
}
}
// Filtro activo no es pendiente/todas: hacemos la consulta directa.
final repo = ref.read(huerfanasRepositoryProvider);
final pendientes = await repo.obtenerHuerfanas(estado: 'pendiente');
return pendientes.length;
});