fix(alarm): anchor snooze to the ringing occurrence, never a future one
posponerAlarma anchored the snooze to snoozeOrigen ?? proximaEjecucion, but once the native fire path works, the fire-time sync records the handled occurrence and recalculation advances proximaEjecucion to the NEXT day before the user can even tap snooze on the still-ringing screen. "Posponer 3" therefore armed the snooze a full day out (captured on-device: snoozeCountdown remaining=1443 minutes). The bug was invisible before because the broken delivery path never advanced proximaEjecucion while ringing — each fix unmasked the next. The anchor is now the newest occurrence that is not meaningfully in the future (shared 90s imminence window): snoozeOrigen for re-snoozes, proximaEjecucion on the watchdog path where it is still today's just-due occurrence, ultimaEjecucionGestionada on the native-fire path where the sync recorded the ringing occurrence, then now. ServicioAlarmas exposes ahora() so the anchor uses the same injectable clock as the rest of the scheduling math. Test fixtures that snoozed half an hour before the ring — a state the ringing screen can never be in, since it is posponerAlarma's only production caller — now move the clock to ring time, preserving their original expectations.
This commit is contained in:
@@ -7,6 +7,7 @@ import '../l10n/gen/app_localizations.dart';
|
||||
import '../modelos/alarma_musical.dart';
|
||||
import '../servicios/servicio_alarmas.dart';
|
||||
import '../servicios/servicio_alarmas_android.dart';
|
||||
import '../servicios/servicio_programacion_alarmas.dart';
|
||||
|
||||
class EstadoAlarmas extends ChangeNotifier {
|
||||
EstadoAlarmas({
|
||||
@@ -193,8 +194,27 @@ class EstadoAlarmas extends ChangeNotifier {
|
||||
|
||||
Future<void> posponerAlarma(AlarmaMusical alarma, int minutos) async {
|
||||
_error = null;
|
||||
// The snooze anchors to the occurrence that is RINGING — never a future
|
||||
// one. When the native fire works, the fire-time sync advances
|
||||
// proximaEjecucion to the NEXT day before the user can even tap snooze,
|
||||
// so anchoring to proximaEjecucion re-armed "posponer 3" a full day out
|
||||
// (observed on-device: snooze armed for tomorrow 23:02). The ringing
|
||||
// occurrence is the newest candidate not meaningfully in the future:
|
||||
// snoozeOrigen (a re-snooze keeps the original anchor), then
|
||||
// proximaEjecucion (watchdog path: still today's just-due occurrence),
|
||||
// then ultimaEjecucionGestionada (native-fire path: the sync recorded
|
||||
// the ringing occurrence there), then now.
|
||||
final ahora = servicio.ahora();
|
||||
final limite = ahora.add(
|
||||
ServicioProgramacionAlarmas.toleranciaDisparoInminente,
|
||||
);
|
||||
DateTime? sonando(DateTime? candidata) =>
|
||||
candidata != null && !candidata.isAfter(limite) ? candidata : null;
|
||||
final ejecucion =
|
||||
alarma.snoozeOrigen ?? alarma.proximaEjecucion ?? DateTime.now();
|
||||
sonando(alarma.snoozeOrigen) ??
|
||||
sonando(alarma.proximaEjecucion) ??
|
||||
sonando(alarma.ultimaEjecucionGestionada) ??
|
||||
ahora;
|
||||
debugPrint(
|
||||
'[PluriWave][alarmas] posponer id=${alarma.id} minutos=$minutos ejecucion=${ejecucion.toIso8601String()}',
|
||||
);
|
||||
|
||||
@@ -35,6 +35,11 @@ class ServicioAlarmas {
|
||||
final DateTime Function() _reloj;
|
||||
final _uuid = const Uuid();
|
||||
|
||||
/// Current time through the injected clock, so callers that need "now"
|
||||
/// (e.g. the ring screen's snooze anchor) stay testable with the same
|
||||
/// fixture clock the service itself computes with.
|
||||
DateTime ahora() => _reloj();
|
||||
|
||||
// In-memory cache + single-writer queue (Design 3.5 / S3-R7): every
|
||||
// mutation runs serialized through [_enCola] and reads [_cache], killing
|
||||
// the read-modify-write race the old cargar()-before-each-mutation had.
|
||||
|
||||
Reference in New Issue
Block a user