fix(alarmas): una alarma de un solo uso a una hora ya pasada suena manana
Build & Deploy PluriWave / Análisis de código (push) Successful in 32s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 3m11s

El editor dejaba la fecha clavada en hoy, asi que al escribir 05:30 siendo
las 18:30 el candidato quedaba en el pasado, `calcularProxima` lo rechazaba
con razon y la alarma se guardaba sin proxima ejecucion: nunca sonaba. En la
practica solo se podian poner alarmas unicas para lo que quedaba de dia.

`normalizarFechaUnica` resuelve el dia que el usuario realmente quiere, con
la convencion de cualquier despertador: hora ya pasada -> manana, hora por
llegar -> hoy. Una fecha elegida a proposito en el futuro no se toca nunca,
y una fecha rancia salta a hoy/manana en vez de a `fecha + 1`, que seguiria
en el pasado.

La regla vive en el editor y NO dentro de `calcularProxima`: esa tiene que
seguir siendo literal, porque el recalculo posterior al disparo y el motor
nativo dependen de que una alarma unica vencida resuelva a null en vez de
resucitar al dia siguiente.

De paso el editor pasa a leer el reloj inyectado del servicio en vez de
`DateTime.now()`, para que la vista previa, los limites del selector de
fecha y el ajuste lean el mismo instante y se puedan fijar en las pruebas.
This commit is contained in:
2026-09-22 13:14:32 +02:00
parent 2891a5703e
commit 44af98eac3
4 changed files with 272 additions and 41 deletions
@@ -53,6 +53,36 @@ class ServicioProgramacionAlarmas {
};
}
/// Resolves the wall-clock date a ONE-TIME alarm should actually land on,
/// given the time the user just typed. Alarm clocks everywhere read "05:30"
/// at 18:30 as "05:30 tomorrow"; without this, `calcularProxima` correctly
/// refuses a candidate that is already in the past and the alarm silently
/// never fires. The rollover belongs here and NOT inside `calcularProxima`:
/// that one must stay literal, because the post-execution path and the
/// native scheduler both rely on a past one-shot resolving to null instead
/// of resurrecting itself on the next day.
///
/// A date the user picked deliberately in the future is never overridden.
/// A stale date (an old draft reopened days later) snaps to today/tomorrow
/// rather than to `fecha + 1`, which would still be in the past.
///
/// Day arithmetic goes through the DateTime constructor, never
/// `add(Duration(days: 1))` — see `_siguienteDia` for why.
DateTime normalizarFechaUnica({
required DateTime fecha,
required int hora,
required int minuto,
required DateTime ahora,
}) {
final elegido = DateTime(fecha.year, fecha.month, fecha.day, hora, minuto);
if (_sigueSiendoInminente(elegido, ahora)) return elegido;
final hoy = DateTime(ahora.year, ahora.month, ahora.day, hora, minuto);
return _sigueSiendoInminente(hoy, ahora)
? hoy
: DateTime(ahora.year, ahora.month, ahora.day + 1, hora, minuto);
}
DateTime calcularSnooze(DateTime desde, int minutos) {
final seguro = minutos == 3 || minutos == 5 || minutos == 10 ? minutos : 5;
return desde.add(Duration(minutes: seguro));