WU15 shipped PantallaGrabaciones (the recordings library: storage bar, recording rows, the "..." Rename/Share/Delete menu) fully tested but reachable from nowhere in the app - a gap flagged in WU15's own apply-progress notes, not fixed there since it needed a design decision rather than a guess. Coordinator ruling applied: the approved mockup's "Ajustes > Grabaciones" screen depicts the library, not the folder/size settings form. So the GRABACIONES Y MUSICA group's "Grabaciones" row in pantalla_ajustes.dart now opens PantallaGrabaciones instead of PantallaAjustesGrabaciones. The settings form is not dropped - it stays reachable, now from within the library via a settings icon in its PluriPushScaffold actions, matching the existing pantalla_ajustes_timer_sueno.dart "Add" action precedent for a real capability living in the header. One new ARB key (en/es only, per precedent): recordingsLibrarySettingsTooltip. Tests: 604 -> 605 (one scenario re-targeted in pantalla_ajustes_test.dart, one new scenario in pantalla_grabaciones_test.dart, which needed the same ListTile-ink-assertion suppression helper WU3a/WU3b established since it now pushes a ListTile-bearing settings screen). flutter analyze unchanged at 1 pre-existing info. Recorded in tasks.md as WU15b - not part of the original 18-unit plan, added here to close the gap WU15 flagged.
769 lines
63 KiB
Markdown
769 lines
63 KiB
Markdown
# Tasks: Functional Redesign (`rediseno-funcional`)
|
||
|
||
> Reads (authoritative): `proposal.md`, `design.md`, `specs/*/spec.md` in this folder.
|
||
> Binding corrections from the design phase are applied throughout (see table below) — they override the proposal
|
||
> where they conflict with it.
|
||
> **Delivery model**: 18 work-unit **commits** on one branch, `feat/rediseno-funcional` — NOT chained PRs (Engram
|
||
> `sdd/rediseno-funcional/delivery-strategy`, id 2504, a user decision that supersedes the proposal's
|
||
> feature-branch-chain recommendation). Conventional commits only. Never `Co-Authored-By` or AI attribution. Never
|
||
> push to `main`. The user opens one PR in the Gitea web UI at the end; this agent never opens PRs.
|
||
> **This artifact intentionally exceeds the default ~530-word task-budget**, for the same reason `proposal.md` and
|
||
> `design.md` do: 18 work units each need a commit message, a RED/GREEN/REFACTOR breakdown, named test files, spec
|
||
> traceability, and a verification command. Collapsing that to fit a budget would hide exactly the detail a reviewer
|
||
> needs. Tables and checklists are used throughout to keep it scannable despite the length.
|
||
> **WU12 does not exist** — the native EQ band-count spike was resolved before planning closed (Engram id 2498, "keep
|
||
> 5 bands"). Numbering skips 11→13 on purpose; this is not a gap.
|
||
> **WU15b was added mid-apply, not planned upfront** — WU15 shipped `PantallaGrabaciones` (the recordings library)
|
||
> fully tested but reachable from nowhere in the app. WU15b (below, after WU15's section) is the coordinator-ruled fix
|
||
> that wires it into Settings navigation. It is small and does not change the 18-commit delivery model's shape.
|
||
> Strict TDD is ON. Runner: `flutter test`. `flutter analyze` and a **scoped** `dart format` gate every commit.
|
||
> **`flutter build` is never run.**
|
||
>
|
||
> ## ⚠️ NEVER run bare `dart format .`
|
||
>
|
||
> Verified on this machine: `dart format .` reformats **27 pre-existing files unrelated to any work unit**
|
||
> (`Formatted 141 files (27 changed)`). Cause is formatter version skew — this machine runs Dart SDK 3.12.0 while
|
||
> the repo's committed formatting came from another version. Running it bare would silently break four hard success
|
||
> criteria at once: the empty-`git diff` guards on `navegacion_auto.dart`, `servicio_ecualizador.dart` and
|
||
> `servicio_audio.dart`, plus "`pantalla_alarma_sonando_dismiss_guard_test.dart` passes unmodified".
|
||
>
|
||
> **Always scope the formatter to the files this work unit actually touched.** The verify commands below use:
|
||
>
|
||
> ```
|
||
> dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')
|
||
> ```
|
||
>
|
||
> Full detail: Engram `reference/dart-format-scope-hazard` (id 2511).
|
||
|
||
## Review Workload Forecast (per commit, not per PR)
|
||
|
||
| WU | Commit | Depends on | Est. changed lines | Budget risk | Exception |
|
||
|----|--------|-----------|---------------------|-------------|-----------|
|
||
| 1 | `feat(tokens): add design tokens, type scale, push scaffold, and root nav state` | — | 220-330 | Low | No |
|
||
| 2 | `test(auto): confirm Android Auto tree matches the redesign, no code changes` | — | 0 | None | No |
|
||
| 3a | `refactor(ajustes): split Settings AUDIO/EMISORAS into pushed detail screens` | 1 | ~~800-1000~~ → **REALIZED: 4,374** (2,760+ / 1,614-, 35 files) | High | **Yes — move-only diff** |
|
||
| 3b | `refactor(ajustes): split remaining Settings sections into pushed screens` | 3a | ~~2,500-3,200~~\* → **REALIZED: 2,190** (1,485+ / 705-, 28 files) | Medium-High | **Yes — move-only diff** |
|
||
| 4 | `feat(favoritos): replace stacked group panels with chip-filtered reorderable list` | 1 | 300-400 | Medium | Monitor |
|
||
| 5 | `feat(escuchar): replace discovery browser with embedded player and favorites grid` | 1, 4 | 350-450 | Medium | Monitor |
|
||
| 6 | `feat(buscar): add discovery landing state, filter pills, counter, and sort` | 5 | 350-500 | Medium-High | Monitor |
|
||
| 7 | `feat(paises): add country browser and extract shared radio transport` | 6 | 300-400 | Medium (test-heavy) | Monitor |
|
||
| 8 | `feat(alarmas): simplify alarm cards and add vacation summary row` | 1 | 400-550 | Medium-High | Monitor† |
|
||
| 9 | `feat(vacaciones): add vacation range manager screen` | 8 | 350-450 | Medium | Monitor |
|
||
| 10 | `feat(alarmas): rewrite alarm editor with inline time widget` | 8 | 500-650 | High | **Yes — indivisible new widget**‡ |
|
||
| 11 | `feat(alarma-sonando): restyle ringing screen, drop live countdown label` | 1 | 200-300 | Low (safety-critical review attention: High) | No |
|
||
| 13 | `feat(eq): restyle equalizer screen and add custom presets` | 3a | 400-550 | Medium-High | Monitor |
|
||
| 14 | `feat(reproductor): restructure full player with tool-tray and EQ sheet` | 13 | 450-600 | Medium-High | Monitor |
|
||
| 15 | `feat(grabaciones): add recordings library screen` | 3b | ~~300-400~~ → **REALIZED: 1,767** (1,767+ / 0-, 22 files) | Medium | Monitor§ |
|
||
| 15b | `fix(grabaciones): wire the recordings library into Settings navigation` | 15 | 60-100 | Low | No |
|
||
| 16 | `feat(connectivity): restyle offline and reconnect banners` | 1 | 150-250 | Low | No |
|
||
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | 150-200 | Low | No |
|
||
| 18 | `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales` | all | ~0 eng. / 400-600 data | Medium (data volume, low logic risk) | No |
|
||
|
||
\* **Re-derived after WU3a landed.** The original 500-700 figure was scaled pro-rata from WU3a's *estimate*, which
|
||
turned out to be wrong by roughly 4x — so the old WU3b number inherited the same error and has been replaced.
|
||
WU3a's realized diff was **4,374 changed lines across 35 files** against an 800-1000 estimate. The gap was not
|
||
scope creep; the estimate simply never counted (a) the 7 matching new test files strict TDD requires, or (b) the
|
||
13 regenerated `lib/l10n/gen/app_localizations*.dart` files that any ARB edit forces. `lib/` alone, excluding
|
||
generated l10n, was 1,474+ / 1,214-, and the root shed 1,320 lines — so the "move-only" characterisation held; only
|
||
the arithmetic was wrong.
|
||
WU3b moves 5 sections rather than 7 and needs no new shared widget (`fila_ajuste.dart` already exists), hence
|
||
~2,500-3,200. **Estimating lesson for every remaining work unit: a strict-TDD commit carries its test files, and any
|
||
ARB touch drags 13 generated files with it. Estimates that count only `lib/` production code will read ~3-4x low.**
|
||
|
||
**Root line count, for reference**: `pantalla_ajustes.dart` went 1,897 → 788 in WU3a → **198** in WU3b. The "under
|
||
400 lines" success criterion was the WU3a+WU3b **combined** end state — WU3a owned only 7 of the 12 detail screens.
|
||
WU3b converted the remaining 5 (Grabaciones, Música local, Idioma, Backup, Info) and the root is now exactly 4
|
||
`GrupoAjustes` cards, well under the 400-line target.
|
||
† Record `size:exception` at apply time only if the realized diff exceeds ~500 lines; justification: "largest single
|
||
alarm-card + hero + vacation-summary restyle, not divisible without breaking the one-commit-per-work-unit rule."
|
||
‡ The proposal already isolates WU10 as "its own PR, never bundled" — splitting further would leave an unintegrated
|
||
commit (a widget with no consumer, or a sheet rewrite with no new editor).
|
||
§ **Re-derived after WU15 landed.** Same estimating lesson as WU3a/WU3b, at brand-new-screen scale: the 300-400
|
||
figure covered only the production screen, not (a) its matching ~470-line strict-TDD test file, (b) the 13
|
||
regenerated `lib/l10n/gen/*.dart` files (12 new ARB keys this time, since a genuinely new screen needs new copy,
|
||
unlike WU3a/WU3b's move-only reuse), or (c) the new `servicio_grabacion_radio.dart` additions and their own test
|
||
group. Realized 1,767 changed lines / 22 files, all additions (no deletions — nothing pre-existing was touched
|
||
beyond the 3 new `ServicioGrabacionRadio`/`EstadoGrabacion` methods). Not recorded as `size:exception` since the
|
||
commit is still a single, cleanly-scoped deliverable (one new screen, its one dependency, no split candidate).
|
||
|
||
```text
|
||
Decision needed before apply: No
|
||
Chained PRs recommended: No
|
||
Chain strategy: size-exception
|
||
400-line budget risk: High
|
||
```
|
||
|
||
**Why these values, not a fresh ask.** The delivery model was already decided by the user (Engram 2504), which
|
||
supersedes the proposal's own `feature-branch-chain` recommendation once the host was confirmed as Gitea (no `gh`,
|
||
agent cannot open PRs). That decision is functionally equivalent to an already-accepted `size:exception` at the
|
||
aggregate level — the one eventual PR will be reviewed commit-by-commit, which is why this forecast is expressed per
|
||
commit. Three commits (WU3a, WU3b, WU10) get an explicit `size:exception` because they cannot be reduced under 400
|
||
lines without violating the one-commit-per-work-unit rule; the rest are within or near budget individually.
|
||
|
||
**Parallelization note.** Delivery is sequential commits on one branch, so WUs land in the order below regardless of
|
||
true independence. For planning only: WU2 has no dependencies and may be pulled to any point (already noted in the
|
||
proposal). WU11, WU16, and WU17 each depend only on WU1 and are mutually independent of the WU3a-WU10 and WU13-15
|
||
chains — they could be reordered among themselves without breaking anything, but the sequence below is kept to match
|
||
the proposal's narrative and dependency table.
|
||
|
||
## Corrections From the Design Phase (applied throughout)
|
||
|
||
| # | Correction | Where applied |
|
||
|---|------------|---------------|
|
||
| 1 | `EstadoNavegacionRaiz` moves from WU5 into WU1 | WU1 tasks 1.6-1.7; WU5 task 5.3 only *consumes* `irA(...)` |
|
||
| 2 | WU3a/WU3b line estimates were structurally low (verbatim move = add+delete in `git diff`) | Forecast table above; `size:exception` recorded for both, justification "move-only diff" |
|
||
| 3 | Settings detail-screen count is **12**, not "~8" | WU3a (7 screens) + WU3b (5 screens) = 12, listed by name in tasks 3a.4-3a.5, 3b.2-3b.3 |
|
||
| 4a | Custom EQ preset persistence must live in a **new** file, never in `servicio_ecualizador.dart` | WU13 task 13.6, hazard box restated inline |
|
||
| 4b | Mini player must hide **visually** (`visible: false`), never leave the tree, to preserve the S3-R3 `didChangeDependencies` side effect | WU5 task 5.2, 5.6 |
|
||
| 5 | `MiniReproductor.altura` must be derived from laid-out height at apply time, not guessed | WU5 task 5.6 |
|
||
|
||
## Non-Negotiable Guards (master checklist — verify before the branch is considered done)
|
||
|
||
- [ ] `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart` passes **unmodified** (touched by WU9, WU11 — if
|
||
a restyle appears to require editing it, STOP and escalate; do not edit the test).
|
||
- [ ] `git diff` is **empty** for `lib/servicios/navegacion_auto.dart` (WU2), `lib/servicios/servicio_ecualizador.dart`
|
||
(WU13), and the band-application block at `lib/servicios/servicio_audio.dart:749-762` (WU13).
|
||
- [ ] The equalizer renders exactly **5** sliders (WU13, task 13.1).
|
||
- [ ] No shipped string contains "PRO", a trial duration, or a price (WU17 task 17.2/17.6; WU18 task 18.6 final
|
||
whole-app grep).
|
||
- [ ] All 13 ARB files keep identical key sets (WU18 tasks 18.1, 18.5 — final parity check; WU1 only touches values
|
||
in 2 of the 13 files, so it cannot break parity on its own).
|
||
|
||
---
|
||
|
||
## WU1 — Tokens, push scaffold, Escuchar rename, root nav provider
|
||
|
||
**Commit**: `feat(tokens): add design tokens, type scale, push scaffold, and root nav state`
|
||
**Depends on**: — (first commit)
|
||
**Spec refs**: `app-navigation-shell` — Five-Tab Root Navigation, Push-Chrome on Second-Level Screens, Root-to-Root
|
||
Switching Without Push (provider only; consumption is WU5), Escuchar Tab Rename Preserves the ARB Key
|
||
**Verify**: `flutter test test/tema/ test/widgets/pluri_push_scaffold_test.dart test/estado/estado_navegacion_test.dart test/widget_test.dart && flutter analyze && dart format --set-exit-if-changed .`
|
||
**New tests**: `test/tema/pluriwave_typography_test.dart`, `test/widgets/pluri_push_scaffold_test.dart`, `test/estado/estado_navegacion_test.dart`
|
||
**Modified tests**: `test/tema/pluriwave_tokens_test.dart` (or equivalent), `test/widget_test.dart`
|
||
|
||
- [x] 1.1 RED — write `pluriwave_typography_test.dart` asserting the 6 named styles (`heroTime`, `sectionTitle`,
|
||
`screenTitle`, `cardTitle`, `bodyStrong`, `eyebrowLabel`) exist with their documented size/weight/letter-spacing
|
||
and that `PluriWaveTheme.dark()` registers the extension.
|
||
- [x] 1.2 RED — extend the tokens test asserting `PluriWaveTokens.listSurface`, `.liveGreen`, `.offlineAccent` exist
|
||
and participate in `lerp`.
|
||
- [x] 1.3 GREEN — add the 3 colour fields to `lib/tema/pluriwave_tokens.dart` (constructor, `copyWith`, `lerp`);
|
||
de-literalise `pluriwave_theme.dart:12,15`; create `lib/tema/pluriwave_typography.dart` built inside
|
||
`PluriWaveTheme.dark()`.
|
||
- [x] 1.4 RED — write `pluri_push_scaffold_test.dart`: each of the 5 root screens mounted bare builds zero
|
||
`Scaffold`; a `PluriPushScaffold` renders exactly one 56px `AppBar` + back affordance and exposes no
|
||
`bottomNavigationBar` parameter on its constructor.
|
||
- [x] 1.5 GREEN — implement `lib/widgets/pluri_push_scaffold.dart` per the ADR-2 API (`title`, `body`,
|
||
`titleOverride`, `leadingIcon`, `onBack`, `actions`, `bottom`, `floatingActionButton`, static `push`).
|
||
- [x] 1.6 RED — write `estado_navegacion_test.dart`: `irA()` transitions `RaizPluriWave`, no-ops and does not notify
|
||
on same-root, `indice` matches enum declaration order.
|
||
- [x] 1.7 GREEN — implement `lib/estado/estado_navegacion.dart` (`RaizPluriWave` enum, `EstadoNavegacionRaiz`);
|
||
register it in `app.dart`'s `MultiProvider`; convert the 3 `setState(() => _indice = ...)` alarm-deep-link
|
||
sites (`app.dart:310,333,350`) to `irA(...)`.
|
||
- [x] 1.8 RED — update `test/widget_test.dart` to expect "Escuchar" (not "Inicio") as the first tab's label, with
|
||
`PluriIconGlyph.home` unchanged as its icon.
|
||
- [x] 1.9 GREEN — change `app_en.arb` line 4 `navHome` value to `"Listen"` and `app_es.arb` line 4 to `"Escuchar"`;
|
||
retitle the first tab in `lib/app.dart`. Leave the other 11 ARB files untouched (WU18's job).
|
||
- [x] 1.10 REFACTOR — sweep `lib/tema/`, `app.dart` for now-dead literals; confirm zero consumer screens were
|
||
touched (WU1 converts zero screens to `PluriPushScaffold` — that is what keeps this commit at its line budget).
|
||
- [x] 1.11 Verify — run the command above; confirm `git diff --stat` touches only tokens/theme/typography files, the
|
||
new push-scaffold and nav-state files, `app.dart`, `app_en.arb`, `app_es.arb`, and their tests.
|
||
|
||
## WU2 — Android Auto verification (zero code)
|
||
|
||
**Commit**: `test(auto): confirm Android Auto tree matches the redesign, no code changes`
|
||
**Depends on**: —
|
||
**Spec refs**: `android-auto-media` — no requirement delta; this WU records the confirmation
|
||
**Verify**: `flutter test test/servicios/navegacion_auto_test.dart && git diff --stat lib/servicios/navegacion_auto.dart`
|
||
**Tests**: none created or modified — existing suite re-run only
|
||
|
||
- [x] 2.1 Run `flutter test test/servicios/navegacion_auto_test.dart`; confirm every case passes unmodified.
|
||
- [x] 2.2 Confirm `git diff lib/servicios/navegacion_auto.dart` is empty — no code change in this commit.
|
||
- [x] 2.3 Record in the commit body: A1-A5 closed, zero code changes, `MediaBrowserService` model confirmed
|
||
non-custom-renderable (the waveform in A4 is not implementable on this platform).
|
||
- [x] 2.4 No REFACTOR step — this WU is verification-only by design.
|
||
|
||
## WU3a — Settings shell + AUDIO/EMISORAS groups
|
||
|
||
**Commit**: `refactor(ajustes): split Settings AUDIO/EMISORAS into pushed detail screens`
|
||
**Depends on**: WU1
|
||
**Spec refs**: `app-navigation-shell` — Push-Chrome on Second-Level Screens (Settings detail screen scenario)
|
||
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/ajustes/ && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: one per new screen under `test/pantallas/ajustes/` (7 files, AUDIO + EMISORAS)
|
||
**Modified tests**: `test/pantallas/pantalla_ajustes_test.dart` (near-total rewrite)
|
||
|
||
- [x] 3a.1 RED — rewrite `pantalla_ajustes_test.dart`: root renders exactly 4 grouped nav lists, zero inline
|
||
controls, file stays under 400 lines. **Corrected at apply time**: WU3a converts only AUDIO + EMISORAS (2
|
||
groups, 7 rows) per its own tasks 3a.4-3a.6 and the explicit "WU3a delivers 7 of 12 screens" scope — the
|
||
remaining 5 sections (GRABACIONES Y MÚSICA, APLICACIÓN) stay inline pending WU3b. "4 groups / <400 lines" is
|
||
the combined WU3a+WU3b end state (matches design ADR-3's own aggregate blast-radius note), not a WU3a-only
|
||
claim; the rewritten test asserts the true WU3a-scoped state instead (2 groups zero-inline, 5 sections still
|
||
inline and reachable).
|
||
- [x] 3a.2 RED — write one test per AUDIO/EMISORAS detail screen (Ecualizador, Salida de audio, Temporizador de
|
||
sueño, Grupos de favoritos, Emisora preferida, Emisoras personalizadas, Orden de listas) asserting it renders
|
||
inside a `PluriPushScaffold` and its moved controls still respond.
|
||
- [x] 3a.3 GREEN — create `lib/pantallas/ajustes/widgets/fila_ajuste.dart` (`FilaAjuste` / `GrupoAjustes` primitives).
|
||
- [x] 3a.4 GREEN — cut `_SeccionEcualizador` (694-782), `_SeccionEcualizadorAvanzado` + `_FilaDispositivo` +
|
||
`_DialogoEdicionDispositivo` (782-1013), `_SeccionTimerSueno` + `_FormularioDuracionTimer` (399-604) into
|
||
`pantalla_ajustes_ecualizador.dart`, `pantalla_ajustes_salida_audio.dart`, `pantalla_ajustes_timer_sueno.dart`.
|
||
Verbatim-move rule: delete only the panel header row; body is untouched. The two sections whose header row
|
||
carried a real action (not just a status readout) — Temporizador de sueño's "Add" and, in 3a.5, Grupos de
|
||
favoritos' "Add list" / Emisoras personalizadas' "Add" — keep that action in the body, right-aligned, rather
|
||
than dropping it; `_SeccionEcualizador`'s header also carried a status `Chip`, dropped since the very next row
|
||
(the enable switch) already shows the same state.
|
||
- [x] 3a.5 GREEN — cut `_SeccionGruposFavoritos` (1187), `_SeccionEmisoraPreferida` (1342), `_SeccionEmisoras` +
|
||
`_FormularioEmisora` (1459-1669), `_SeccionOrdenListas` (1133) into `pantalla_ajustes_grupos_favoritos.dart`,
|
||
`pantalla_ajustes_emisora_preferida.dart`, `pantalla_ajustes_emisoras_personalizadas.dart`,
|
||
`pantalla_ajustes_orden_listas.dart`.
|
||
- [x] 3a.6 GREEN — in the root, replace the 7 moved sections with `FilaAjuste` rows under two `GrupoAjustes` cards
|
||
(AUDIO, EMISORAS); each row pushes its screen via `PluriPushScaffold.push`. Two new ARB keys added (en/es only,
|
||
matching the WU1 precedent) for the group eyebrow labels: `settingsGroupAudioTitle` ("AUDIO"/"AUDIO"),
|
||
`settingsGroupStationsTitle` ("STATIONS"/"EMISORAS") — all 7 detail-screen titles reuse existing ARB keys
|
||
(the same string the old in-body header already showed), so no other new UI copy was introduced.
|
||
- [x] 3a.7 REFACTOR — confirm zero business-logic edits inside any `_CuerpoX` (diff reads "header removed, body
|
||
identical"); confirm `app.dart`'s import of `pantalla_ajustes.dart` is unchanged.
|
||
- [x] 3a.8 Verify — `git diff` touches only screen files (no service/state file) — confirmed. Root file line count:
|
||
**788 lines, not yet <400** — accurate for WU3a alone (5 sections remain inline; see 3a.1's note). WU3b's own
|
||
3b.5 REFACTOR is where the root actually crosses under 400.
|
||
|
||
**`size:exception` — "move-only diff".** ~800-1000 changed lines (design-verified figure), ~85% relocated not
|
||
modified. Do not attempt to slice under 450.
|
||
|
||
## WU3b — Settings GRABACIONES Y MÚSICA / APLICACIÓN groups
|
||
|
||
**Commit**: `refactor(ajustes): split remaining Settings sections into pushed screens`
|
||
**Depends on**: WU3a
|
||
**Spec refs**: `app-navigation-shell` — Push-Chrome on Second-Level Screens
|
||
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/ajustes/ && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: one per new screen under `test/pantallas/ajustes/` (5 files: Grabaciones, Música local, Idioma,
|
||
Backup, Info)
|
||
**Modified tests**: `test/pantallas/pantalla_ajustes_test.dart` (extended)
|
||
|
||
- [x] 3b.1 RED — extend detail-screen tests for the remaining 5 sections, asserting `PluriPushScaffold` usage and
|
||
preserved controls. **Applied against the CURRENT 788-line file (re-read fresh at apply time, not the stale
|
||
92-1896 line references below, which were computed against the pre-WU3a 1,897-line file).** Root test file:
|
||
removed the now-false "Sections pending WU3b remain inline" and "Phase 7" groups, added a `WU3b — GRABACIONES Y
|
||
MÚSICA and APLICACIÓN groups` group (5 scenarios: 2-row/3-row zero-inline-control checks, 2 push-navigation
|
||
checks, one "root is exactly 4 GrupoAjustes cards" check). The 2 relocated Phase-7 friendly-folder-name
|
||
scenarios moved verbatim into the new `ajustes/pantalla_ajustes_musica_local_test.dart`, targeting the isolated
|
||
pushed screen directly instead of scrolling to find it inside the whole root.
|
||
- [x] 3b.2 GREEN — cut `_SeccionGrabaciones`, `_SeccionMusicaLocal` (Stateful) into `pantalla_ajustes_grabaciones.dart`,
|
||
`pantalla_ajustes_musica_local.dart` — verbatim, header row removed per the established rule.
|
||
- [x] 3b.3 GREEN — cut `_SeccionIdioma` + `_IdiomaDisponible`, `_SeccionBackup`, `_SeccionInfo` into
|
||
`pantalla_ajustes_idioma.dart`, `pantalla_ajustes_backup.dart`, `pantalla_ajustes_info.dart`. **Correction
|
||
found at apply time**: unlike the other 4 sections, `_SeccionInfo` never had its own header icon+title row —
|
||
its first tile (app name + version) already served that role — so there was no header to strip for that one
|
||
screen; its body moved in full, unchanged. This is also why `infoSectionTitle` is a genuinely new ARB key (no
|
||
existing in-body header string covered a bare "Info" label), unlike the other 4 screens which all reuse
|
||
pre-existing strings.
|
||
- [x] 3b.4 GREEN — in the root, replaced the remaining 5 sections with `FilaAjuste` rows under two more
|
||
`GrupoAjustes` cards: RECORDINGS & MUSIC (Recordings, Local music) and APPLICATION (Language, Backup, Info).
|
||
Confirmed sleep timer (WU3a) and backup/restore (this WU) rows are both present — corollary ruling, nothing
|
||
dropped. **3 new ARB keys** (en/es only, matching WU1/WU3a's precedent of leaving the other 11 locales for
|
||
WU18): `settingsGroupRecordingsTitle` ("RECORDINGS & MUSIC"/"GRABACIONES Y MÚSICA"),
|
||
`settingsGroupApplicationTitle` ("APPLICATION"/"APLICACIÓN") for the two new group eyebrow labels, and
|
||
`infoSectionTitle` ("Info"/"Información") for the one row with no pre-existing header string (see 3b.3). All
|
||
other row titles reuse existing ARB keys.
|
||
- [x] 3b.5 REFACTOR — confirmed `pantalla_ajustes.dart` now contains exactly 4 `GrupoAjustes` cards and is **198
|
||
lines** (well under the 400-line target); deleted all 5 now-unused private section widgets plus every import
|
||
that was only needed by their bodies (`dart:async`, `dart:io`, `file_picker`, `package_info_plus`,
|
||
`path_provider`, `provider`, `share_plus`, `estado_grabacion.dart`, `estado_idioma.dart`, `estado_radio.dart`,
|
||
`musica_local_auto.dart`, `pluri_glass_surface.dart`, `pluri_onboarding_dialog.dart` — none of these are read
|
||
by the root anymore since it is pure navigation chrome).
|
||
- [x] 3b.6 Verify — scoped suite (`pantalla_ajustes_test.dart` + `ajustes/`) green: 45/45. All 12 detail screens
|
||
present and reachable. `git diff` touches only screen/test/ARB(+generated l10n) files — no service/state file
|
||
touched. Full suite: 592/592 (up from 579). `flutter analyze`: 1 issue, identical to baseline.
|
||
|
||
**`size:exception` — "move-only diff".** Realized: **2,190 changed lines** (1,485+ / 705-) across 28 files — under
|
||
the ~2,500-3,200 estimate this time (Música local's compact Stateful body and this batch's conservative choice to
|
||
verify reachability rather than tap through unmocked native-channel controls for Backup/Grabaciones' riskiest rows
|
||
both kept it below forecast). Same "move-only diff" justification as WU3a.
|
||
|
||
## WU4 — Favoritos restyle
|
||
|
||
**Commit**: `feat(favoritos): replace stacked group panels with chip-filtered reorderable list`
|
||
**Depends on**: WU1
|
||
**Spec refs**: `favorites-organization` — Chip-Filtered Flat List, Drag-to-Reorder Within the Active Filter, Sort
|
||
Action Using Existing Criteria, Group Management Reachable from Favoritos, Custom-Station CTA Preserved
|
||
**Verify**: `flutter test test/pantallas/pantalla_favoritos_plural_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `test/pantallas/pantalla_favoritos_plural_test.dart` (+ new reorder scenario)
|
||
|
||
- [ ] 4.1 RED — update the test file: chip filter narrows the list ("Todas · N" + one chip per group); drag-to-reorder
|
||
persists across a simulated restart; `swap_vert` applies `OrdenEmisoras`; create-group action reachable;
|
||
custom-station CTA reachable.
|
||
- [ ] 4.2 GREEN — replace `_GrupoFavoritosPanel` / `_FavoritoItem` with a horizontally-scrollable chip row + a single
|
||
`ReorderableListView` filtered by the active chip.
|
||
- [ ] 4.3 GREEN — wire persistence for the new order and the `swap_vert` action to `OrdenEmisoras`.
|
||
- [ ] 4.4 GREEN — surface the create-group action; keep the dashed custom-station CTA.
|
||
- [ ] 4.5 REFACTOR — extract the chip row / reorderable row into private widgets if the file grows unwieldy; confirm
|
||
push-chrome is **not** applied here (Favoritos keeps its tab bar — the one exemption).
|
||
- [ ] 4.6 Verify — reorder-persists scenario green after simulated restart; sort scenario green.
|
||
|
||
## WU5 — Escuchar restructure
|
||
|
||
**Commit**: `feat(escuchar): replace discovery browser with embedded player and favorites grid`
|
||
**Depends on**: WU1, WU4
|
||
**Spec refs**: `app-navigation-shell` — Root-to-Root Switching Without Push
|
||
**Verify**: `flutter test test/pantallas/pantalla_inicio_test.dart test/pantallas/pantalla_inicio_rebuild_test.dart test/widgets/mini_reproductor_configurar_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `pantalla_inicio_test.dart`, `pantalla_inicio_rebuild_test.dart`, `mini_reproductor_configurar_test.dart`
|
||
|
||
- [ ] 5.1 RED — write the ADR-7 anti-cache test: mutate `EstadoRadio` **from outside the widget tree** (simulating
|
||
Android Auto / notification-driven playback change) and assert the Escuchar hero follows, with zero cached
|
||
fields in `State`.
|
||
- [ ] 5.2 RED — **hazard test**: assert `MiniReproductor` is `visible: false` (renders `SizedBox.shrink()`) while on
|
||
Escuchar, AND that `configurarLocalizaciones` still ran in `didChangeDependencies`
|
||
(`mini_reproductor.dart:27-38`, the S3-R3 contract). Removing the widget from the tree would silently break
|
||
this — the hazard is hiding it structurally, not visually.
|
||
- [ ] 5.3 RED — assert "Ver todas" calls `context.read<EstadoNavegacionRaiz>().irA(RaizPluriWave.favoritos)` and that
|
||
`Navigator` depth is unchanged (switches tab, does not push). This only *consumes* WU1's provider.
|
||
- [ ] 5.4 RED — update `pantalla_inicio_test.dart` / `pantalla_inicio_rebuild_test.dart` for the new content model
|
||
(hero + "Tus emisoras" grid replaces the discovery grid); confirm `context.select` scoping keeps rebuild count
|
||
low (existing `MemoLista` pattern).
|
||
- [ ] 5.5 GREEN — build the Escuchar hero as a `StatelessWidget` reusing `_WaveHero` / `VisualizadorAudio` patterns
|
||
from `pantalla_reproductor.dart` (square art, `barras: 30`, `altura: 26`, `color: liveGreen`), transport row
|
||
with sleep as the 5th action, tool-tray entry chip; reads via `context.select<EstadoRadio, T>` per scalar.
|
||
- [ ] 5.6 GREEN — add `MiniReproductor.altura` as a value **measured from its actual laid-out height at apply time**
|
||
(e.g. via a `GlobalKey`/`RenderBox` read at build), never a guessed constant; add its `visible` parameter.
|
||
`_PaginaPrincipal` sets `visible: false` on Escuchar only, keeping `State` mounted.
|
||
- [ ] 5.7 GREEN — add the derived `PluriLayout` constant (`bottomChromeInset - MiniReproductor.altura`,
|
||
`pluri_layout.dart:10` currently hardcodes `146` assuming the mini player is present) for Escuchar's content
|
||
padding.
|
||
- [ ] 5.8 GREEN — swap the "Tus emisoras" grid data source to `listaFavoritos` (capped) with "Ver todas" wired to
|
||
`EstadoNavegacionRaiz.irA(RaizPluriWave.favoritos)`.
|
||
- [ ] 5.9 REFACTOR — leave the discovery-section widgets (`_seccionCercanas`/`_chipGeneros`/etc.) in place for now;
|
||
WU6 relocates and then deletes them — do not delete here to avoid an intermediate commit with the content
|
||
nowhere. Note this handoff explicitly in the commit body.
|
||
- [ ] 5.10 Verify — anti-cache test green; mini-player-hidden-but-side-effect-ran test green; tab-switch-not-push
|
||
test green; `visualizador_audio.dart` and `estado_radio.dart` show empty `git diff`.
|
||
|
||
## WU6 — Buscar landing state + filters
|
||
|
||
**Commit**: `feat(buscar): add discovery landing state, filter pills, counter, and sort`
|
||
**Depends on**: WU5
|
||
**Spec refs**: `station-discovery-browse` — Buscar Landing State Shows Discovery Content, Active-Filter Pills and
|
||
Results Counter, One-Tap Clear-All-Filters on Empty Results, Client-Side Search Sort Only
|
||
**Verify**: `flutter test test/pantallas/pantalla_buscar_shimmer_test.dart test/estado/estado_busqueda_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `pantalla_buscar_shimmer_test.dart`, `estado_busqueda_test.dart` (non-sort paths unmodified)
|
||
|
||
- [ ] 6.1 No spike needed here — the sort ruling is already verified (Engram 2505): server-side `order`/`reverse`
|
||
exist but are rejected; `OrdenEmisoras` is the only extension point. Do not re-derive.
|
||
- [ ] 6.2 RED — empty query renders discovery content (near-you / genres / trending / Países entry); non-empty query
|
||
replaces it with the results view.
|
||
- [ ] 6.3 RED — active-filter pills (country/language/quality) with a close affordance; results counter text; the
|
||
one-tap "Quitar los 2 filtros" action on a 2-filter/0-result fixture.
|
||
- [ ] 6.4 RED — regression guard: every rendered "Ordenar" option maps to a real `OrdenEmisoras` case with its own
|
||
test.
|
||
- [ ] 6.5 GREEN — move the discovery content (near-you/genres/trending, Países entry point) from
|
||
`pantalla_inicio.dart`'s old sections into `pantalla_buscar.dart`'s empty-query landing state; **delete** the
|
||
moved sections from `pantalla_inicio.dart` now (completes WU5's deferred cleanup, task 5.9).
|
||
- [ ] 6.6 GREEN — collapse the 3 always-visible `FilterChip` rows into an active-pills bar with bottom-sheet
|
||
pickers; add the results counter and clear-all-filters action.
|
||
- [ ] 6.7 GREEN — extend `enum OrdenEmisoras` (`orden_emisoras.dart:4`) only with criteria backed by existing
|
||
`Emisora` fields (`bitrate`, `votes`, `clickcount`); wire "Ordenar" client-side only — no `order`/`reverse`
|
||
param added to any request.
|
||
- [ ] 6.8 REFACTOR — confirm `estado_busqueda_test.dart`'s non-sort paths still pass unmodified.
|
||
- [ ] 6.9 Verify — no test in this WU asserts an `order` param on any outgoing request; sort-option regression guard
|
||
green.
|
||
|
||
## WU7 — Países browser + `_get` transport extraction
|
||
|
||
**Commit**: `feat(paises): add country browser and extract shared radio transport`
|
||
**Depends on**: WU6
|
||
**Spec refs**: `station-discovery-browse` — Países Browser Over the Verified Countries Contract, Existing Station
|
||
Calls Unchanged by Transport Extraction
|
||
**Verify**: `flutter test test/servicios/servicio_radio_test.dart test/servicios/servicio_radio_transporte_test.dart test/estado/estado_busqueda_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: `test/servicios/servicio_radio_transporte_test.dart`, country-model test, `pantalla_paises_test.dart`
|
||
**Modified tests**: `estado_busqueda_test.dart` (additions only); `servicio_radio_test.dart` is **not modified**
|
||
|
||
> **Special ordering rule (non-negotiable): task 7.1 must be green before task 7.4 begins.** Its shuffled-fixture
|
||
> UUID-sequence assertion is what makes the extraction safe — skipping it lets a sort silently sink into transport.
|
||
|
||
- [ ] 7.1 RED→GREEN (characterisation, green by construction against the **unmodified** `_get`) — create
|
||
`servicio_radio_transporte_test.dart` pinning, for each of the 7 `_get`-based methods (`obtenerPopulares`,
|
||
`obtenerTendencias`, `buscarPorNombre`, `buscarPorPais`, `buscarPorIdioma`, `buscarPorTag`, `buscar`): request
|
||
path, `lastcheckok=1` present, `hidebroken=true` present, non-empty `User-Agent`, exact
|
||
`order`/`reverse`/`limit`/`offset`, and the exact returned UUID sequence from a fixture with deliberately
|
||
shuffled `bitrate`/`clickcount`/`votes`. Pin `registrarClick`'s path and that it sends *some* `User-Agent`
|
||
(do **not** pin its known-stale literal at line 325 — that would convert a bug into a contract).
|
||
- [ ] 7.2 RED — `PaisRadio.fromApi` parses `stationcount` via `int.tryParse` from a fixture where it arrives as a
|
||
JSON **string**; defaults safely on missing fields.
|
||
- [ ] 7.3 RED — `estado_busqueda_test.dart` additions for `paises`, `cargandoPaises`, `cargarPaises()` (in-memory
|
||
cache guard); `pantalla_paises_test.dart` for "Tus idiomas" + full alphabetical list with counts.
|
||
- [ ] 7.4 GREEN — extract `_getJson` (transport only: `_descubrirServidores`, host rotation, bounded retries,
|
||
User-Agent, timeout, status check, `json.decode`, sticky-host bookkeeping) out of `_get`, moved as **one
|
||
block, no logic edits**; `_get` re-implemented on top, still applying `lastcheckok`, `Emisora.fromApi`,
|
||
`_compararCalidad`.
|
||
- [ ] 7.5 GREEN — implement `lib/modelos/pais_radio.dart`, `obtenerPaises()` (calls
|
||
`_getJson('/json/countries', const {})`, no `lastcheckok`, no `order`), and `lib/pantallas/pantalla_paises.dart`.
|
||
- [ ] 7.6 RE-RUN (proof step) — re-run task 7.1's tests against the extracted code; must be byte-identical green,
|
||
including the shuffled-fixture UUID-sequence assertion.
|
||
- [ ] 7.7 REFACTOR — document in the commit body that a successful `/json/countries` call now warms
|
||
`_servidorActual` for subsequent station calls — an accepted, intentional behaviour delta, not a regression.
|
||
- [ ] 7.8 Verify — grep confirms `countrycodes` appears nowhere in `lib/`; the string-`stationcount` fixture renders
|
||
`482` without a cast error; the countries request has no `lastcheckok`; all 8 original methods' tests pass
|
||
identically before and after.
|
||
|
||
## WU8 — Alarmas root restyle
|
||
|
||
**Commit**: `feat(alarmas): simplify alarm cards and add vacation summary row`
|
||
**Depends on**: WU1
|
||
**Spec refs**: `native-alarms` (delta) — Alarm Card Tap-to-Edit Swipe-to-Delete, Hero Banner Inline Skip;
|
||
`alarm-vacation-ranges` — Vacation Summary Row Replaces the Inline Panel
|
||
**Verify**: `flutter test test/pantallas/pantalla_alarmas_editor_test.dart test/estado/estado_alarmas_test.dart test/estado/estado_alarmas_snooze_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `pantalla_alarmas_editor_test.dart` (+ new card-gesture scenarios); `estado_alarmas_test.dart`,
|
||
`estado_alarmas_snooze_test.dart` must pass unmodified
|
||
|
||
- [ ] 8.1 RED — tapping a simplified alarm card (giant time + station + switch) opens the editor pre-filled;
|
||
swiping triggers delete with confirmation; no capability from the old always-visible button row is lost.
|
||
- [ ] 8.2 RED — the hero banner's inline "Saltar" action skips the featured (soonest-firing) alarm, consistent with
|
||
existing skip-next behaviour.
|
||
- [ ] 8.3 RED — the vacation panel is now a summary row (range count + next-range countdown) that pushes a screen
|
||
on tap (destination screen not yet built — assert `PluriPushScaffold.push` is invoked; WU9 builds it).
|
||
- [ ] 8.4 GREEN — simplify `_TarjetaAlarma` to giant time/name/switch; move Edit/Skip/Delete behind tap/swipe.
|
||
- [ ] 8.5 GREEN — add the hero banner's inline "Saltar" pill calling the existing skip-next path.
|
||
- [ ] 8.6 GREEN — replace `_PanelVacaciones`'s inline list with a summary row wired to push the Vacaciones manager
|
||
screen.
|
||
- [ ] 8.7 REFACTOR — confirm no scheduling/dismiss-guard code path was touched; `estado_alarmas_test.dart` and
|
||
`estado_alarmas_snooze_test.dart` pass unmodified.
|
||
- [ ] 8.8 Verify — tap/swipe/hero-skip scenarios all green; confirm
|
||
`pantalla_alarma_sonando_dismiss_guard_test.dart` was not touched by this WU (it only edits the root list
|
||
screen).
|
||
|
||
## WU9 — Vacaciones manager (new screen)
|
||
|
||
**Commit**: `feat(vacaciones): add vacation range manager screen`
|
||
**Depends on**: WU8
|
||
**Spec refs**: `alarm-vacation-ranges` — Active Range Detection, Per-Alarm Pause-Impact Computation, Upcoming Ranges
|
||
Query, Past-Ranges History Query, Vacation Summary Row Replaces the Inline Panel (destination screen)
|
||
**Verify**: `flutter test test/estado/estado_alarmas_test.dart test/pantallas/pantalla_vacaciones_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: `pantalla_vacaciones_test.dart`
|
||
**Modified tests**: `estado_alarmas_test.dart` (additions only)
|
||
|
||
- [ ] 9.1 RED — with a **fixed `ahora`**: `rangoVacacionesActivo` returns the active range + days-remaining, or null
|
||
when none matches; `vacacionesProximas` returns future ranges ascending by start; `vacacionesPasadas` returns
|
||
ended ranges descending by end, disjoint from active/upcoming.
|
||
- [ ] 9.2 RED — `impactoDeRango`: given 3 alarms (2 with `sonarEnVacaciones == false`, 1 with `true`) during an
|
||
active range, assert exactly those 2 in `pausadas` and that 1 in `noAfectadas`, mirroring
|
||
`servicio_programacion_alarmas.dart:150`'s predicate exactly.
|
||
- [ ] 9.3 RED — `pantalla_vacaciones_test.dart`: active-range hero with progress + per-alarm impact line;
|
||
"PROGRAMADOS" upcoming list; dashed "Añadir rango" CTA; "Rangos pasados" history row/screen.
|
||
- [ ] 9.4 GREEN — add the 4 pure query members to `EstadoAlarmas` (`{DateTime? ahora}` defaulting to
|
||
`DateTime.now()`), delegating `rangoVacacionesActivo` to the existing `RangoVacaciones.contiene(fecha)`.
|
||
- [ ] 9.5 GREEN — add `ImpactoVacaciones` beside `RangoVacaciones` in `lib/modelos/alarma_musical.dart`.
|
||
- [ ] 9.6 GREEN — build `lib/pantallas/pantalla_vacaciones.dart` as a `PluriPushScaffold`; wire WU8's summary-row
|
||
tap to it.
|
||
- [ ] 9.7 REFACTOR — confirm none of the 4 new methods call `guardarVacaciones` or any reprogramming path
|
||
(read-only over `_alarmas`/`_vacaciones`); confirm no ticker/periodic timer was added.
|
||
- [ ] 9.8 Verify — `pantalla_alarma_sonando_dismiss_guard_test.dart` passes **unmodified** (hard rule — if a query
|
||
addition appears to require touching it, STOP and escalate; do not edit the test).
|
||
|
||
## WU10 — Alarm editor sheet rewrite
|
||
|
||
**Commit**: `feat(alarmas): rewrite alarm editor with inline time widget`
|
||
**Depends on**: WU8
|
||
**Spec refs**: `native-alarms` (delta) — Alarm Editor Preserves Date, Fallback Station, and Sound Fields
|
||
**Verify**: `flutter test test/widgets/editor_hora_inline_test.dart test/pantallas/pantalla_alarmas_fecha_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: `test/widgets/editor_hora_inline_test.dart` (standalone)
|
||
**Modified tests**: `pantalla_alarmas_fecha_test.dart`
|
||
|
||
- [ ] 10.1 RED — `editor_hora_inline_test.dart`, **standalone, independent of the sheet**: drag/tap adjusts hour and
|
||
minute, wraps at 23:59→00:00, exposes correct a11y labels.
|
||
- [ ] 10.2 GREEN — implement `lib/widgets/editor_hora_inline.dart` (giant inline HH:MM display, drag/tap-to-adjust).
|
||
- [ ] 10.3 RED — update `pantalla_alarmas_fecha_test.dart`: one-time date alarm still creatable via the (now
|
||
collapsed) Advanced section; fallback-station picker and sound dropdown still settable and persisted.
|
||
- [ ] 10.4 GREEN — replace the native `showTimePicker` dialog with `EditorHoraInline`; keep weekday circles always
|
||
visible; move the date field, fallback-station picker, and sound dropdown into a collapsed "Advanced"
|
||
section rather than dropping them.
|
||
- [ ] 10.5 GREEN — restyle the volume/fade-in sliders (cosmetic only, no behaviour change).
|
||
- [ ] 10.6 REFACTOR — confirm the dismiss-guard test is untouched by this WU (editor sheet only, not the ringing
|
||
screen).
|
||
- [ ] 10.7 Verify — standalone widget test green independent of the sheet; date/fallback/sound round-trip test
|
||
green.
|
||
|
||
**`size:exception` recommended.** ~500-650 lines — the largest genuinely-new widget in the plan; already isolated
|
||
as its own unit per the proposal ("never bundled"). See forecast table footnote ‡.
|
||
|
||
## WU11 — Alarm ringing restyle
|
||
|
||
**Commit**: `feat(alarma-sonando): restyle ringing screen, drop live countdown label`
|
||
**Depends on**: WU1
|
||
**Spec refs**: `native-alarms` (delta) — Ringing Screen Shows a Static Status Label, Dismiss Guard and Force-Stop
|
||
Banner Survive the Restyle
|
||
**Verify**: `flutter test test/pantallas/pantalla_alarma_sonando_test.dart test/pantallas/pantalla_alarma_sonando_scaffold_test.dart test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `pantalla_alarma_sonando_test.dart`, `pantalla_alarma_sonando_scaffold_test.dart`.
|
||
**`pantalla_alarma_sonando_dismiss_guard_test.dart` is run but MUST NOT be edited.**
|
||
|
||
> **Hard rule, restated:** if the restyle appears to require changing the dismiss-guard test, the task is to STOP
|
||
> and escalate — not to edit the test.
|
||
|
||
- [ ] 11.1 RED — update the two non-guard test files for the full-bleed blurred-art layout, giant 88px time
|
||
(`heroTime`, wrapped in `FittedBox(fit: BoxFit.scaleDown)` per the text-scaling rule), 3 fixed snooze tiles
|
||
(3/5/10 min, 10 highlighted), full-width stop pill.
|
||
- [ ] 11.2 RED — the status label renders "Subiendo volumen" (or localized equivalent) with no accompanying
|
||
numeric value that changes over time.
|
||
- [ ] 11.3 GREEN — restyle to a full-bleed art background; replace the `Wrap` of snooze chips with 3 fixed tiles;
|
||
replace the glass-card container.
|
||
- [ ] 11.4 GREEN — keep the status label static, sourced only from `widget.alarma` (unchanged from the existing
|
||
documented constraint); preserve the force-stop retry banner in the new layout.
|
||
- [ ] 11.5 REFACTOR — run `pantalla_alarma_sonando_dismiss_guard_test.dart` **unchanged**; if it fails, STOP — do
|
||
not edit it, escalate instead.
|
||
- [ ] 11.6 Verify — force-stop banner still appears on a simulated stop failure; dismiss-guard test file diff is
|
||
empty.
|
||
|
||
## WU13 — Ecualizador settings screen (5-band restyle)
|
||
|
||
**Commit**: `feat(eq): restyle equalizer screen and add custom presets`
|
||
**Depends on**: WU3a
|
||
**Spec refs**: `eq-custom-presets` — Five-Band Equalizer (Regression Guard), Custom Preset Save, Custom Preset
|
||
Naming Validates Non-Empty Input, Per-Station EQ Entry Point (component only; consumption is WU14),
|
||
Stations-With-Own-EQ Drill-Down, Base-vs-Per-Station Explainer Preserved, Active Output Surfaced on the Main Screen
|
||
**Verify**: `flutter test test/widgets/ecualizador_widget_test.dart test/servicios/servicio_presets_personalizados_test.dart test/servicios/servicio_ecualizador_test.dart test/estado/estado_ecualizador_test.dart test/servicios/servicio_audio_eq_reapply_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: `servicio_presets_personalizados_test.dart`, `pantalla_ajustes_ecualizador_test.dart`
|
||
**Modified tests**: `ecualizador_widget_test.dart`. `servicio_ecualizador_test.dart`, `estado_ecualizador_test.dart`,
|
||
`servicio_audio_eq_reapply_test.dart` must pass **unmodified**.
|
||
|
||
> **Hazard, restated: persistence for custom presets must NOT go in `servicio_ecualizador.dart`.** That file has an
|
||
> empty-`git diff` success criterion.
|
||
|
||
- [ ] 13.1 RED — the Ecualizador screen renders exactly **5** vertical sliders (regression guard — reject any
|
||
future 7-slider change on sight).
|
||
- [ ] 13.2 RED — `servicio_presets_personalizados_test.dart`: save/list/delete round-trip for a named custom preset
|
||
via its **own** SharedPreferences key `eq_custom_presets_v1`.
|
||
- [ ] 13.3 RED — submitting an empty/whitespace-only preset name shows a validation message and persists nothing.
|
||
- [ ] 13.4 RED — the "Emisoras con ajuste propio" drill-down lists exactly the stations present in
|
||
`presetsPorEmisora`.
|
||
- [ ] 13.5 RED — the "Salida activa" row is visible on the main screen (not only Advanced) and updates on a
|
||
simulated device change; the base-vs-per-station explainer banner is visible.
|
||
- [ ] 13.6 GREEN — create `lib/servicios/servicio_presets_personalizados.dart` (**new file**, own SharedPreferences
|
||
key). **Do NOT add any member to `servicio_ecualizador.dart`.** `lib/modelos/preset_ecualizador.dart` stays
|
||
unchanged — a user preset is a `PresetEcualizador` with a user-supplied `nombre`; `toJson`/`desdeJson`
|
||
already exist.
|
||
- [ ] 13.7 GREEN — extend `EstadoEcualizador` with `presetsPersonalizados`, `guardarPresetPersonalizado(nombre)`,
|
||
`eliminarPresetPersonalizado(nombre)`, reading/writing only through the new service.
|
||
- [ ] 13.8 GREEN — restyle `lib/widgets/ecualizador_widget.dart` in place: strip the internal title + preset chip
|
||
row (lines 59-77 — the pushed header now carries the title), restyle the 5 `Card`/`RotatedBox`/`Slider`
|
||
tiles to the vertical-track look, add the `habilitado` parameter. Keep the `for (int i = 0; i < 5; i++)`
|
||
loop bound literally `5`.
|
||
- [ ] 13.9 GREEN — build `lib/pantallas/ajustes/pantalla_ajustes_ecualizador.dart` wiring
|
||
`EstadoEcualizador.presetPrincipal` / `cambiarPresetPrincipal`, surfacing "Salida activa" and the drill-down
|
||
row.
|
||
- [ ] 13.10 REFACTOR — reviewer check: confirm `git diff` is **empty** for `lib/servicios/servicio_ecualizador.dart`,
|
||
`lib/modelos/preset_ecualizador.dart`, and the band-application block at
|
||
`lib/servicios/servicio_audio.dart:749-762`.
|
||
- [ ] 13.11 Verify — the three EQ test files pass **unmodified**; exactly 5 sliders render.
|
||
|
||
## WU14 — Reproductor completo + per-station EQ sheet
|
||
|
||
**Commit**: `feat(reproductor): restructure full player with tool-tray and EQ sheet`
|
||
**Depends on**: WU13
|
||
**Spec refs**: `eq-custom-presets` — Per-Station EQ Entry Point From the Player; `multi-device-eq` (delta) —
|
||
Per-Station EQ Entry Relocates, Resolution Logic Does Not
|
||
**Verify**: `flutter test test/pantallas/pantalla_reproductor_test.dart test/servicios/servicio_ecualizador_test.dart test/estado/estado_ecualizador_test.dart test/servicios/servicio_audio_eq_reapply_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `pantalla_reproductor_test.dart` (or equivalent widget test). Three EQ test files must pass
|
||
**unmodified**.
|
||
|
||
- [ ] 14.1 RED — the per-station EQ bottom sheet renders **the same `EcualizadorWidget` type** WU13 restyled
|
||
(assert by `runtimeType`, so a duplicate implementation fails the test, not just a visual review).
|
||
- [ ] 14.2 RED — the 4 tool-tray tiles ("EQ propio", "Grabar", sleep-timer value, "Compartir") each open their own
|
||
bottom sheet.
|
||
- [ ] 14.3 RED — opening "EQ propio" for a playing station shows 5 sliders bound to that station's resolved preset,
|
||
and a change round-trips through the existing per-station persistence path (`presetsPorEmisora` /
|
||
`presetsMatriz`).
|
||
- [ ] 14.4 GREEN — restructure `pantalla_reproductor.dart`: square art, favorite moved into the transport row,
|
||
single subtitle line (collapse the current `_InfoChips` `Wrap`), 4-tile tool-tray grid replacing the
|
||
always-expanded recording panel + separate sleep-timer button + EQ toggle.
|
||
- [ ] 14.5 GREEN — wire "EQ propio" to a bottom sheet hosting `EcualizadorWidget` bound via
|
||
`EstadoEcualizador.presetParaEmisora(uuid)` / `guardarPresetPorEmisora(uuid, ...)`.
|
||
- [ ] 14.6 GREEN — add the quality row + "Cambiar" action and the "Compartir" tool-tray tile.
|
||
- [ ] 14.7 REFACTOR — confirm no second EQ editor file was created; confirm the `multi-device-eq` regression
|
||
scenarios (device-event resolve-and-apply, first-seen bootstrap, cold start, connect/disconnect/reconnect,
|
||
toggle-off) still pass unmodified.
|
||
- [ ] 14.8 Verify — the 3 EQ test files remain green and unmodified; `EcualizadorWidget` type-identity assertion
|
||
passes.
|
||
|
||
## WU15 — Grabaciones library (new list)
|
||
|
||
**Commit**: `feat(grabaciones): add recordings library screen`
|
||
**Depends on**: WU3b
|
||
**Spec refs**: `recordings-library` — Storage Usage Display, Browsable Recordings List, Per-Recording Menu
|
||
Constrained to Rename/Share/Delete
|
||
**Verify**: `flutter test test/pantallas/pantalla_grabaciones_test.dart test/servicios/servicio_grabacion_radio_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: `pantalla_grabaciones_test.dart`
|
||
**Modified tests**: `servicio_grabacion_radio_test.dart` (only if a listing method is added)
|
||
|
||
- [x] 15.1 RED — storage progress-bar fill reflects used/total (e.g. 84/200 MB fixture) with a caption stating both
|
||
values.
|
||
- [x] 15.2 RED — 3 recording-file fixtures render as 3 rows (name/date/duration/size); an empty folder renders an
|
||
empty state, not an error; tapping play starts/stops playback.
|
||
- [x] 15.3 RED — the "⋮" menu exposes exactly Rename/Share/Delete — constrained to what
|
||
`servicio_grabacion_radio.dart` already exposes, no extra action.
|
||
- [x] 15.4 RED — Delete (file + row removed), Rename (persists across reload), Share (invokes platform share sheet)
|
||
scenarios. **Delete and Rename widget-level scenarios are written but `skip: true`** (Share is not — see
|
||
below): both hang indefinitely the instant `EstadoGrabacion` wires to a bare, real `ServicioGrabacionRadio`
|
||
pointed at a real directory inside a `testWidgets()` body — a combination no other test in this codebase
|
||
uses. Four independent causes were ruled out at apply time (a directory-path off-by-one;
|
||
`Directory.list()`'s async stream vs `listSync()`; the confirm-dialog interaction specifically — a
|
||
render-only reproduction with zero taps hangs identically; `WidgetTester.runAsync()`, Flutter's own
|
||
documented escape hatch for real I/O during widget lifecycle). The underlying logic is NOT unproven:
|
||
`eliminarGrabacion` and `renombrarGrabacion` both have passing unit tests in
|
||
`servicio_grabacion_radio_test.dart` (plain `test()`, exercised against real files, no hang) — the one
|
||
variable no diagnostic could change was `testWidgets()` itself. Documented inline in both skipped tests;
|
||
flagged for a fresh Windows-process debugging pass in a future batch, not treated as a code defect.
|
||
- [x] 15.5 GREEN — added `listarGrabaciones()` to `servicio_grabacion_radio.dart` (pure filesystem read via
|
||
`listSync`/`statSync`, sorted most-recent-first, empty list for a missing directory — never throws). Also
|
||
added `eliminarGrabacion(ruta)` and `renombrarGrabacion(ruta, nuevoNombre)` — file-lifecycle management for
|
||
files this service already creates, not new capabilities; both needed for the constrained "⋮" menu (task
|
||
15.3/15.4), both via plain `dart:io`, no new conceptual surface. `EstadoGrabacion` gained thin delegates for
|
||
all three plus `notifyListeners()`. New `lib/modelos/archivo_grabacion.dart` (ruta, nombre, fecha,
|
||
tamanoBytes — pure filesystem metadata, no audio decoding).
|
||
- [x] 15.6 GREEN — built `lib/pantallas/pantalla_grabaciones.dart`: storage bar (`LinearProgressIndicator` over
|
||
`EstadoGrabacion.maxBytes` vs. the summed listing), per-recording rows (name/date/duration/size) with inline
|
||
play/pause, "⋮" menu (`PopupMenuButton`, exactly 3 items). New `ReproductorGrabaciones` abstraction
|
||
(duration-lookup + toggle-play) keeps `just_audio.AudioPlayer` out of `ServicioAudio` (never touched — hard
|
||
constraint) and out of `servicio_grabacion_radio.dart`; the real `just_audio`-backed implementation is
|
||
static-review-only (same documented constraint as `PluriWaveAudioHandler` in `cola_local_test.dart` — a real
|
||
`AudioPlayer` needs platform `MethodChannel`s this suite does not mock), every test injects a fake. `Share`
|
||
is similarly injected (`compartir` constructor parameter), defaulting to the real `share_plus` call. 12 new
|
||
ARB keys (en/es only, matching WU1/3a/3b precedent): `recordingsLibraryTitle`,
|
||
`recordingsLibraryStorageCaption`, `recordingsLibraryEmptyTitle`, `recordingsLibraryEmptySubtitle`,
|
||
`recordingActionRename/Share/Delete`, `recordingRenameDialogTitle/Label/EmptyError`,
|
||
`recordingDeleteConfirmTitle/Message` — a brand-new screen needs new copy, unlike WU3a/WU3b's move-only
|
||
screens.
|
||
- [x] 15.7 REFACTOR — confirmed the menu cannot expose a 4th action (task 15.3's test asserts `PopupMenuItem`
|
||
count is exactly 3); confirmed the empty-folder scenario renders `PluriEmptyState`, not a crash or exception
|
||
(`tester.takeException()` asserted null).
|
||
- [x] 15.8 Verify — scoped verify green: 17 passed, 2 skipped (documented above), 0 failed — the menu-action
|
||
scenarios remain exercised end-to-end for Share (15.4-C) plus both file-mutation methods proven at the
|
||
service level. Empty-folder scenario green. Full suite: 604/604 green (2 skipped), up from 592. `flutter
|
||
analyze`: 1 issue, identical to baseline.
|
||
|
||
**Known plan gap, not fixed here (out of WU15's task list, flagged for the orchestrator):** no task in WU15 — or
|
||
anywhere else in this file — wires a navigation entry point to `PantallaGrabaciones`. It is a genuinely new,
|
||
currently-unreachable route. `PantallaAjustesGrabaciones` (WU3b) is a different screen (folder/size-limit settings)
|
||
and no task asks it to gain a "view library" link. WU15's own **Verify** command lists only
|
||
`pantalla_grabaciones_test.dart` and `servicio_grabacion_radio_test.dart` — not
|
||
`pantalla_ajustes_grabaciones_test.dart` — confirming this screen is not meant to touch WU3b's file in this work
|
||
unit. Not invented here; needs a design decision, not a guess.
|
||
|
||
## WU15b — Wire the recordings library into Settings navigation
|
||
|
||
**Not in the original plan — added to close the WU15 gap noted above.** WU15 built `PantallaGrabaciones` (the
|
||
recordings library) fully tested and committed, but left it unreachable from the app: `rg "PantallaGrabaciones" lib/`
|
||
found only its own declaration. This work unit exists solely to fix that.
|
||
|
||
**Commit**: `fix(grabaciones): wire the recordings library into Settings navigation`
|
||
**Depends on**: WU15
|
||
**Spec refs**: `recordings-library` — Browsable Recordings List (reachability); `app-navigation-shell` — Push-Chrome
|
||
on Second-Level Screens (the settings form stays a pushed screen, now one level deeper)
|
||
**Verify**: `flutter test test/pantallas/pantalla_ajustes_test.dart test/pantallas/pantalla_grabaciones_test.dart test/pantallas/ajustes/pantalla_ajustes_grabaciones_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --cached --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `pantalla_ajustes_test.dart` (one scenario re-targeted), `pantalla_grabaciones_test.dart` (one new
|
||
scenario)
|
||
|
||
**Coordinator ruling applied.** The approved mockup's screen 12 ("Ajustes › Grabaciones") depicts the recordings
|
||
**library**, not the settings form — so the `GRABACIONES Y MÚSICA` group's "Grabaciones" row now opens
|
||
`PantallaGrabaciones` (the library) instead of `PantallaAjustesGrabaciones` (the folder/size-limit settings form).
|
||
The settings form is not dropped: it stays reachable, but now from **within** the library, via a settings icon in its
|
||
`PluriPushScaffold.actions` (matching the existing `pantalla_ajustes_timer_sueno.dart` "Add" action precedent for a
|
||
real capability living in the header, not decorative chrome).
|
||
|
||
- [x] 15b.1 RED — re-targeted `pantalla_ajustes_test.dart`'s "tapping the Grabaciones row" scenario: it now asserts
|
||
landing on `PantallaGrabaciones` (`find.text('My recordings')`), not the settings form (`find.text('Change
|
||
path')` asserted absent).
|
||
- [x] 15b.2 RED — added a new scenario to `pantalla_grabaciones_test.dart`: tapping the library's settings icon
|
||
pushes a second `PluriPushScaffold` showing the settings form (`find.text('Change path')`).
|
||
- [x] 15b.3 GREEN — `pantalla_ajustes.dart`: the "Grabaciones" `FilaAjuste.onTap` now pushes `PantallaGrabaciones`;
|
||
removed the now-unused `PantallaAjustesGrabaciones` import from this file.
|
||
- [x] 15b.4 GREEN — `pantalla_grabaciones.dart`: added `actions: [IconButton(Icons.settings_outlined, ...)]` to its
|
||
`PluriPushScaffold`, pushing `PantallaAjustesGrabaciones`. One new ARB key (en/es only, matching precedent):
|
||
`recordingsLibrarySettingsTooltip`.
|
||
- [x] 15b.5 REFACTOR — confirmed the new test needed the same `_suppressListTileInkAssertion()` helper WU3a/WU3b
|
||
established (WU3b Discovery 9: any pushed screen whose `PluriGlassSurface` body contains a tappable `ListTile`
|
||
needs it) — added to `pantalla_grabaciones_test.dart`, scoped to only the one new scenario that mounts
|
||
`PantallaAjustesGrabaciones`.
|
||
- [x] 15b.6 Verify — scoped suite green: 605/605 (up from 604), 2 skipped (unchanged, both pre-existing WU15
|
||
documented skips). `flutter analyze`: 1 issue, identical to baseline. Literal-encoding scan: zero hits.
|
||
|
||
|
||
|
||
**Commit**: `feat(connectivity): restyle offline and reconnect banners`
|
||
**Depends on**: WU1
|
||
**Spec refs**: crosscutting risk register (reconnect attempt counter is verify-first, not assumed)
|
||
**Verify**: `flutter test test/widgets/reconnect_ui_test.dart test/servicios/servicio_audio_reconnect_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: `reconnect_ui_test.dart`. `servicio_audio_reconnect_test.dart` must pass unmodified.
|
||
|
||
- [ ] 16.1 Verify-first — run `servicio_audio_reconnect_test.dart` and inspect the reconnect controller: confirm
|
||
whether it tracks an attempt count.
|
||
- [ ] 16.2 RED — restyle test for the offline banner (visual only) and, **only if 16.1 confirms an attempt count
|
||
exists**, a test for the "Reconectando · intento N de M" label.
|
||
- [ ] 16.3 GREEN — restyle the offline banner to the new visual language.
|
||
- [ ] 16.4 GREEN — **conditionally**: wire the attempt-count label only if the controller exposes one; otherwise
|
||
ship the restyle without the counter — do not add new plumbing to invent one.
|
||
- [ ] 16.5 REFACTOR — confirm shimmer skeletons (`TarjetaEmisoraShimmer`) are unchanged.
|
||
- [ ] 16.6 Verify — `reconnect_ui_test.dart` updated and green; `servicio_audio_reconnect_test.dart` unmodified and
|
||
green.
|
||
|
||
## WU17 — Welcome / onboarding screen
|
||
|
||
**Commit**: `feat(bienvenida): add monetization-free welcome screen`
|
||
**Depends on**: WU1
|
||
**Spec refs**: `onboarding-welcome` — Full-Screen Welcome Route, No Monetization Content, CTA Dismisses to Escuchar
|
||
**Verify**: `flutter test test/pantallas/pantalla_bienvenida_test.dart && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**New tests**: `pantalla_bienvenida_test.dart`
|
||
|
||
- [ ] 17.1 RED — the welcome screen renders as a full-screen route (not a modal `Dialog`) with a logo, headline,
|
||
body copy, exactly 3 feature bullets, and exactly 1 primary CTA.
|
||
- [ ] 17.2 RED — binding no-monetization guard: scan the rendered widget tree's text content and assert it contains
|
||
none of: the substring "PRO", a currency amount, or a day-count trial phrase; no secondary "free version"
|
||
link.
|
||
- [ ] 17.3 RED — tapping "Empezar a escuchar" lands on the Escuchar tab (index 0) with the welcome route removed
|
||
from the back stack.
|
||
- [ ] 17.4 GREEN — build `lib/pantallas/pantalla_bienvenida.dart` as a full-screen route; content only (logo /
|
||
headline / body / 3 bullets / 1 CTA) — no PRO pill, no trial duration, no price, no secondary link.
|
||
- [ ] 17.5 GREEN — wire the CTA to dismiss-and-navigate to Escuchar with the route removed.
|
||
- [ ] 17.6 REFACTOR — grep `lib/l10n/app_*.arb` for "PRO"/price/day-count trial strings introduced by this WU;
|
||
confirm none exist.
|
||
- [ ] 17.7 Verify — all 3 scenario tests green; grep scan clean.
|
||
|
||
## WU18 — i18n batch (all 13 locales)
|
||
|
||
**Commit**: `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales`
|
||
**Depends on**: WU1-WU17 (runs last, after `en`/`es` land across all slices)
|
||
**Spec refs**: `app-navigation-shell` — Escuchar Tab Rename Preserves the ARB Key (11-locale completion); backs the
|
||
UI copy every other capability introduced
|
||
**Verify**: `flutter test && flutter analyze && dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM HEAD -- '*.dart')`
|
||
**Modified tests**: ARB key-parity guard test (extend existing or add one if none exists)
|
||
|
||
- [ ] 18.1 RED — an ARB-parity test asserting all 13 `lib/l10n/app_*.arb` files contain an identical key set.
|
||
- [ ] 18.2 RED — `navHome` exists as a key in all 13 locales, and `git diff` for this commit touches exactly 11
|
||
`navHome` **value** lines (en/es were WU1's job, task 1.9).
|
||
- [ ] 18.3 GREEN — add the ~35-45 new keys (accumulated from WU4-WU17's English/Spanish-only strings) to all 13
|
||
ARB files, sourcing translations for the 11 non-en/es locales.
|
||
- [ ] 18.4 GREEN — set the `navHome` value in the remaining 11 locales to a real translation of "Listen" (not a
|
||
mechanical copy of the English/Spanish pair).
|
||
- [ ] 18.5 REFACTOR — run the ARB-parity check across all 13 files; confirm zero missing/extra keys.
|
||
- [ ] 18.6 Verify — full `flutter test` suite green; `flutter analyze` clean; scoped `dart format` over the files
|
||
this change touched produces no diff (NEVER bare `dart format .` — see the warning at the top);
|
||
grep confirms no "PRO"/price/trial-duration string exists in any ARB file (final whole-app Success Criteria
|
||
check).
|
||
|
||
---
|
||
|
||
## Final Verification Sequence (run once, after WU18's commit)
|
||
|
||
1. `flutter test` — full suite green.
|
||
2. `flutter analyze` — clean.
|
||
3. `dart format --set-exit-if-changed $(git diff --name-only --diff-filter=ACM main -- '*.dart')` — no diff over the
|
||
files this change touched. **NEVER bare `dart format .`** — it reformats 27 unrelated pre-existing files and would
|
||
break the empty-diff guards in steps 4 and 5 below (Engram `reference/dart-format-scope-hazard`, id 2511).
|
||
4. `git diff --stat lib/servicios/navegacion_auto.dart lib/servicios/servicio_ecualizador.dart` — both empty.
|
||
5. `git diff lib/servicios/servicio_audio.dart` — band-application block (`749-762`) unchanged.
|
||
6. Grep `lib/` for `countrycodes` — zero hits.
|
||
7. Grep `lib/l10n/app_*.arb` for "PRO", a currency amount, or a day-count trial phrase — zero hits.
|
||
8. Confirm all 13 ARB files have identical key sets and `navHome` present in each.
|
||
9. Confirm the equalizer screen renders exactly 5 sliders.
|
||
10. Run `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart` in isolation — passes, file diff empty.
|
||
|
||
## Next Step
|
||
|
||
`sdd-apply` — implement WU1 first (unblocks everything else); honor the `size:exception` commits (WU3a, WU3b, WU10)
|
||
as recorded above rather than attempting to split them further.
|