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
@@ -75,41 +75,38 @@ void main() {
expect(proxima, DateTime(2026, 5, 23, 9));
});
test(
'un registro de fallo de programacion NO omite esa ejecucion (solo '
'skipNext debe hacerlo)',
() {
final alarma = AlarmaMusical(
id: 'a4',
nombre: 'Diaria',
hora: 9,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: const [],
);
final ocurrencia = DateTime(2026, 5, 22, 9);
test('un registro de fallo de programacion NO omite esa ejecucion (solo '
'skipNext debe hacerlo)', () {
final alarma = AlarmaMusical(
id: 'a4',
nombre: 'Diaria',
hora: 9,
minuto: 0,
tipoProgramacion: TipoProgramacionAlarma.diaria,
diasSemana: const [],
);
final ocurrencia = DateTime(2026, 5, 22, 9);
final proxima = servicio.calcularProxima(
alarma: alarma,
desde: DateTime(2026, 5, 22, 8),
excepciones: [
ExcepcionAlarma(
alarmaId: 'a4',
ejecucion: ocurrencia,
tipo: ExcepcionAlarma.tipoFalloProgramacion,
),
],
);
final proxima = servicio.calcularProxima(
alarma: alarma,
desde: DateTime(2026, 5, 22, 8),
excepciones: [
ExcepcionAlarma(
alarmaId: 'a4',
ejecucion: ocurrencia,
tipo: ExcepcionAlarma.tipoFalloProgramacion,
),
],
);
expect(
proxima,
ocurrencia,
reason:
'un fallo de programacion registrado no debe comportarse '
'como un salto de usuario',
);
},
);
expect(
proxima,
ocurrencia,
reason:
'un fallo de programacion registrado no debe comportarse '
'como un salto de usuario',
);
});
test('snooze solo permite 3, 5 o 10 minutos y cae a 5', () {
expect(
@@ -354,4 +351,87 @@ void main() {
expect(siguiente, isNull);
});
});
group('ServicioProgramacionAlarmas.normalizarFechaUnica', () {
final servicio = ServicioProgramacionAlarmas();
test('rolls to tomorrow when the chosen time already passed today', () {
final fecha = servicio.normalizarFechaUnica(
fecha: DateTime(2026, 9, 22),
hora: 5,
minuto: 30,
ahora: DateTime(2026, 9, 22, 18, 30),
);
expect(fecha, DateTime(2026, 9, 23, 5, 30));
});
test('keeps today when the chosen time is still ahead', () {
final fecha = servicio.normalizarFechaUnica(
fecha: DateTime(2026, 9, 22),
hora: 21,
minuto: 45,
ahora: DateTime(2026, 9, 22, 18, 30),
);
expect(fecha, DateTime(2026, 9, 22, 21, 45));
});
test('never overrides a date the user deliberately picked ahead', () {
final fecha = servicio.normalizarFechaUnica(
fecha: DateTime(2026, 9, 27),
hora: 5,
minuto: 30,
ahora: DateTime(2026, 9, 22, 18, 30),
);
expect(fecha, DateTime(2026, 9, 27, 5, 30));
});
test('snaps a long-stale date to today or tomorrow, not to date + 1', () {
final fecha = servicio.normalizarFechaUnica(
fecha: DateTime(2026, 9, 10),
hora: 5,
minuto: 30,
ahora: DateTime(2026, 9, 22, 18, 30),
);
expect(fecha, DateTime(2026, 9, 23, 5, 30));
});
test('stays today inside the imminent-trigger tolerance', () {
final fecha = servicio.normalizarFechaUnica(
fecha: DateTime(2026, 9, 22),
hora: 18,
minuto: 30,
ahora: DateTime(2026, 9, 22, 18, 30, 45),
);
expect(fecha, DateTime(2026, 9, 22, 18, 30));
});
test('the rolled date is what calcularProxima can actually schedule', () {
final ahora = DateTime(2026, 9, 22, 18, 30);
final fecha = servicio.normalizarFechaUnica(
fecha: DateTime(2026, 9, 22),
hora: 5,
minuto: 30,
ahora: ahora,
);
final alarma = AlarmaMusical(
id: 'unica-rodada',
nombre: 'Unica',
hora: 5,
minuto: 30,
tipoProgramacion: TipoProgramacionAlarma.unica,
diasSemana: const [],
fechaUnica: fecha,
);
expect(
servicio.calcularProxima(alarma: alarma, desde: ahora),
DateTime(2026, 9, 23, 5, 30),
);
});
});
}