Files
pluriwave/openspec/changes/rediseno-funcional/tasks.md
T
FreeTLab bd2b7d8e02 refactor(ajustes): split Settings AUDIO/EMISORAS into pushed detail screens
Moves the AUDIO group (Ecualizador, Salida de audio, Temporizador de
sueno) and the EMISORAS group (Grupos de favoritos, Emisora preferida,
Emisoras personalizadas, Orden de listas) out of pantalla_ajustes.dart
into 7 new lib/pantallas/ajustes/*.dart screens, each wrapped in
PluriPushScaffold. The root now reaches them through FilaAjuste rows
under two new GrupoAjustes cards (lib/pantallas/ajustes/widgets/
fila_ajuste.dart), per design ADR-3.

Verbatim-move rule applied throughout: only each section's panel header
(icon + title, sometimes a status chip) was removed, since the pushed
screen's own 56px header now carries the title. Two sections whose
header row carried a real action (Temporizador de sueno's "Add",
Grupos de favoritos' "Add list", Emisoras personalizadas' "Add") kept
that action in the body instead of dropping it.

size:exception (move-only diff, pre-recorded at design/tasks time):
34 files, ~4250 changed lines excluding the 13 auto-regenerated l10n
files (~90 more lines there) - higher than the 800-1000 estimate
because that estimate covered the 7 production screens but not the
matching 7 new test files (task 3a.2), one of which relocates ~10
pre-existing device-management test cases verbatim. Business logic is
untouched; app.dart's import of pantalla_ajustes.dart is unchanged.

Correction to tasks.md 3a.1/3a.8: those two lines describe the combined
WU3a+WU3b end state ("4 grouped nav lists", "<400 lines"), matching
design ADR-3's own aggregate blast-radius note - not a WU3a-only claim.
This commit converts only the 2 groups that are WU3a's job; the root
is 788 lines with 5 sections (Grabaciones, Musica local, Idioma,
Backup, Info) still inline, reachable, and unchanged, pending WU3b.

Two new ARB keys (settingsGroupAudioTitle, settingsGroupStationsTitle),
en/es only per the WU1 precedent - all 7 detail-screen titles reuse
existing keys. Discovered and worked around, without touching app
code: Directory.systemTemp hangs real dart:io writes in this sandbox,
and pumpAndSettle() cannot settle while a screen shows an indeterminate
CircularProgressIndicator - both are test-only concerns, documented
inline where hit.

Tests: 560 -> 579 (32 in this commit's scope, net +19 after retiring
13 relocated cases from the old combined pantalla_ajustes_test.dart).
flutter analyze: unchanged at 1 pre-existing info. git diff is empty
for navegacion_auto.dart, servicio_ecualizador.dart and
servicio_audio.dart; pantalla_alarma_sonando_dismiss_guard_test.dart
untouched.
2026-07-28 21:38:20 +02:00

51 KiB

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. 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 High Yes — move-only diff
3b refactor(ajustes): split remaining Settings sections into pushed screens 3a 500-700* 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 Medium Monitor
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

* Not an explicit design-stated figure — derived pro-rata from WU3a's verified 800-1000/7-sections ratio, scaled to WU3b's 5 sections (no new shared widget file needed there, since fila_ajuste.dart already exists from WU3a). † 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).

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

  • 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.
  • 1.2 RED — extend the tokens test asserting PluriWaveTokens.listSurface, .liveGreen, .offlineAccent exist and participate in lerp.
  • 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().
  • 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.
  • 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).
  • 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.
  • 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(...).
  • 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.
  • 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).
  • 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).
  • 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

  • 2.1 Run flutter test test/servicios/navegacion_auto_test.dart; confirm every case passes unmodified.
  • 2.2 Confirm git diff lib/servicios/navegacion_auto.dart is empty — no code change in this commit.
  • 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).
  • 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)

  • 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).
  • 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.
  • 3a.3 GREEN — create lib/pantallas/ajustes/widgets/fila_ajuste.dart (FilaAjuste / GrupoAjustes primitives).
  • 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.
  • 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.
  • 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.
  • 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.
  • 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)

  • 3b.1 RED — extend detail-screen tests for the remaining 5 sections, asserting PluriPushScaffold usage and preserved controls.
  • 3b.2 GREEN — cut _SeccionGrabaciones (92-291), _SeccionMusicaLocal (291-399) into pantalla_ajustes_grabaciones.dart, pantalla_ajustes_musica_local.dart.
  • 3b.3 GREEN — cut _SeccionIdioma + _IdiomaDisponible (492-694), _SeccionBackup (1669-1791), _SeccionInfo (1791-1896) into pantalla_ajustes_idioma.dart, pantalla_ajustes_backup.dart, pantalla_ajustes_info.dart.
  • 3b.4 GREEN — in the root, replace the remaining sections with FilaAjuste rows under two more GrupoAjustes cards (GRABACIONES Y MÚSICA, APLICACIÓN); confirm sleep timer and backup rows are present — corollary ruling, nothing dropped.
  • 3b.5 REFACTOR — confirm pantalla_ajustes.dart now contains only 4 GrupoAjustes cards and is under 400 lines; delete now-unused private section widgets.
  • 3b.6 Verify — full pantalla_ajustes_test.dart suite green; all 12 detail screens present and reachable; no service/state file touched.

size:exception — "move-only diff". ~500-700 changed lines (derived; see forecast table footnote). Same 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)

  • 15.1 RED — storage progress-bar fill reflects used/total (e.g. 84/200 MB fixture) with a caption stating both values.
  • 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.
  • 15.3 RED — the "⋮" menu exposes exactly Rename/Share/Delete — constrained to what servicio_grabacion_radio.dart already exposes, no extra action.
  • 15.4 RED — Delete (file + row removed), Rename (persists across reload), Share (invokes platform share sheet) scenarios.
  • 15.5 GREEN — add any missing listing method to servicio_grabacion_radio.dart, strictly limited to what it already conceptually supports.
  • 15.6 GREEN — build lib/pantallas/pantalla_grabaciones.dart (storage bar, per-recording rows with inline playback, "⋮" menu).
  • 15.7 REFACTOR — confirm the menu cannot expose a 4th action; confirm the screen degrades to an empty state, not a crash, on an empty folder.
  • 15.8 Verify — all 4 menu-action scenarios green; empty-folder scenario green.

WU16 — Connectivity states

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.