docs(openspec): add SDD artifact trail for recent alarm and EQ changes
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s

Persist the exploration, proposal, spec, design, tasks, and
verify/archive reports produced during the multi-device EQ,
alarm-countdown, and notification-visual-polish SDD cycles.
This commit is contained in:
2026-07-04 12:42:11 +02:00
parent e5b6d8acb3
commit bccc5c48b8
64 changed files with 6020 additions and 0 deletions
@@ -0,0 +1,79 @@
# Tasks: Alarm Live Countdown
## Review Workload Forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | ~175200 (additions + deletions) |
| 400-line budget risk | Low |
| Chained PRs recommended | No |
| Suggested split | Single PR |
| Delivery strategy | ask-on-risk |
| Chain strategy | pending |
Decision needed before apply: No
Chained PRs recommended: No
Chain strategy: pending
400-line budget risk: Low
### Suggested Work Units
| Unit | Goal | Likely PR | Notes |
|------|------|-----------|-------|
| 1 | All changes (l10n + MethodChannel + Kotlin receiver + snooze guard) | PR 1 | Two logically isolated features; same PR acceptable given low line count |
---
## Phase 1: Foundation — ARB Keys and Contracts
- [ ] 1.1 Add `preNoticeCountdown` key (value: `"Starts in {minutes} min"`) to `lib/l10n/app_en.arb` as the canonical English source key.
- [ ] 1.2 **[RED]** Write a Dart unit test asserting `AppLocalizations.of(ctx).preNoticeCountdown(minutes: 30)` returns the expected English string; confirm it fails (key missing).
- [ ] 1.3 Add `preNoticeCountdown` key to all 12 remaining `lib/l10n/app_*.arb` files (es, fr, de, pt, it, ja, ko, zh, ar, ru, nl, pl) using locale-appropriate translation following the `durationMinutesOnly` sentence pattern. Each value must contain the `{minutes}` placeholder.
- [ ] 1.4 **[GREEN]** Run `flutter gen-l10n`; confirm test 1.2 now passes.
- [ ] 1.5 **[REFACTOR]** Verify ARB placeholder annotations (`"@preNoticeCountdown"` with `placeholders.minutes.type: "int"`) are present in `app_en.arb` per ARB spec convention.
## Phase 2: Flutter Side — MethodChannel Argument
- [ ] 2.1 **[RED]** Write a unit/widget test for `ServicioAlarmasAndroid.programar()` asserting the MethodChannel call includes a `preNoticeTemplate` key whose value contains `{minutes}`.
- [ ] 2.2 In `lib/servicios/servicio_alarmas_android.dart`, compute `preNoticeTemplate` from `l10n.preNoticeCountdown(minutes: '{minutes}')` (passing the literal placeholder string) and add it to the `scheduleAlarm` MethodChannel arguments map.
- [ ] 2.3 **[GREEN]** Confirm test 2.1 passes.
- [ ] 2.4 **[REFACTOR]** Ensure the `preNoticeTemplate` argument is added adjacent to the existing `title` localization call; no dead code left over.
## Phase 3: Kotlin — AlarmScheduler and NativeAlarmSpec
- [ ] 3.1 **[RED]** Write a Kotlin unit test asserting `AlarmScheduler.scheduleAlarm()` with a `preNoticeTemplate` string stores it in the resulting `NativeAlarmSpec` and embeds it into the PendingIntent extras under key `"preNoticeTemplate"`.
- [ ] 3.2 Add `preNoticeTemplate: String? = null` field to `NativeAlarmSpec` data class in `android/.../AlarmScheduler.kt` (nullable for backward compat; schema stays v3).
- [ ] 3.3 Update `MainActivity.kt` to read `preNoticeTemplate` from the MethodChannel arguments map and pass it to `AlarmScheduler.scheduleAlarm()`.
- [ ] 3.4 In `AlarmScheduler.scheduleAlarm()`, accept `preNoticeTemplate` param and embed it into the pre-notice `Intent` extras as `EXTRA_PRE_NOTICE_TEMPLATE = "preNoticeTemplate"`.
- [ ] 3.5 **[GREEN]** Confirm test 3.1 passes.
- [ ] 3.6 **[REFACTOR]** Confirm `EXTRA_PRE_NOTICE_TEMPLATE` constant is declared once (in `AlarmScheduler` or a shared constants file) and not duplicated between scheduler and receiver.
## Phase 4: Kotlin — BroadcastReceiver Countdown Computation
- [ ] 4.1 **[RED]** Write a Kotlin unit test for `PluriWaveAlarmReceiver.showPreNoticeNotification()` covering: (a) normal case returns template with computed minutes, (b) clamped to 1 when `triggerAtMillis <= currentTimeMillis`, (c) null template falls back to English default text.
- [ ] 4.2 In `android/.../PluriWaveAlarmReceiver.kt`, read `EXTRA_PRE_NOTICE_TEMPLATE` from the intent extras. Compute `remaining = max(1L, (triggerAtMillis - System.currentTimeMillis()) / 60_000)`. Replace `{minutes}` placeholder in the template and pass result to `NotificationCompat.Builder.setContentText()`.
- [ ] 4.3 Add null-safety fallback: if `preNoticeTemplate` is null or blank, use `"Starts in $remaining min"` as the default English string.
- [ ] 4.4 Remove the existing hardcoded Spanish `"Empieza en 30 minutos"` string from the receiver.
- [ ] 4.5 **[GREEN]** Confirm all test cases in 4.1 pass.
- [ ] 4.6 **[REFACTOR]** Extract the minutes-computation expression into a private function `computeRemainingMinutes(triggerAtMillis: Long): Long` for readability and testability.
## Phase 5: Dart — Snooze Dismiss Guard
- [ ] 5.1 **[RED]** Write a widget test for `PantallaAlarmaSonando._posponer()` in `lib/pantallas/pantalla_alarma_sonando.dart` asserting: (a) when `Navigator.canPop()` is true, `Navigator.pop()` is called and `SystemNavigator.pop()` is NOT called; (b) when `canPop()` is false, `SystemNavigator.pop()` is called instead.
- [ ] 5.2 In `lib/pantallas/pantalla_alarma_sonando.dart`, replace the bare `Navigator.of(context).pop()` in `_posponer()` with `if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } else { SystemNavigator.pop(); }`. Apply the same guard to `_detener()` if it also calls `pop()` without a guard.
- [ ] 5.3 Verify that `_liberarAudioLocal()`, `radio.audio.pausar()`, and `alarmas.posponerAlarma()` all execute BEFORE the navigation action (ordering unchanged).
- [ ] 5.4 **[GREEN]** Confirm tests from 5.1 pass.
- [ ] 5.5 **[REFACTOR]** Extract the guard into a private helper `_dismissScreen()` called from both `_posponer()` and `_detener()` to eliminate duplication.
## Phase 6: Integration Verification
- [ ] 6.1 Extend existing `scheduleAlarm` integration test (MethodChannel round-trip) to assert `preNoticeTemplate` survives the Flutter → Kotlin boundary correctly.
- [ ] 6.2 Confirm `notificationIdForAlarm(alarmId)` is used in `showPreNoticeNotification()` (notification ID stability — re-posting same ID updates in place). No change needed if already correct; add assertion to test if not.
- [ ] 6.3 Run full test suite (`flutter test` + Kotlin `./gradlew test`); all pre-existing tests must remain green.
- [ ] 6.4 Manual smoke test: schedule an alarm ~2 min out; verify pre-notice notification shows correct locale and computed minutes; tap Snooze from a fresh app launch; confirm screen dismisses cleanly.
## Phase 7: Cleanup
- [ ] 7.1 Confirm no hardcoded Spanish strings remain in `PluriWaveAlarmReceiver.kt` notification path (`rg "Empieza" android/`).
- [ ] 7.2 Confirm `preNoticeTemplate` is the only new MethodChannel key added; no dead args left in `scheduleAlarm` map.
- [ ] 7.3 Update inline code comments in `AlarmScheduler.kt` and `PluriWaveAlarmReceiver.kt` to document the template-at-schedule-time, replace-at-fire-time pattern.