import 'dart:async'; import 'package:flutter/foundation.dart'; import '../modelos/alarma_musical.dart'; import '../servicios/servicio_alarmas.dart'; import '../servicios/servicio_alarmas_android.dart'; class EstadoAlarmas extends ChangeNotifier { EstadoAlarmas({ ServicioAlarmas? servicio, ServicioAlarmasAndroid? android, bool iniciarAutomaticamente = true, }) : servicio = servicio ?? ServicioAlarmas(), android = android ?? ServicioAlarmasAndroid() { if (iniciarAutomaticamente) { inicializar(); } } final ServicioAlarmas servicio; final ServicioAlarmasAndroid android; List _alarmas = []; List _vacaciones = []; List _excepciones = []; DiagnosticoAlarmasAndroid? _diagnostico; Timer? _refresco; Timer? _vigilancia; final _alarmasVencidasController = StreamController.broadcast(); final Set _ejecucionesEmitidas = {}; bool _cargando = false; String? _error; List get alarmas => List.unmodifiable(_alarmas); List get vacaciones => List.unmodifiable(_vacaciones); List get excepciones => List.unmodifiable(_excepciones); DiagnosticoAlarmasAndroid? get diagnostico => _diagnostico; bool get cargando => _cargando; String? get error => _error; Stream get alarmasVencidasStream => _alarmasVencidasController.stream; AlarmaMusical? get proximaAlarma { final candidatas = _alarmas.where((a) => a.activa && a.proximaEjecucion != null).toList() ..sort((a, b) => a.proximaEjecucion!.compareTo(b.proximaEjecucion!)); return candidatas.isEmpty ? null : candidatas.first; } Future inicializar() async { debugPrint('[PluriWave][alarmas] inicializar'); _cargando = true; _error = null; notifyListeners(); try { final config = await servicio.cargar(); _aplicar(config); debugPrint('[PluriWave][alarmas] cargadas=${_alarmas.length} vacaciones=${_vacaciones.length} excepciones=${_excepciones.length}'); await _sincronizarTodas(); await cargarDiagnostico(); _activarRefresco(); } catch (e) { _error = 'No se pudieron cargar las alarmas: $e'; debugPrint('[PluriWave][alarmas] inicializar ERROR $e'); } finally { _cargando = false; notifyListeners(); } } Future guardarAlarma(AlarmaMusical alarma) async { debugPrint('[PluriWave][alarmas] guardar id=${alarma.id} activa=${alarma.activa} hora=${alarma.hora}:${alarma.minuto} tipo=${alarma.tipoProgramacion.name}'); final config = await servicio.guardarAlarma(alarma); _aplicar(config); try { final guardada = _alarmas.firstWhere((a) => a.id == alarma.id); debugPrint('[PluriWave][alarmas] guardada id=${guardada.id} proxima=${guardada.proximaEjecucion?.toIso8601String()}'); await android.programar(guardada); } catch (e) { _error = 'Alarma guardada, pero Android no pudo programarla todavĂ­a: $e'; } notifyListeners(); } Future refrescarProgramacion() async { debugPrint('[PluriWave][alarmas] refrescar programacion'); final config = await servicio.recalcularTodas(); _aplicar(config); await _sincronizarTodas(); notifyListeners(); } Future eliminarAlarma(String id) async { debugPrint('[PluriWave][alarmas] eliminar id=$id'); final config = await servicio.eliminarAlarma(id); _aplicar(config); await android.cancelar(id); notifyListeners(); } Future cambiarActiva(AlarmaMusical alarma, bool activa) async { await guardarAlarma(alarma.copyWith(activa: activa)); } Future saltarProxima(String alarmaId) async { debugPrint('[PluriWave][alarmas] saltar proxima id=$alarmaId'); final config = await servicio.saltarProxima(alarmaId); _aplicar(config); AlarmaMusical? alarma; for (final item in _alarmas) { if (item.id == alarmaId) { alarma = item; break; } } if (alarma != null) { await android.programar(alarma); } notifyListeners(); } Future guardarVacaciones(List vacaciones) async { debugPrint('[PluriWave][alarmas] guardar vacaciones count=${vacaciones.length}'); final config = await servicio.guardarVacaciones(vacaciones); _aplicar(config); await _sincronizarTodas(); notifyListeners(); } Future posponerAlarma(AlarmaMusical alarma, int minutos) async { final proxima = DateTime.now().add(Duration(minutes: minutos)); debugPrint('[PluriWave][alarmas] posponer id=${alarma.id} minutos=$minutos proxima=${proxima.toIso8601String()}'); await android.ocultarNotificacionAlarma(alarma.id); await android.programar(alarma.copyWith(proximaEjecucion: proxima)); } Future finalizarEjecucion(String alarmaId) async { debugPrint('[PluriWave][alarmas] finalizar ejecucion id=$alarmaId'); await android.ocultarNotificacionAlarma(alarmaId); await refrescarProgramacion(); } Future crearRangoVacaciones(RangoVacaciones rango) async { final nuevos = [..._vacaciones, rango]; await guardarVacaciones(nuevos); } Future eliminarRangoVacaciones(String id) async { final nuevos = _vacaciones.where((v) => v.id != id).toList(); await guardarVacaciones(nuevos); } ExcepcionAlarma? ultimaExcepcionPara(String alarmaId) { final candidatas = _excepciones.where((e) => e.alarmaId == alarmaId).toList() ..sort((a, b) => b.ejecucion.compareTo(a.ejecucion)); return candidatas.isEmpty ? null : candidatas.first; } Future cargarDiagnostico() async { try { _diagnostico = await android.diagnostico(); } catch (e) { debugPrint('[PluriWave][alarmas] diagnostico ERROR $e'); _diagnostico = null; } notifyListeners(); } Future _sincronizarTodas() async { debugPrint('[PluriWave][alarmas] sincronizar todas count=${_alarmas.length}'); for (final alarma in _alarmas) { await android.programar(alarma); } } void _aplicar(ConfiguracionAlarmas config) { _alarmas = config.alarmas; _vacaciones = config.vacaciones; _excepciones = config.excepciones; } void _activarRefresco() { _refresco?.cancel(); _refresco = Timer.periodic(const Duration(minutes: 1), (_) { refrescarProgramacion(); }); _vigilarAlarmasVencidas(); _vigilancia?.cancel(); _vigilancia = Timer.periodic(const Duration(seconds: 10), (_) { _vigilarAlarmasVencidas(); }); } void _vigilarAlarmasVencidas() { final ahora = DateTime.now(); for (final alarma in _alarmas) { final proxima = alarma.proximaEjecucion; if (!alarma.activa || proxima == null) continue; if (proxima.isAfter(ahora)) continue; final key = '${alarma.id}:${proxima.millisecondsSinceEpoch}'; if (_ejecucionesEmitidas.add(key)) { debugPrint('[PluriWave][alarmas] vencida local id=${alarma.id} proxima=${proxima.toIso8601String()}'); _alarmasVencidasController.add(alarma); } } } @override void dispose() { _refresco?.cancel(); _vigilancia?.cancel(); _alarmasVencidasController.close(); super.dispose(); } }