docs(sdd): add work-unit task breakdown for the functional redesign
This commit is contained in:
@@ -0,0 +1,613 @@
|
||||
# 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 `dart format .` gate every commit.
|
||||
> **`flutter build` is never run.**
|
||||
|
||||
## 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).
|
||||
|
||||
```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`
|
||||
|
||||
- [ ] 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 .`
|
||||
**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.
|
||||
- [ ] 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.
|
||||
- [ ] 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`.
|
||||
- [ ] 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 — root file line count < 400; `git diff` touches only screen files (no service/state file).
|
||||
|
||||
**`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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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 .`
|
||||
**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; `dart format .` produces no diff;
|
||||
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 .` — no diff.
|
||||
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.
|
||||
Reference in New Issue
Block a user