Compare commits
2
Commits
cb76f09257
...
3a4dc3b4b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a4dc3b4b1 | ||
|
|
04300592e0 |
@@ -568,13 +568,34 @@ class ServicioAlarmas {
|
||||
alarma.activa &&
|
||||
alarma.snoozeHasta != null &&
|
||||
alarma.snoozeHasta!.isAfter(ahora);
|
||||
// Self-heal for state poisoned before the Detener anchor fix: a stop
|
||||
// that closed a FUTURE occurrence wrote it into
|
||||
// ultimaEjecucionGestionada, and _esValida rejects any candidate
|
||||
// matching it -- so the alarm silently skips that day forever after,
|
||||
// with nothing in the UI to explain it. An occurrence cannot have been
|
||||
// handled before it happens, so a value meaningfully in the future is
|
||||
// corrupt by definition and safe to drop: it can only ever suppress a
|
||||
// real future ring, never prevent a double-fire (which needs a PAST
|
||||
// occurrence to guard). Placed here, in the recalculation every load and
|
||||
// every mutation already funnels through, so an affected alarm heals on
|
||||
// the next app open with no user action.
|
||||
final gestionada = alarma.ultimaEjecucionGestionada;
|
||||
final gestionadaCorrupta =
|
||||
gestionada != null &&
|
||||
gestionada.isAfter(
|
||||
ahora.add(ServicioProgramacionAlarmas.toleranciaDisparoInminente),
|
||||
);
|
||||
final saneada =
|
||||
gestionadaCorrupta
|
||||
? alarma.copyWith(limpiarUltimaEjecucionGestionada: true)
|
||||
: alarma;
|
||||
final proxima = _programacion.calcularProxima(
|
||||
alarma: alarma,
|
||||
alarma: saneada,
|
||||
desde: ahora,
|
||||
vacaciones: vacaciones,
|
||||
excepciones: excepciones,
|
||||
);
|
||||
return alarma.copyWith(
|
||||
return saneada.copyWith(
|
||||
proximaEjecucion: proxima,
|
||||
limpiarProximaEjecucion: true,
|
||||
limpiarSnooze: !snoozeActivo,
|
||||
|
||||
@@ -96,6 +96,53 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test('estado ya envenenado se cura solo: una ocurrencia futura marcada '
|
||||
'como gestionada se descarta al recalcular', () async {
|
||||
// Devices that ran the buggy build carry the poisoned value in
|
||||
// SharedPreferences. Without this, the fix would still leave the
|
||||
// affected alarm skipping one more time, with nothing in the UI to
|
||||
// explain it -- and the user would reasonably read that as "not fixed".
|
||||
final ahora = DateTime(2026, 8, 3, 9, 0);
|
||||
final servicio = ServicioAlarmas(reloj: () => ahora);
|
||||
|
||||
// Saved by the buggy stop path: next Monday recorded as already handled.
|
||||
await servicio.guardarAlarma(
|
||||
semanalLunes(
|
||||
'a3',
|
||||
).copyWith(ultimaEjecucionGestionada: DateTime(2026, 8, 10, 16, 20)),
|
||||
);
|
||||
|
||||
final config = await servicio.recalcularTodas();
|
||||
final alarma = config.alarmas.single;
|
||||
|
||||
expect(alarma.ultimaEjecucionGestionada, isNull);
|
||||
expect(
|
||||
alarma.proximaEjecucion,
|
||||
DateTime(2026, 8, 3, 16, 20),
|
||||
reason: 'y con el dato corrupto fuera, hoy vuelve a ser candidata',
|
||||
);
|
||||
});
|
||||
|
||||
test('una ocurrencia gestionada REAL (pasada) se conserva: es la que evita '
|
||||
'que la alarma vuelva a sonar en el mismo minuto', () async {
|
||||
final ahora = DateTime(2026, 8, 3, 16, 20, 30);
|
||||
final servicio = ServicioAlarmas(reloj: () => ahora);
|
||||
final gestionada = DateTime(2026, 8, 3, 16, 20);
|
||||
|
||||
await servicio.guardarAlarma(
|
||||
semanalLunes('a4').copyWith(ultimaEjecucionGestionada: gestionada),
|
||||
);
|
||||
|
||||
final alarma = (await servicio.recalcularTodas()).alarmas.single;
|
||||
|
||||
expect(alarma.ultimaEjecucionGestionada, gestionada);
|
||||
expect(
|
||||
alarma.proximaEjecucion,
|
||||
DateTime(2026, 8, 10, 16, 20),
|
||||
reason: 'la de hoy ya sonó, la siguiente es el lunes que viene',
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'Detener sin nada sonando tampoco consume la próxima ocurrencia',
|
||||
() async {
|
||||
|
||||
Reference in New Issue
Block a user