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( '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), ); }, ); }