Tier 4 visual fidelity, audit 1.11/1.12 (t4 line 80): a pill badge next
to "Tus emisoras" now shows the TOTAL favorite count (not the 8-capped
grid size), and "Ver todas" is plain 12px/w800 brand-teal text instead
of a Material TextButton with its own padding and splash.
Item 1.4 (hero art radius) was already fixed as a side effect of an
earlier commit — no change needed here.
Audit 1.10 (t4 lines 82-88): the prototype shows a 2x2+ grid of 8
favorite stations; the build was a 260px-wide horizontal strip capped
at 6.
Raises the cap to 8 and replaces the horizontal ListView with a
GridView.builder (2 columns, gap 10). Adds a dedicated _CeldaTusEmisoras
cell (44px square art radius 11, name + genre) rather than reusing
TarjetaEmisora(esCompacta: true), since that widget always renders a
favorite button and a live badge that this prototype cell never draws.
Audit 1.7/2.5 (t4 lines 66-68, 120-122): the prototype draws 30
discrete bottom-anchored bars (radius 2, vertical gradient); the
build's VisualizadorAudio painted a single continuous oscilloscope
stroke, so its `barras: 30` parameter never produced bars.
Adds an opt-in `barrasDiscretas` mode (default false, byte-identical
continuous rendering preserved for any other caller) plus a
`gradienteFinAlpha` knob for the two screens' differing gradient
end-alpha (.3 vs .45). Wires it into the Escuchar hero and the full
player, correcting the player's bar count/height to match the
prototype (26->30 bars, 46->40px) at the same time.
Audit 1.6 (t4 line 62): the Escuchar hero never rendered "genre ·
country · bitrate kbps" between the station name and the visualizer.
Built from fields Emisora already carries (tags/pais/bitrate) — no
new fields, no service calls — omitting gracefully whatever a station
lacks.
Audit 1.3: the hero art shipped at 84 where the prototype draws 132
(t4 line 56), with a 24 corner radius rather than the shared radiusMd.
Adds a dimension guard. Every existing test on this screen asserts
behaviour only, which is why an artwork 36% too small went unnoticed.
Restructures PantallaInicio's top of screen: a new _EscucharHero
(square art, live/offline pill, VisualizadorAudio at barras: 30 /
altura: 26 / color: liveGreen, a 5-action transport row - favorite,
EQ toggle, stop, play/pause, sleep - plus a tool-tray entry chip
opening the full player) replaces the old PluriScreenHeader hero, and
a new "Tus emisoras" section (favorites preview, capped, "Ver todas")
follows it. Per design ADR-7, EstadoRadio stays the single source of
truth: the hero is a StatelessWidget with no cached fields, reading
emisoraActual via context.select (uuid-based equality scopes rebuilds
to real station changes) and the fast-changing playback status via
StreamBuilder, the same pattern _Controles/MiniReproductor already
use. The still-present discovery sections (_seccionCercanas onward,
including the old grid) are deliberately left in place - WU6
relocates them to Buscar and deletes them from here; removing them
now would leave that content nowhere until WU6 lands.
MiniReproductor gains a `visible` parameter (default true) and a
measured `static const double altura`. app.dart passes
`visible: indice != RaizPluriWave.escuchar.index`, hiding it visually
only (SizedBox.shrink()) while Escuchar is active, since the hero
already shows the same station - the State stays mounted so its
didChangeDependencies side effect (configurarLocalizaciones, S3-R3)
keeps running regardless of tab. altura was measured empirically
(72.0, via tester.getSize) rather than guessed, backing a new derived
PluriLayout.escucharBottomChromeInset constant now wired into
PantallaInicio's own bottom padding.
"Ver todas" switches roots via EstadoNavegacionRaiz.irA(favoritos),
verified via a NavigatorObserver asserting the push count is
unchanged (switches tabs, does not push).
Fixed a pre-existing test-infrastructure gap while writing the
anti-cache test: no test in this codebase had ever exercised
ServicioAudio.androidAudioSessionIdStream against a bare
FakeServicioAudio (pantalla_reproductor.dart has always read it but
has no test file at all) - the real getter needs registrarHandler()
(main.dart, production only) and threw otherwise. Added an empty
stream override to FakeServicioAudio, matching VisualizadorAudio's
own documented no-native-session fallback.
Tests: 614 -> 618 (2 skipped, unchanged). flutter analyze unchanged
at 1 pre-existing info. git diff empty for visualizador_audio.dart
and estado_radio.dart - this WU touches neither.
- New EstadoGrabacion owns the recording service, subscription, directory/size preferences and open-file actions
- New EstadoBusqueda owns search, nearby stations, pagination and the min-bitrate filter
- New orden_emisoras.dart with the OrdenEmisoras enum, shared sorter and list identity memoization so context.select comparisons work on derived lists
- Large screens (inicio, buscar, favoritos, ajustes, reproductor) consume scoped selects/dedicated notifiers instead of root context.watch<EstadoRadio>, so audio buffer events no longer rebuild whole screens
- Remove all 15 TODO(S4b) compat members from EstadoRadio; consumers use the dedicated providers. EstadoRadio drops from ~1121 to 753 lines, keeping playback/stations/favorites orchestration
- 8 new tests including a rebuild-scoping probe (110 total green), flutter analyze clean
- Added state management for startup retry and custom station handling in `EstadoRadio`.
- Created tasks for implementing strict TDD with RED tests for HTTP failure retries and EQ persistence.
- Developed verification report to ensure compliance with TDD practices.
- Introduced fake services for testing, including `FakeServicioAudio`, `FakeServicioFavoritos`, and `FakeServicioRadio`.
- Implemented widget tests for `PantallaInicio` and `PantallaFavoritos` to validate UI behavior with custom stations.
- Enhanced `ServicioRadio` to support host rotation and retry logic for API calls.
- Established a new configuration file to enforce project constraints and testing rules.