feat(alarmas): surface the three native scheduling failures in Dart

Completes the bridge the native side already exposed. AlarmScheduler and
PluriWaveAlarmService record a pre-notice that could not be armed, a
refused foreground-service start, and a per-alarm reschedule that failed
after a reboot -- but nothing read them, so all three still ended at
logcat.

EstadoAlarmas now drains them at startup and turns each into a per-alarm
exception, which the card already knows how to mark. An alarm that never
reached the OS stops looking identical to one that did.

The read is deliberately tolerant: a failure to read is logged and
swallowed, never surfaced as an alarm error, so a diagnostics gap cannot
masquerade as a scheduling problem.
This commit is contained in:
2026-07-31 23:24:01 +02:00
parent 7722f204ca
commit a8dca83cd9
10 changed files with 599 additions and 16 deletions
@@ -2,6 +2,7 @@ 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';
@@ -73,4 +74,66 @@ void main() {
expect(estado.ultimaExcepcionPara('sana1'), isNull);
expect(estado.error, isNull);
});
test(
'inicializar importa los fallos nativos (pre-aviso, servicio en primer '
'plano, reprogramacion tras arranque) como excepciones por alarma',
() async {
final android =
FakePuertoAlarmasAndroid()
..fallosProgramacionNativos.addAll([
FalloProgramacionNativo(
alarmaId: 'con-preaviso-roto',
tipo: ExcepcionAlarma.tipoFalloPreaviso,
ocurridoEn: DateTime(2026, 5, 25, 7),
),
FalloProgramacionNativo(
alarmaId: 'servicio-rechazado',
tipo: ExcepcionAlarma.tipoFalloServicioSonido,
ocurridoEn: DateTime(2026, 5, 25, 7),
),
]);
final servicio = ServicioAlarmas(
reloj: () => DateTime(2026, 5, 25, 7, 30),
);
await servicio.guardarAlarma(
AlarmaMusical(
id: 'con-preaviso-roto',
nombre: 'Diaria',
hora: 7,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: const [],
),
);
await servicio.guardarAlarma(
AlarmaMusical(
id: 'servicio-rechazado',
nombre: 'Diaria',
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();
final falloPreaviso = estado.ultimaExcepcionPara('con-preaviso-roto');
expect(falloPreaviso, isNotNull);
expect(falloPreaviso!.tipo, ExcepcionAlarma.tipoFalloPreaviso);
final falloServicio = estado.ultimaExcepcionPara('servicio-rechazado');
expect(falloServicio, isNotNull);
expect(falloServicio!.tipo, ExcepcionAlarma.tipoFalloServicioSonido);
},
);
}