import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:pluriwave/estado/estado_alarmas.dart'; import 'package:pluriwave/modelos/alarma_musical.dart'; import 'package:pluriwave/servicios/servicio_alarmas.dart'; import 'package:pluriwave/servicios/servicio_alarmas_android.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../helpers/fakes_alarmas.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() { SharedPreferences.setMockInitialValues({}); }); test('posponer persiste snooze y refrescarProgramacion no lo pisa', () async { var ahora = DateTime(2026, 5, 25, 7, 31); final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => ahora), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'a1', nombre: 'Diaria', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); final alarma = estado.alarmas.single; await estado.posponerAlarma(alarma, 5); // Ancla unificada (S2-R6): proximaEjecucion (7:31:02, normalizada por // inminencia) + 5 minutos — ya no "ahora + 5". expect(estado.alarmas.single.snoozeHasta, DateTime(2026, 5, 25, 7, 36, 2)); expect( android.programadas.last.proximaProgramable, DateTime(2026, 5, 25, 7, 36, 2), ); ahora = DateTime(2026, 5, 25, 7, 32); await estado.refrescarProgramacion(); expect(estado.alarmas.single.snoozeHasta, DateTime(2026, 5, 25, 7, 36, 2)); expect( android.programadas.last.proximaProgramable, DateTime(2026, 5, 25, 7, 36, 2), ); }); test( 'posponer desde preaviso mueve esta ejecucion desde la hora original', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( AlarmaMusical( id: 'pre1', nombre: 'Preaviso', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], proximaEjecucion: DateTime(2026, 5, 25, 7, 30), ), ); final alarma = estado.alarmas.single; await estado.posponerProximaDesdePreaviso( alarma, 10, DateTime(2026, 5, 25, 7, 30), ); expect(estado.alarmas.single.snoozeHasta, DateTime(2026, 5, 25, 7, 40)); expect(estado.alarmas.single.snoozeOrigen, DateTime(2026, 5, 25, 7, 30)); expect( android.programadas.last.proximaProgramable, DateTime(2026, 5, 25, 7, 40), ); }, ); test('finalizar diaria calcula siguiente dia y limpia snooze', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( AlarmaMusical( id: 'a2', nombre: 'Diaria', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], proximaEjecucion: DateTime(2026, 5, 25, 7, 30), snoozeHasta: DateTime(2026, 5, 25, 7, 36), snoozeOrigen: DateTime(2026, 5, 25, 7, 30), ), ); await estado.finalizarEjecucion('a2'); expect(estado.alarmas.single.snoozeHasta, isNull); expect( estado.alarmas.single.proximaEjecucion, DateTime(2026, 5, 26, 7, 30), ); }); test('finalizar unica la desactiva y queda sin proxima ejecucion', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( AlarmaMusical( id: 'a3', nombre: 'Unica', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.unica, diasSemana: const [], fechaUnica: DateTime(2026, 5, 25), proximaEjecucion: DateTime(2026, 5, 25, 7, 30), ), ); await estado.finalizarEjecucion('a3'); expect(estado.alarmas.single.activa, isFalse); expect(estado.alarmas.single.proximaEjecucion, isNull); }); test( 'solicita exencion de bateria una sola vez cuando no esta exenta', () async { final android = FakePuertoAlarmasAndroid()..ignoraOptimizacionBateria = false; final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'bat1', nombre: 'Bateria', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); expect(android.solicitudesExencionBateria, 1); await estado.guardarAlarma( const AlarmaMusical( id: 'bat2', nombre: 'Bateria 2', hora: 8, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); expect(android.solicitudesExencionBateria, 1); }, ); test('no solicita exencion de bateria cuando ya esta exenta', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'bat3', nombre: 'Exenta', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); expect(android.solicitudesExencionBateria, 0); }); test( 'cambiarActiva(false) detiene el audio cuando la alarma esta sonando (SS-1a)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'ring1', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.alarmaSonandoIdValor = 'ring1'; await estado.cambiarActiva(estado.alarmas.single, false); expect(android.detencionesActivas, contains('ring1')); }, ); test( 'guardarAlarma detiene el audio cuando edita la alarma sonando (SS-1b)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'ring2', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.alarmaSonandoIdValor = 'ring2'; await estado.guardarAlarma(estado.alarmas.single.copyWith(minuto: 45)); expect(android.detencionesActivas, contains('ring2')); }, ); test('eliminarAlarma detiene el audio antes de cancelar cuando esta sonando ' '(SS-1c, guardia de regresion)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'ring3', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.alarmaSonandoIdValor = 'ring3'; await estado.eliminarAlarma('ring3'); expect(android.detencionesActivas, contains('ring3')); expect(android.canceladas, contains('ring3')); }); test('eliminarAlarma usa detenerSonidoNativo cuando la consulta de sonando ' 'falla (fail-toward-silence, regresion de eliminarAlarma)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'ring4', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.fallaConsultaSonando = true; await estado.eliminarAlarma('ring4'); expect(android.detenidas, contains('ring4')); expect(android.canceladas, contains('ring4')); }); test( 'guardarAlarma (deshabilitar) usa detenerSonidoNativo cuando la consulta ' 'de sonando falla (fail-toward-silence)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'ring5', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.fallaConsultaSonando = true; await estado.cambiarActiva(estado.alarmas.single, false); expect(android.detenidas, contains('ring5')); }, ); test( 'mutar una alarma distinta a la que suena no dispara el guard (SS-1d)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'y1', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); await estado.guardarAlarma( const AlarmaMusical( id: 'z1', nombre: 'Quieta', hora: 8, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.alarmaSonandoIdValor = 'y1'; final z1 = estado.alarmas.firstWhere((a) => a.id == 'z1'); await estado.cambiarActiva(z1, false); expect(android.detencionesActivas, isEmpty); }, ); test('finalizarEjecucion no registra error cuando el stop nativo se confirma ' '(SS-2a)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'fin1', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); android.alarmaSonandoIdValor = 'fin1'; await estado.finalizarEjecucion('fin1'); expect(android.detencionesActivas, contains('fin1')); expect(estado.error, isNull); }); test('finalizarEjecucion registra error cuando el stop nativo no se confirma ' '(SS-2b)', () async { final android = FakePuertoAlarmasAndroid()..fallaDetener = true; final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'fin2', nombre: 'Sonando', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); await estado.finalizarEjecucion('fin2'); expect(estado.error, isNotNull); }); test('forzarDetencion reintenta el stop nativo y limpia el error si tiene ' 'exito (SS-3b)', () async { final android = FakePuertoAlarmasAndroid()..fallaDetener = true; final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'force1', nombre: 'Forzada', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); await estado.finalizarEjecucion('force1'); expect(estado.error, isNotNull); android.fallaDetener = false; await estado.forzarDetencion('force1'); expect(estado.error, isNull); expect(android.detencionesActivas.length, 2); }); test( 'forzarDetencion mantiene el error si el reintento tambien falla (SS-3b)', () async { final android = FakePuertoAlarmasAndroid()..fallaDetener = true; final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'force2', nombre: 'Forzada', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); await estado.finalizarEjecucion('force2'); await estado.forzarDetencion('force2'); expect(estado.error, isNotNull); }, ); test('evento nativo missed completa la ejecucion (Phase 6)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7, 31)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( AlarmaMusical( id: 'miss1', nombre: 'Perdida', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], proximaEjecucion: DateTime(2026, 5, 25, 7, 30), ), ); final notificado = Completer(); estado.addListener(() { if (!notificado.isCompleted) notificado.complete(); }); android.emitirEvento( EventoAlarmaAndroid( alarmaId: 'miss1', titulo: 'Perdida', accion: EventoAlarmaAndroid.accionMissed, occurrenceAtMillis: DateTime(2026, 5, 25, 7, 30).millisecondsSinceEpoch, ), ); await notificado.future; expect( estado.alarmas.single.proximaEjecucion, DateTime(2026, 5, 26, 7, 30), ); }); test( 'inicializar sincroniza ejecucion nativa y evita reprogramar al instante', () async { final android = FakePuertoAlarmasAndroid() ..ejecucionesNativas.add( EjecucionAlarmaNativa( alarmaId: 'native1', gestionadaEn: DateTime(2026, 5, 25, 7, 30), ), ); final servicio = ServicioAlarmas( reloj: () => DateTime(2026, 5, 25, 7, 30, 20), ); await servicio.guardarAlarma( AlarmaMusical( id: 'native1', nombre: 'Nativa', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], proximaEjecucion: DateTime(2026, 5, 25, 7, 30), ), ); final estado = EstadoAlarmas( servicio: servicio, android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.inicializar(); expect( estado.alarmas.single.ultimaEjecucionGestionada, DateTime(2026, 5, 25, 7, 30), ); expect( estado.alarmas.single.proximaEjecucion, DateTime(2026, 5, 26, 7, 30), ); expect( android.programadas.last.proximaProgramable, DateTime(2026, 5, 26, 7, 30), ); }, ); test( 'guardarAlarma: cuando android.programar falla, marca la alarma con una ' 'excepcion de fallo visible via ultimaExcepcionPara', () async { final android = FakePuertoAlarmasAndroid()..fallaProgramar = true; final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'fallo1', nombre: 'Diaria', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); final excepcion = estado.ultimaExcepcionPara('fallo1'); expect(excepcion, isNotNull); expect(excepcion!.tipo, ExcepcionAlarma.tipoFalloProgramacion); expect(estado.error, isNotNull); }, ); test( 'guardarAlarma: un reintento exitoso limpia la excepcion de fallo previa', () async { final android = FakePuertoAlarmasAndroid()..fallaProgramar = true; final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); final alarma = const AlarmaMusical( id: 'fallo2', nombre: 'Diaria', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ); await estado.guardarAlarma(alarma); expect(estado.ultimaExcepcionPara('fallo2'), isNotNull); android.fallaProgramar = false; await estado.guardarAlarma(estado.alarmas.single); expect(estado.ultimaExcepcionPara('fallo2'), isNull); }, ); test( 'guardarAlarma en el camino feliz nunca registra una excepcion de fallo', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 5, 25, 7)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'ok1', nombre: 'Diaria', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); expect(estado.ultimaExcepcionPara('ok1'), isNull); expect(estado.error, isNull); }, ); test( 'inicializar: un fallo de programacion en UNA alarma no aborta la ' 'sincronizacion de las demas (S-sincronizarTodas continua tras error)', () async { final android = FakePuertoAlarmasAndroid() ..idsFallanProgramar.add('rota'); final servicio = ServicioAlarmas( reloj: () => DateTime(2026, 5, 25, 6, 0), ); await servicio.guardarAlarma( AlarmaMusical( id: 'rota', nombre: 'Rota', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], ), ); await servicio.guardarAlarma( AlarmaMusical( id: 'sana', nombre: 'Sana', hora: 8, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: const [], ), ); final estado = EstadoAlarmas( servicio: servicio, android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.inicializar(); expect( android.programadas.map((a) => a.id), contains('sana'), reason: 'la alarma sana debe seguir programandose aunque la rota falle', ); expect(estado.ultimaExcepcionPara('rota'), isNotNull); expect(estado.ultimaExcepcionPara('sana'), isNull); }, ); group('EstadoAlarmas — consultas de vacaciones (ADR-6, WU9)', () { test('rangoVacacionesActivo devuelve el rango cuyo intervalo incluye ' '"ahora" (dias restantes derivables de finDia), o null si ninguno ' 'calza (fixed ahora)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 7, 10)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarVacaciones([ RangoVacaciones( id: 'v1', nombre: 'Julio', inicio: DateTime(2026, 7, 5), fin: DateTime(2026, 7, 15), ), ]); final activo = estado.rangoVacacionesActivo(ahora: DateTime(2026, 7, 10)); expect(activo?.id, 'v1'); expect(activo!.finDia.difference(DateTime(2026, 7, 10)).inDays, 5); expect(estado.rangoVacacionesActivo(ahora: DateTime(2026, 8, 1)), isNull); }); test('vacacionesProximas devuelve los rangos futuros ordenados por inicio ' 'ascendente; vacacionesPasadas los finalizados por fin descendente; ' 'ambas son disjuntas del rango activo', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 7, 10)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarVacaciones([ RangoVacaciones( id: 'activo', nombre: 'Activo', inicio: DateTime(2026, 7, 5), fin: DateTime(2026, 7, 15), ), RangoVacaciones( id: 'lejos', nombre: 'Lejos', inicio: DateTime(2026, 12, 1), fin: DateTime(2026, 12, 10), ), RangoVacaciones( id: 'cerca', nombre: 'Cerca', inicio: DateTime(2026, 8, 1), fin: DateTime(2026, 8, 5), ), RangoVacaciones( id: 'viejo', nombre: 'Viejo', inicio: DateTime(2026, 1, 1), fin: DateTime(2026, 1, 10), ), RangoVacaciones( id: 'reciente', nombre: 'Reciente', inicio: DateTime(2026, 6, 1), fin: DateTime(2026, 6, 5), ), ]); final ahora = DateTime(2026, 7, 10); final proximas = estado.vacacionesProximas(ahora: ahora); expect(proximas.map((r) => r.id).toList(), ['cerca', 'lejos']); final pasadas = estado.vacacionesPasadas(ahora: ahora); expect(pasadas.map((r) => r.id).toList(), ['reciente', 'viejo']); expect(proximas.any((r) => r.id == 'activo'), isFalse); expect(pasadas.any((r) => r.id == 'activo'), isFalse); }); test('impactoDeRango separa alarmas activas pausadas de las no afectadas ' 'segun sonarEnVacaciones, igual que el predicado del planificador ' '(servicio_programacion_alarmas.dart)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 7, 10)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'p1', nombre: 'Pausada 1', hora: 7, minuto: 30, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], sonarEnVacaciones: false, ), ); await estado.guardarAlarma( const AlarmaMusical( id: 'p2', nombre: 'Pausada 2', hora: 13, minuto: 45, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], sonarEnVacaciones: false, ), ); await estado.guardarAlarma( const AlarmaMusical( id: 'n1', nombre: 'Sigue', hora: 8, minuto: 15, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); final rango = RangoVacaciones( id: 'v1', nombre: 'Julio', inicio: DateTime(2026, 7, 5), fin: DateTime(2026, 7, 15), ); await estado.guardarVacaciones([rango]); final impacto = estado.impactoDeRango(rango); expect(impacto.pausadas.map((a) => a.id).toSet(), {'p1', 'p2'}); expect(impacto.noAfectadas.map((a) => a.id).toSet(), {'n1'}); }); test('impactoDeRango no cuenta alarmas inactivas ni como pausadas ni como ' 'no afectadas', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 7, 10)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'inactiva', nombre: 'Apagada', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], sonarEnVacaciones: false, activa: false, ), ); final rango = RangoVacaciones( id: 'v1', nombre: 'Julio', inicio: DateTime(2026, 7, 5), fin: DateTime(2026, 7, 15), ); await estado.guardarVacaciones([rango]); final impacto = estado.impactoDeRango(rango); expect(impacto.pausadas, isEmpty); expect(impacto.noAfectadas, isEmpty); }); test('ninguno de los 4 metodos de consulta reprograma Android (guardia de ' 'pureza — son solo lectura sobre _alarmas/_vacaciones)', () async { final android = FakePuertoAlarmasAndroid(); final estado = EstadoAlarmas( servicio: ServicioAlarmas(reloj: () => DateTime(2026, 7, 10)), android: android, iniciarAutomaticamente: false, ); addTearDown(estado.dispose); addTearDown(android.dispose); await estado.guardarAlarma( const AlarmaMusical( id: 'q1', nombre: 'Consulta', hora: 7, minuto: 0, tipoProgramacion: TipoProgramacionAlarma.diaria, diasSemana: [], ), ); final rango = RangoVacaciones( id: 'v1', nombre: 'Julio', inicio: DateTime(2026, 7, 5), fin: DateTime(2026, 7, 15), ); await estado.guardarVacaciones([rango]); final programadasAntes = android.programadas.length; estado.rangoVacacionesActivo(); estado.vacacionesProximas(); estado.vacacionesPasadas(); estado.impactoDeRango(rango); expect(android.programadas.length, programadasAntes); }); }); }