feat(bienvenida): add monetization-free welcome screen

Build the first-run welcome surface from mockup screen 14, stripped of
its entire monetization block: no PRO pill, no "14 dias PRO gratis"
trial line, no pricing card, no secondary "free version" link. Ships
only logo, headline, body copy, exactly 3 feature bullets, and the
single "Empezar a escuchar" CTA, as a full-screen route (not a modal).

Spanish copy is re-cast from the mockup's "tu" form to the app's
established voseo register (matching ~750 existing app_es.arb lines),
using "auto" instead of "coche" per the one existing precedent. The 3
bullet icons reuse existing tokens (electricMagenta/liveGreen/
warmCoral) that already match the mockup's own hex values for them.

CTA switches to the Escuchar tab and pops the route. Wiring this
screen into the real first-launch flow (main.dart/app.dart) is left
for a follow-up unit, same shape as WU15/WU15b - this WU only covers
the isolated, tested screen per its own task list and verify command.

WU17.
This commit is contained in:
2026-07-29 15:04:44 +02:00
parent 862197ab48
commit 9b415e73b0
19 changed files with 820 additions and 14 deletions
+43 -12
View File
@@ -56,7 +56,7 @@
| 15 | `feat(grabaciones): add recordings library screen` | 3b | ~~300-400~~**REALIZED: 1,767** (1,767+ / 0-, 22 files) | Medium | Monitor§ |
| 15b | `fix(grabaciones): wire the recordings library into Settings navigation` | 15 | 60-100 | Low | No |
| 16 | `feat(connectivity): restyle offline and reconnect banners` | 1 | 150-250 | Low | No |
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | 150-200 | Low | No |
| 17 | `feat(bienvenida): add monetization-free welcome screen` | 1 | ~~150-200~~**REALIZED: 361** (screen + test, 313 / ARB 22 / docs 48) — see the WU15-precedent note below | Low | No — same reasoning as WU15§ |
| 18 | `feat(i18n): add redesign strings and translate Escuchar rename to 11 locales` | all | ~0 eng. / 400-600 data | Medium (data volume, low logic risk) | No |
\* **Re-derived after WU3a landed.** The original 500-700 figure was scaled pro-rata from WU3a's *estimate*, which
@@ -85,6 +85,11 @@ unlike WU3a/WU3b's move-only reuse), or (c) the new `servicio_grabacion_radio.da
group. Realized 1,767 changed lines / 22 files, all additions (no deletions — nothing pre-existing was touched
beyond the 3 new `ServicioGrabacionRadio`/`EstadoGrabacion` methods). Not recorded as `size:exception` since the
commit is still a single, cleanly-scoped deliverable (one new screen, its one dependency, no split candidate).
**WU17 hit the identical pattern**: 150-200 covered only the screen itself; realized 361 lines across the screen
(169) + its test (144) + 9 new ARB keys × 2 locales (22) + this docs update (48), PLUS 13 regenerated
`lib/l10n/gen/*.dart` files (~430 more lines, untouched by hand) for a raw `git diff --stat` total of 814. Same
ruling: not `size:exception` — one new, cleanly-scoped screen with no split candidate, all-additions except the ARB
files (which gain lines, not churn).
```text
Decision needed before apply: No
@@ -1013,19 +1018,45 @@ real capability living in the header, not decorative chrome).
**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
- [x] 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. **Discovered at apply time**: a pre-existing,
UNRELATED `PluriOnboardingDialog` (a "what's new"/help-content modal loaded from markdown assets, shown from
`app.dart`'s launch flow) already exists — it is NOT the mockup's welcome screen and is not touched or
replaced by this WU.
- [x] 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.
- [x] 17.3 RED — tapping "Empezar a escuchar" lands on the Escuchar tab (index 0) with the welcome route removed
from the back stack. Test starts `EstadoNavegacionRaiz` on a DIFFERENT tab first so a pass actually proves
`irA()` ran, not just that escuchar was already the default.
- [x] 17.4 GREEN — built `lib/pantallas/pantalla_bienvenida.dart` as a full-screen route (bare `Scaffold`, no
`PluriPushScaffold` — this is an entry screen with no back affordance, not a second-level pushed screen);
content only (logo / headline / body / 3 bullets / 1 CTA) — no PRO pill, no trial duration, no price, no
secondary link. Content is the mockup's screen 14 copy verbatim MINUS its entire monetization block (the "14
días PRO gratis / luego 2,99 €/año" pricing card and the "Seguir con la versión gratuita" link), Spanish
re-cast to the app's established voseo register (the raw mockup HTML uses "tú" form; the other ~750 lines of
`app_es.arb` consistently use voseo — e.g. "Elegí", "Guardá", "querés" — matched that instead of copying the
mockup's grammar verbatim) and "auto" instead of the mockup's "coche" (matching the one existing precedent at
`localMusicSectionDescription`). The 3 bullet icon colours reuse existing named tokens
(`electricMagenta`/`liveGreen`/`warmCoral`) that happen to be the exact hex values the mockup already used for
those 3 icons — no new raw `Color(0x...)` literal introduced. `SingleChildScrollView` used instead of a
`Column` + `Spacer()` (the exact overflow hazard WU14 already hit and fixed) since this screen carries
meaningfully more content than a typical card. 9 new ARB keys, en/es only (`welcomeHeadline`, `welcomeBody`,
`welcomeBullet{1,2,3}{Title,Subtitle}`, `welcomeCtaLabel`).
- [x] 17.5 GREEN — CTA wired to `context.read<EstadoNavegacionRaiz>().irA(RaizPluriWave.escuchar)` then
`Navigator.of(context).pop()`. **Scope note**: this WU's own verify command and task list only cover
`pantalla_bienvenida_test.dart` — wiring the screen into the REAL first-launch flow (i.e. `app.dart`/
`main.dart` deciding *when* to push it, analogous to `ServicioContenidoApp.debeMostrarInicio()`'s pattern for
`PluriOnboardingDialog`) is NOT part of WU17 as scoped and was deliberately not added here to avoid inventing
untested persistence/launch semantics. Flagged as a likely WU17b gap (same shape as WU15/WU15b).
- [x] 17.6 REFACTOR — whole-repo grep confirmed clean: `\bPRO\b` (word-boundary) has exactly one hit in all of
`lib/` — this WU's own doc comment describing the guard (never rendered). The unqualified substring "PRO"
also matches pre-existing, unrelated `"PROGRAMADOS"` (vacation-alarms section title, WU9) and
`pluri_premium_widgets.dart` (a pre-existing decorative-hero-card widget file name, "premium" as in visual
styling, not a paid tier) — both false positives for a naive substring check, neither a monetization string.
No day-count trial phrase, no €/$ amount, no "gratuita"/"gratis"/"free version" string anywhere in
`lib/l10n/app_*.arb` or `lib/`.
- [x] 17.7 Verify — all 3 scenario tests green; grep scan clean.
## WU18 — i18n batch (all 13 locales)