# Alarm Stop Safety Specification ## Purpose Dart-side fail-safe orchestration: every surface that can act on a ringing alarm (ringing-screen Stop/Snooze, alarm-list disable/toggle, edit/save, delete) MUST deterministically silence a live ring or surface a visible, retryable failure. Dismiss-by-design (the ringing screen always closes) is preserved but MUST NOT mask a failed native stop. ## Requirements ### Requirement: Mutation-While-Ringing Stop Guard `EstadoAlarmas` MUST route any mutation targeting the currently-ringing alarm — disable/toggle (`cambiarActiva`), edit/save (`guardarAlarma`), delete (`eliminarAlarma`) — through one centralized stop-first guard, not per-call-site logic. #### Scenario: Toggling the ringing alarm off stops audio **Testability**: [dart-testable] - GIVEN alarm X is ringing and active - WHEN `cambiarActiva(X, false)` is called - THEN `detenerSonidoNativo(X)` runs before/alongside `programar`/`cancelar` #### Scenario: Editing/saving the ringing alarm stops audio **Testability**: [dart-testable] - GIVEN alarm X is ringing - WHEN `guardarAlarma` is called with an edited config for id X - THEN the stop guard fires before the save persists #### Scenario: Deleting the ringing alarm stops audio (regression lock) **Testability**: [dart-testable] - GIVEN alarm X is ringing - WHEN `eliminarAlarma(X)` is called - THEN `detenerSonidoNativo(X)` runs before `cancelar` (locks in existing correct behavior) #### Scenario: Mutating a non-ringing alarm does not trigger the guard **Testability**: [dart-testable] - GIVEN alarm Y is ringing, alarm Z is not - WHEN `cambiarActiva(Z, ...)` or `guardarAlarma(Z)` is called - THEN no stop call targets Y ### Requirement: Stop/Snooze Result Verification Stop (`detenerSonidoNativo`) and snooze channel calls MUST return a success/failure/unconfirmed result instead of only throwing or being swallowed. `EstadoAlarmas` MUST record this outcome per alarm. #### Scenario: Native stop confirms success **Testability**: [dart-testable] - GIVEN the stop channel call resolves with `confirmed=true` - WHEN `_detener()` completes - THEN `EstadoAlarmas` records a confirmed-stop state for that alarm #### Scenario: Native stop reports unconfirmed/failure **Testability**: [dart-testable] - GIVEN the fake channel is set to fail or return `confirmed=false` - WHEN `_detener()`/`_posponer()` runs - THEN `EstadoAlarmas` records a failed/unconfirmed state, never a confirmed one ### Requirement: Retryable Force-Stop Affordance When a stop/snooze attempt is not confirmed, the app MUST present a persistent, retryable "Force stop" affordance until a confirmed stop is recorded or the alarm is externally cleared. Dismiss-by-design (screen closing) stays independent of this affordance. #### Scenario: Failed stop surfaces Force stop **Testability**: [dart-testable] - GIVEN `_detener()` receives an unconfirmed/failure result - WHEN the ringing screen finishes its dismiss-by-design close - THEN a persistent retryable Force-stop affordance appears #### Scenario: Retrying force-stop attempts stop again **Testability**: [dart-testable] - GIVEN a Force-stop affordance is visible after a failed attempt - WHEN the user retries - THEN `detenerSonidoNativo` is invoked again; success clears the affordance, failure keeps it visible #### Scenario: Confirmed stop shows no failure UI **Testability**: [dart-testable] - GIVEN a confirmed-stop result - WHEN the screen dismisses - THEN no Force-stop affordance appears ### Requirement: Notification Stop/Snooze Stays Native-Only Notification Stop/Snooze actions use `PendingIntent.getService` directly (native-only, Flutter-independent) and remain outside this Dart guard's scope; only Dart-initiated mutations and in-app screens are covered here. #### Scenario: Notification action bypasses the Dart guard by design **Testability**: [kotlin-static] - GIVEN a fire notification with Stop/Snooze actions - WHEN the user taps either action - THEN it invokes the native service directly, never touching `EstadoAlarmas`' guard