Compare commits
2
Commits
cb76f09257
...
3a4dc3b4b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a4dc3b4b1 | ||
|
|
04300592e0 |
@@ -568,13 +568,34 @@ class ServicioAlarmas {
|
|||||||
alarma.activa &&
|
alarma.activa &&
|
||||||
alarma.snoozeHasta != null &&
|
alarma.snoozeHasta != null &&
|
||||||
alarma.snoozeHasta!.isAfter(ahora);
|
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(
|
final proxima = _programacion.calcularProxima(
|
||||||
alarma: alarma,
|
alarma: saneada,
|
||||||
desde: ahora,
|
desde: ahora,
|
||||||
vacaciones: vacaciones,
|
vacaciones: vacaciones,
|
||||||
excepciones: excepciones,
|
excepciones: excepciones,
|
||||||
);
|
);
|
||||||
return alarma.copyWith(
|
return saneada.copyWith(
|
||||||
proximaEjecucion: proxima,
|
proximaEjecucion: proxima,
|
||||||
limpiarProximaEjecucion: true,
|
limpiarProximaEjecucion: true,
|
||||||
limpiarSnooze: !snoozeActivo,
|
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(
|
test(
|
||||||
'Detener sin nada sonando tampoco consume la próxima ocurrencia',
|
'Detener sin nada sonando tampoco consume la próxima ocurrencia',
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
Reference in New Issue
Block a user