fix(alarm): arm a just-passed occurrence instead of skipping it a day
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m42s

The native next-occurrence recompute required the trigger to be
strictly in the future, while the Dart side keeps an occurrence whose
trigger passed within a 90s tolerance. When the periodic resync
re-armed an alarm microseconds after its trigger (app foregrounded,
the 60s tick straddling the trigger instant), computeNextTriggerMillis
recomputed the next weekday/daily occurrence as tomorrow and, through
the shared FLAG_UPDATE_CURRENT fire PendingIntent, replaced the
in-flight fire before AlarmManager delivered it. The alarm never rang
until the screen was turned on and the Dart watchdog caught it late.

computeNextTriggerMillis now mirrors Dart's toleranciaDisparoInminente:
base is lowered by a 90s grace window so a just-passed occurrence is
armed (and delivered ~immediately) rather than pushed to the next day.
The handledFloor (lastHandledAtMillis + 60s) stays a hard lower bound,
so an already-fired occurrence can never be re-selected — no
double-fire. Dart contract tests lock the boundary the native constant
must track. Native verification is on-device (no JVM test harness).
This commit is contained in:
2026-07-11 23:02:14 +02:00
parent 812922d7f1
commit 3ebb41aa9d
2 changed files with 59 additions and 1 deletions
@@ -167,6 +167,50 @@ void main() {
expect(proxima, DateTime(2026, 5, 26, 7, 30));
});
test('mantiene la ocurrencia diaria cuyo disparo acaba de pasar', () {
// Contract the native AlarmScheduler.computeNextTriggerMillis MUST
// mirror: a trigger that just passed within toleranciaDisparoInminente
// (90s) and has NOT been handled is kept (normalized to desde + 2s),
// never skipped to tomorrow. The native side lacking this window is
// what recomputed the next occurrence as tomorrow and cancelled the
// in-flight fire, so the alarm never rang until the screen was turned
// on. If this expectation changes, the native constant
// IMMINENT_TOLERANCE_MILLIS must change with it.
final alarma = AlarmaMusical(
id: 'a-inminente-diaria',
nombre: 'Diaria inminente',
hora: 22,
minuto: 35,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: const [],
);
final proxima = servicio.calcularProxima(
alarma: alarma,
desde: DateTime(2026, 7, 11, 22, 35, 30),
);
expect(proxima, DateTime(2026, 7, 11, 22, 35, 32));
});
test('mantiene la ocurrencia por dias de semana recien pasada', () {
final alarma = AlarmaMusical(
id: 'a-inminente-semana',
nombre: 'Semana inminente',
hora: 22,
minuto: 35,
tipoProgramacion: TipoProgramacionAlarma.diasSemana,
diasSemana: const [1, 2, 3, 4, 5, 6, 7],
);
final proxima = servicio.calcularProxima(
alarma: alarma,
desde: DateTime(2026, 7, 11, 22, 35, 30),
);
expect(proxima, DateTime(2026, 7, 11, 22, 35, 32));
});
test('calcula siguiente por dias de semana despues de ejecucion', () {
final alarma = AlarmaMusical(
id: 'a7',