diff --git a/openspec/changes/rediseno-funcional/specs/alarm-vacation-ranges/spec.md b/openspec/changes/rediseno-funcional/specs/alarm-vacation-ranges/spec.md
new file mode 100644
index 0000000..f8c0f14
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/alarm-vacation-ranges/spec.md
@@ -0,0 +1,75 @@
+# Alarm Vacation Ranges Specification
+
+## Purpose
+
+Active vacation-range detection, per-alarm pause-impact computation,
+upcoming ranges, past-ranges history, and the summary-row entry point from
+the Alarmas root into a dedicated Vacaciones manager screen. Traceable to
+WU8 (summary row), WU9 (manager screen + `EstadoAlarmas` queries).
+
+## Requirements
+
+### Requirement: Active Range Detection
+
+`EstadoAlarmas` MUST expose a query returning the currently-active vacation
+range (if today falls within one), including days remaining. It MUST
+return no active range when today falls within none.
+
+#### Scenario: Today falls within a range
+
+- GIVEN a vacation range whose start-to-end span includes today
+- WHEN the active-range query runs
+- THEN it returns that range with a computed days-remaining count
+
+#### Scenario: No active range
+
+- GIVEN no configured range includes today
+- WHEN the active-range query runs
+- THEN it returns no active range
+
+### Requirement: Per-Alarm Pause-Impact Computation
+
+For the currently-active range, the system MUST compute which alarms are
+paused (`sonarEnVacaciones == false`) and which are unaffected
+(`sonarEnVacaciones == true`), producing data sufficient to render an
+impact line (e.g. "Pausa 07:30 y 13:45 · 08:15 sigue").
+
+#### Scenario: Mixed paused and unaffected alarms
+
+- GIVEN 3 alarms exist, 2 with `sonarEnVacaciones == false` and 1 with `true`, during an active range
+- WHEN the impact computation runs
+- THEN it lists exactly those 2 as paused and exactly that 1 as unaffected
+
+### Requirement: Upcoming Ranges Query
+
+`EstadoAlarmas` MUST expose a query returning ranges that have not yet
+started, ordered soonest-first.
+
+#### Scenario: Two future ranges
+
+- GIVEN 2 ranges with future start dates
+- WHEN the upcoming-ranges query runs
+- THEN both are returned, ordered by soonest start date first
+
+### Requirement: Past-Ranges History Query
+
+`EstadoAlarmas` MUST expose a query returning ranges whose end date has
+already passed, distinct from the active and upcoming queries.
+
+#### Scenario: A range that ended yesterday
+
+- GIVEN a range whose end date was yesterday
+- WHEN the past-ranges query runs
+- THEN that range appears in the past-ranges result and in neither the active nor upcoming result
+
+### Requirement: Vacation Summary Row Replaces the Inline Panel
+
+The Alarmas root MUST show a summary row (range count and next-range
+countdown) in place of the current full inline vacation-ranges list.
+Tapping the row MUST push the dedicated Vacaciones manager screen.
+
+#### Scenario: Tapping the summary row opens the manager screen
+
+- GIVEN the Alarmas root shows the vacation summary row
+- WHEN the user taps it
+- THEN the Vacaciones manager screen opens as a second-level (push-chrome) screen with no tab bar
diff --git a/openspec/changes/rediseno-funcional/specs/app-navigation-shell/spec.md b/openspec/changes/rediseno-funcional/specs/app-navigation-shell/spec.md
new file mode 100644
index 0000000..a9ba4b7
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/app-navigation-shell/spec.md
@@ -0,0 +1,89 @@
+# App Navigation Shell Specification
+
+## Purpose
+
+The 5-root tab contract, the push-chrome rule for second-level screens, the
+explicit Favoritos exemption from that rule, and root-to-root switching
+(Escuchar "Ver todas" to Favoritos). Traceable to WU1.
+
+## Requirements
+
+### Requirement: Five-Tab Root Navigation
+
+The app MUST render exactly 5 root tabs, in order: Escuchar, Buscar,
+Favoritos, Alarmas, Ajustes, using the existing `PluriIconGlyph.*` icon set.
+`PluriIconGlyph.home` MUST be the Escuchar tab's icon.
+
+#### Scenario: App launch renders the 5 tabs in order
+
+- GIVEN the app has finished cold-start
+- WHEN the bottom navigation renders
+- THEN exactly 5 tabs appear, labeled Escuchar, Buscar, Favoritos, Alarmas, Ajustes, in that order
+
+#### Scenario: Escuchar uses the home glyph
+
+- GIVEN the bottom navigation is rendered
+- WHEN the Escuchar tab's icon is inspected
+- THEN it is `PluriIconGlyph.home`
+
+### Requirement: Push-Chrome on Second-Level Screens
+
+Every genuine second-level screen (Reproductor completo, Países, Vacaciones,
+Ecualizador, Grabaciones, and every new Settings detail screen) MUST render
+a 56px header with a back affordance and MUST NOT render a bottom tab bar.
+
+#### Scenario: Reproductor completo hides the tab bar
+
+- GIVEN the user opens Reproductor completo from Escuchar
+- WHEN the screen renders
+- THEN it shows a 56px header with a back arrow and no bottom tab bar
+
+#### Scenario: A Settings detail screen hides the tab bar
+
+- GIVEN the user pushes any Settings detail screen from the Ajustes root
+- WHEN the screen renders
+- THEN it shows a 56px header with a back arrow and no bottom tab bar
+
+### Requirement: Favoritos Tab-Bar Exemption
+
+Favoritos MUST keep its bottom tab bar. This is the one deliberate exception
+to the push-chrome rule: the source mockup draws this content as a pushed
+screen only because its own 4-root IA had demoted Favoritos out of the root
+set. In this app's 5-tab IA, Favoritos is root-level and MUST render the tab
+bar like every other root.
+
+#### Scenario: Favoritos renders with its tab bar
+
+- GIVEN the user is on the Favoritos tab
+- WHEN the screen renders
+- THEN the bottom tab bar is visible, unlike every true second-level screen
+
+### Requirement: Root-to-Root Switching Without Push
+
+Navigating from Escuchar's "Tus emisoras" section to the full favorites list
+MUST switch the active tab index to Favoritos. It MUST NOT push a new route.
+
+#### Scenario: "Ver todas" switches tabs, not routes
+
+- GIVEN the user is on Escuchar with 1 or more favorite stations
+- WHEN they tap "Ver todas"
+- THEN the active tab index becomes Favoritos
+- AND no new route is pushed onto the navigation stack (back from Favoritos returns to whatever screen preceded Escuchar, not to Escuchar itself)
+
+### Requirement: Escuchar Tab Rename Preserves the ARB Key
+
+The former "Inicio" tab MUST be relabeled "Escuchar" (English: "Listen") by
+changing the `navHome` key's VALUE, not by introducing a new key. The key
+`navHome` MUST continue to exist, unchanged, in all 13 ARB locales.
+
+#### Scenario: English and Spanish values are updated
+
+- GIVEN `lib/l10n/app_en.arb` and `lib/l10n/app_es.arb`
+- WHEN the `navHome` key is read
+- THEN `app_en.arb` returns `"Listen"` and `app_es.arb` returns `"Escuchar"`
+
+#### Scenario: Key parity across all 13 locales (edge case)
+
+- GIVEN all 13 ARB files
+- WHEN each is checked for the `navHome` key
+- THEN every file still contains the key `navHome` (value content may differ), and no file is missing it
diff --git a/openspec/changes/rediseno-funcional/specs/eq-custom-presets/spec.md b/openspec/changes/rediseno-funcional/specs/eq-custom-presets/spec.md
new file mode 100644
index 0000000..886977a
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/eq-custom-presets/spec.md
@@ -0,0 +1,104 @@
+# EQ Custom Presets Specification
+
+## Purpose
+
+User-defined preset save/name/list on top of the existing 6 fixed presets,
+a per-station EQ entry point from the player's tool tray, and a
+"stations with their own EQ" drill-down. **Band count stays 5** — resolved
+by spike (Engram id 2498), not a pending decision. Traceable to WU13
+(Settings screen) and WU14 (player tool tray, shares WU13's editor
+component).
+
+## Requirements
+
+### Requirement: Five-Band Equalizer (Regression Guard, First-Class)
+
+The equalizer screen MUST render exactly **5** vertical sliders.
+`assert(bandas.length == 5)` in `preset_ecualizador.dart` MUST remain
+unchanged. Band count is device-reported via `just_audio`'s
+`AndroidEqualizer` (`params.bands`), not app-chosen; any requirement or
+implementation asserting 7 bands is rejected on sight.
+
+#### Scenario: Ecualizador screen renders 5 sliders
+
+- GIVEN the user opens the Ecualizador settings screen
+- WHEN it renders
+- THEN exactly 5 vertical sliders are shown, one per band
+
+#### Scenario: Band-count regression is rejected (edge case)
+
+- GIVEN a future change proposes 7 sliders or a modified `bandas.length` assert
+- WHEN that change is reviewed against this requirement
+- THEN it fails: 5 is the resolved, binding band count for this change
+
+### Requirement: Custom Preset Save
+
+The user MUST be able to save the current 5-band configuration as a new,
+named custom preset, which then appears alongside the 6 fixed presets in
+the preset chip row.
+
+#### Scenario: Saving a custom preset
+
+- GIVEN the user has adjusted the 5 sliders from a fixed preset
+- WHEN they choose "Guardar como preset" and enter the name "Mi preset"
+- THEN a new preset named "Mi preset" is persisted
+- AND it appears in the preset chip row on next render
+
+### Requirement: Custom Preset Naming Validates Non-Empty Input
+
+The system MUST reject an empty or whitespace-only preset name without
+crashing, and MUST NOT persist a preset in that case.
+
+#### Scenario: Empty name is rejected
+
+- GIVEN the "Guardar como preset" flow is open
+- WHEN the user submits an empty name
+- THEN a validation message is shown and no preset is saved
+
+### Requirement: Per-Station EQ Entry Point From the Player
+
+The player's tool tray MUST expose an "EQ propio" tile that opens a
+per-station EQ bottom sheet. That sheet MUST reuse the same 5-band editor
+component used by the Settings Ecualizador screen (not a separate
+implementation).
+
+#### Scenario: Opening per-station EQ from the player
+
+- GIVEN a station is currently playing
+- WHEN the user taps "EQ propio" in the tool tray
+- THEN a bottom sheet opens with 5 sliders bound to that station's resolved preset
+- AND changes made there apply through the existing per-station EQ persistence path
+
+### Requirement: Stations-With-Own-EQ Drill-Down
+
+The Settings Ecualizador screen MUST expose a row that navigates to a list
+of stations that currently have a station-specific preset override,
+sourced from the existing `presetsPorEmisora` map.
+
+#### Scenario: Drill-down lists exactly the overridden stations
+
+- GIVEN 2 stations have entries in `presetsPorEmisora` and N others do not
+- WHEN the user opens the drill-down
+- THEN exactly those 2 stations are listed
+
+### Requirement: Base-vs-Per-Station Explainer Preserved
+
+An info banner distinguishing the base (global/device) EQ from per-station
+overrides MUST remain visible on the Ecualizador screen.
+
+#### Scenario: Banner is visible on screen open
+
+- GIVEN the user opens the Ecualizador screen
+- WHEN it renders
+- THEN the base-vs-per-station explainer banner is visible
+
+### Requirement: Active Output Surfaced on the Main Screen
+
+The "Salida activa" (active output device) row MUST be visible on the main
+Ecualizador screen, not only in the Advanced sub-section.
+
+#### Scenario: Output row updates on device change
+
+- GIVEN the Ecualizador screen is open
+- WHEN the active audio output device changes (e.g. Bluetooth connects)
+- THEN the "Salida activa" row updates to reflect the new device
diff --git a/openspec/changes/rediseno-funcional/specs/favorites-organization/spec.md b/openspec/changes/rediseno-funcional/specs/favorites-organization/spec.md
new file mode 100644
index 0000000..6adc44b
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/favorites-organization/spec.md
@@ -0,0 +1,75 @@
+# Favorites Organization Specification
+
+## Purpose
+
+Chip-filtered flat list replacing stacked per-group panels, drag-to-reorder,
+the sort action, and group management surfaced from Favoritos. Traceable to
+WU4.
+
+## Requirements
+
+### Requirement: Chip-Filtered Flat List
+
+Favoritos MUST render horizontally-scrollable group filter chips (e.g.
+"Todas · N", one per favorite group) and a single flat list of favorite
+stations filtered by the active chip, replacing the current stacked
+per-group panel layout.
+
+#### Scenario: Selecting a group chip filters the list
+
+- GIVEN 2 or more favorite groups exist, each with members
+- WHEN the user selects a specific group's chip
+- THEN the flat list shows only that group's member stations
+
+#### Scenario: "Todas" chip shows every favorite
+
+- GIVEN the user has favorites across multiple groups
+- WHEN the "Todas" chip is active
+- THEN the flat list shows every favorite station regardless of group
+
+### Requirement: Drag-to-Reorder Within the Active Filter
+
+The user MUST be able to reorder favorite stations within the currently
+active filter via a drag handle. The new order MUST persist across app
+restarts.
+
+#### Scenario: Reordering persists
+
+- GIVEN a filtered list of 3+ favorites
+- WHEN the user drags the 3rd item to the 1st position
+- THEN the list reflects the new order immediately
+- AND reopening the app shows the same order
+
+### Requirement: Sort Action Using Existing Criteria
+
+Favoritos MUST expose a sort action (`swap_vert`) using the existing
+`OrdenEmisoras` criteria (name, quality). It MUST NOT introduce a sort
+criterion with no backing implementation.
+
+#### Scenario: Sort by name reorders alphabetically
+
+- GIVEN the active filter shows 3+ favorites in a non-alphabetical order
+- WHEN the user triggers sort-by-name
+- THEN the list re-renders in alphabetical order
+
+### Requirement: Group Management Reachable from Favoritos
+
+Creating and managing favorite groups MUST be reachable directly from the
+Favoritos screen, in addition to its existing entry point in Settings.
+
+#### Scenario: Creating a group from Favoritos
+
+- GIVEN the user is on the Favoritos screen
+- WHEN they tap the create-group action
+- THEN the group-creation flow opens and a newly created group appears as a new filter chip
+
+### Requirement: Custom-Station CTA Preserved
+
+The dashed "Añadir emisora personalizada" call-to-action MUST remain
+reachable from Favoritos.
+
+#### Scenario: Custom station CTA opens the add flow
+
+- GIVEN the user is on Favoritos, any filter active
+- WHEN they tap "Añadir emisora personalizada"
+- THEN the custom-station add flow opens
diff --git a/openspec/changes/rediseno-funcional/specs/multi-device-eq/spec.md b/openspec/changes/rediseno-funcional/specs/multi-device-eq/spec.md
new file mode 100644
index 0000000..fa8c6dd
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/multi-device-eq/spec.md
@@ -0,0 +1,31 @@
+# Delta for multi-device-eq
+
+Existing spec: `openspec/specs/multi-device-eq/spec.md` (device-event
+resolve-and-apply, first-seen bootstrap, cold start, connect-disconnect-
+reconnect cycle, toggle-off behavior). None of that resolution logic
+changes. This delta only ADDS a requirement about where the per-station
+editing surface is reached from, and pins that the relocation has zero
+effect on resolution. Traceable to WU14 (shares WU13's editor component).
+
+## ADDED Requirements
+
+### Requirement: Per-Station EQ Entry Relocates, Resolution Logic Does Not
+
+The per-station EQ editing surface MUST be reachable from the player's
+tool tray (see `eq-custom-presets`). This relocation MUST NOT alter the
+existing 4-level resolution hierarchy (matrix -> station -> device ->
+global) or any of its resolve-and-apply, first-seen-bootstrap, cold-start,
+or connect/disconnect/reconnect behavior.
+
+#### Scenario: Editing per-station EQ from the player uses the existing hierarchy
+
+- GIVEN a station has an existing entry in `presetsMatriz` or `presetsPorEmisora`
+- WHEN the user edits it via the player's per-station EQ sheet
+- THEN the change persists through the same resolution path already covered by `multi-device-eq`'s existing tests
+- AND device-change resolve-and-apply behavior for that station is unaffected
+
+#### Scenario: Existing multi-device-eq test suite is unaffected (regression guard)
+
+- GIVEN the existing `multi-device-eq` scenarios (device-event resolve-and-apply, first-seen bootstrap, cold start, connect-disconnect-reconnect, toggle-off)
+- WHEN the per-station entry point relocates to the player
+- THEN every one of those scenarios continues to pass unmodified
diff --git a/openspec/changes/rediseno-funcional/specs/native-alarms/spec.md b/openspec/changes/rediseno-funcional/specs/native-alarms/spec.md
new file mode 100644
index 0000000..80fd3a5
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/native-alarms/spec.md
@@ -0,0 +1,93 @@
+# Delta for native-alarms
+
+Existing spec: `openspec/specs/native-alarms/spec.md` (native foreground
+service, sole ring-audio ownership, fade-in curve, focus/no-volume-writes,
+notification channel migration, "ring screen is pure UI"). None of that
+native/audio behavior changes in this redesign — it is a visual restyle of
+the Alarmas root, the alarm card, and the ringing screen. This delta ADDS
+requirements for card affordances, the hero skip action, the editor's
+preserved advanced fields, and the ringing screen's static status label.
+Traceable to WU8 (card + hero), WU10 (editor), WU11 (ringing screen).
+
+## ADDED Requirements
+
+### Requirement: Alarm Card Tap-to-Edit, Swipe-to-Delete
+
+The simplified alarm card (giant time + station + switch, no always-visible
+action row) MUST preserve edit and delete capability: tapping the card
+MUST open the editor for that alarm; swiping it MUST trigger delete
+(with confirmation). No capability present in the current always-visible
+button row may be lost.
+
+#### Scenario: Tap opens the editor
+
+- GIVEN an alarm card is visible on the Alarmas root
+- WHEN the user taps it
+- THEN the editor sheet opens pre-filled with that alarm's data
+
+#### Scenario: Swipe deletes with confirmation
+
+- GIVEN an alarm card is visible
+- WHEN the user swipes it and confirms
+- THEN that alarm is deleted and its card is removed from the list
+
+### Requirement: Hero Banner Inline Skip
+
+The Alarmas root's hero banner MUST include an inline "Saltar" action that
+skips the next occurrence of the featured (soonest-firing) alarm, in
+addition to any per-card skip action.
+
+#### Scenario: Tapping "Saltar" on the hero banner skips the featured alarm
+
+- GIVEN the hero banner shows the soonest-firing alarm
+- WHEN the user taps its inline "Saltar" action
+- THEN that alarm's next occurrence is marked skipped, consistent with the existing skip-next behavior
+
+### Requirement: Alarm Editor Preserves Date, Fallback Station, and Sound Fields
+
+The rewritten editor (giant inline HH:MM display, always-visible weekday
+circles) MUST NOT drop the existing one-time date field, the
+fallback-station picker, or the sound dropdown. These MUST remain
+reachable, e.g. under a collapsed "Advanced" section.
+
+#### Scenario: One-time date alarm is still creatable
+
+- GIVEN the user is creating a new alarm
+- WHEN they choose a specific one-time date instead of a weekly recurrence
+- THEN the date field is reachable and the saved alarm reflects that specific date, not a weekday pattern
+
+#### Scenario: Fallback station and sound remain settable
+
+- GIVEN the editor's Advanced section
+- WHEN the user sets a fallback station and a sound choice
+- THEN both persist and are reflected the next time the alarm is edited
+
+### Requirement: Ringing Screen Shows a Static Status Label (No Live Countdown)
+
+The ringing screen's "Subiendo volumen" status MUST render as a static
+label with no live seconds counter. Adding a live counter is out of scope:
+it would require native-to-Flutter progress plumbing that is deliberately
+absent from the architecture.
+
+#### Scenario: Status label has no numeric countdown
+
+- GIVEN an alarm is ringing and fading in
+- WHEN the status label renders
+- THEN it shows "Subiendo volumen" (or equivalent localized text) with no accompanying seconds value that changes over time
+
+### Requirement: Dismiss Guard and Force-Stop Banner Survive the Restyle (First-Class Regression Guard)
+
+The ringing screen's single-exit dismiss guard and force-stop retry banner
+MUST be preserved unmodified by the visual restyle.
+
+#### Scenario: Dismiss guard test passes unmodified
+
+- GIVEN `test/pantallas/pantalla_alarma_sonando_dismiss_guard_test.dart`
+- WHEN the ringing screen restyle lands
+- THEN that test file passes without being modified
+
+#### Scenario: Force-stop retry banner still appears on failure
+
+- GIVEN a stop action fails to actually silence the ring
+- WHEN the failure is detected
+- THEN the force-stop retry banner is shown, in whatever position the restyled layout places it
diff --git a/openspec/changes/rediseno-funcional/specs/onboarding-welcome/spec.md b/openspec/changes/rediseno-funcional/specs/onboarding-welcome/spec.md
new file mode 100644
index 0000000..e396c39
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/onboarding-welcome/spec.md
@@ -0,0 +1,47 @@
+# Onboarding Welcome Specification
+
+## Purpose
+
+A first-run welcome surface in the new visual language, explicitly
+monetization-free. Traceable to WU17.
+
+## Requirements
+
+### Requirement: Full-Screen Welcome Route
+
+The welcome screen MUST render as a full-screen route (not a modal
+dialog), containing: logo mark, headline, body copy, exactly 3 feature
+bullets, and exactly one primary call-to-action ("Empezar a escuchar").
+
+#### Scenario: All elements render, one CTA
+
+- GIVEN the app launches for the first time
+- WHEN the welcome screen renders
+- THEN a logo, headline, body copy, and 3 feature bullets are visible
+- AND exactly 1 primary CTA is present
+
+### Requirement: No Monetization Content (First-Class, Binding)
+
+The welcome screen MUST NOT render a PRO pill, any trial-duration text
+(e.g. a day count), any price, or a secondary "free version" link. This
+follows the binding no-monetization decision for this change.
+
+#### Scenario: Rendered content contains no monetization strings
+
+- GIVEN the welcome screen is rendered
+- WHEN its widget tree's text content is scanned
+- THEN it contains none of: the substring "PRO", a currency amount, or a day-count trial phrase
+- AND no secondary "free version" link is present
+
+### Requirement: CTA Dismisses to Escuchar
+
+Tapping the primary CTA MUST dismiss the welcome screen and land on the
+Escuchar tab (index 0), with the welcome route removed from the
+navigation stack.
+
+#### Scenario: CTA navigates to Escuchar
+
+- GIVEN the welcome screen is showing
+- WHEN the user taps "Empezar a escuchar"
+- THEN the app shows the Escuchar tab
+- AND the welcome route is no longer on the back stack
diff --git a/openspec/changes/rediseno-funcional/specs/recordings-library/spec.md b/openspec/changes/rediseno-funcional/specs/recordings-library/spec.md
new file mode 100644
index 0000000..091a423
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/recordings-library/spec.md
@@ -0,0 +1,76 @@
+# Recordings Library Specification
+
+## Purpose
+
+A browsable recordings list with storage usage and per-recording
+rename/share/delete, constrained to what `servicio_grabacion_radio.dart`
+already exposes. Traceable to WU15.
+
+## Requirements
+
+### Requirement: Storage Usage Display
+
+The screen MUST show used vs. total storage as a progress bar with a
+caption (used amount, total amount, retention/path info).
+
+#### Scenario: Storage usage renders proportionally
+
+- GIVEN recordings occupy 84 MB of a 200 MB budget
+- WHEN the screen renders
+- THEN the progress bar fill reflects 84/200 and the caption states both values
+
+### Requirement: Browsable Recordings List
+
+The screen MUST list individual recordings sourced from the recordings
+folder, each row showing name, date, duration, and size, with inline
+playback.
+
+#### Scenario: Recordings on disk render as rows
+
+- GIVEN 3 recording files exist in the recordings folder
+- WHEN the screen loads
+- THEN exactly 3 rows render, each with correct name/date/duration/size
+
+#### Scenario: No recordings (edge case)
+
+- GIVEN the recordings folder is empty
+- WHEN the screen loads
+- THEN an empty state renders, not an error
+
+#### Scenario: Row playback starts and stops
+
+- GIVEN a recording row is visible
+- WHEN the user taps its play control
+- THEN playback of that recording starts
+- AND tapping again stops it
+
+### Requirement: Per-Recording Menu Constrained to Rename/Share/Delete
+
+The "⋮" menu on each recording row MUST expose exactly three actions —
+Rename, Share, Delete — constrained to capabilities already exposed by
+`servicio_grabacion_radio.dart`. It MUST NOT expose an action that service
+does not support.
+
+#### Scenario: Menu shows exactly 3 actions
+
+- GIVEN any recording row
+- WHEN its "⋮" menu opens
+- THEN exactly Rename, Share, and Delete are shown, no other action
+
+#### Scenario: Delete removes the file and its row
+
+- GIVEN a recording row's menu is open
+- WHEN the user selects Delete and confirms
+- THEN the underlying file is removed and the row disappears from the list
+
+#### Scenario: Rename updates the displayed name
+
+- GIVEN a recording row's menu is open
+- WHEN the user selects Rename and submits a new, non-empty name
+- THEN the row displays the new name and it persists across a reload
+
+#### Scenario: Share invokes the platform share sheet
+
+- GIVEN a recording row's menu is open
+- WHEN the user selects Share
+- THEN the platform share sheet opens with that recording's file
diff --git a/openspec/changes/rediseno-funcional/specs/station-discovery-browse/spec.md b/openspec/changes/rediseno-funcional/specs/station-discovery-browse/spec.md
new file mode 100644
index 0000000..f2beab9
--- /dev/null
+++ b/openspec/changes/rediseno-funcional/specs/station-discovery-browse/spec.md
@@ -0,0 +1,139 @@
+# Station Discovery & Browse Specification
+
+## Purpose
+
+Discovery content as the Buscar tab's landing state, active-filter pills,
+results counter, clear-all-filters, the Países browser over the verified
+`/json/countries` contract, and a client-side search-sort control. Traceable
+to WU5 (relocation), WU6 (Buscar), WU7 (Países + `_get` transport
+extraction).
+
+## Requirements
+
+### Requirement: Buscar Landing State Shows Discovery Content
+
+Buscar MUST render the existing discovery content (near-you, genres,
+trending, countries entry point) as its landing state whenever no search
+query is entered. Entering a query MUST replace that landing state with
+search results.
+
+#### Scenario: Empty query shows discovery content
+
+- GIVEN the user opens the Buscar tab with no prior query
+- WHEN the screen renders
+- THEN near-you, genre, trending, and a Países entry point are visible
+
+#### Scenario: Entering a query replaces the landing state
+
+- GIVEN the Buscar tab is showing discovery content
+- WHEN the user types a non-empty query
+- THEN discovery content is replaced by the search-results view
+
+### Requirement: Active-Filter Pills and Results Counter
+
+The search-results view MUST show each active filter (country, language,
+minimum quality) as a removable pill, and MUST show a results counter
+("N RESULTADOS").
+
+#### Scenario: Applying a filter shows a removable pill
+
+- GIVEN the user has an active search
+- WHEN they apply a country filter
+- THEN a pill labeled with that country and a close (x) affordance appears
+- AND the results counter reflects the filtered count
+
+#### Scenario: Removing a pill re-runs the search without that filter
+
+- GIVEN a country filter pill is active
+- WHEN the user taps its close affordance
+- THEN the filter is cleared and results/count update accordingly
+
+### Requirement: One-Tap Clear-All-Filters on Empty Results
+
+When 1 or more filters are active and the resulting search has zero results,
+the system MUST offer a single action that clears every active filter at
+once ("Quitar los N filtros").
+
+#### Scenario: Two active filters, zero results
+
+- GIVEN country and quality filters are both active and yield 0 results
+- WHEN the empty state renders
+- THEN a "Quitar los 2 filtros" action is shown
+- AND tapping it clears both filters in one action, not one at a time
+
+### Requirement: Países Browser Over the Verified Countries Contract
+
+The Países screen (new second-level screen) MUST fetch from
+**`/json/countries`** (optional substring filter `/json/countries/{filter}`)
+and MUST NOT use `/json/countrycodes`, which is deprecated. It MUST parse
+the `stationcount` field as a **string** via `int.tryParse`, never as a
+direct `int` cast. Its request MUST NOT include `lastcheckok`, since that
+parameter is meaningful only for station-listing calls.
+Source: Engram `reference/radio-browser-countries-endpoint` (id 2500),
+verified against .
+
+#### Scenario: Countries list renders with counts
+
+- GIVEN `/json/countries` returns a list of country objects
+- WHEN the Países screen loads
+- THEN "Tus idiomas" and the full alphabetical list render with each country's parsed station count
+
+#### Scenario: `stationcount` arrives as a JSON string (edge case, critical)
+
+- GIVEN a country entry has `"stationcount": "482"` (a JSON string, not a number)
+- WHEN the screen parses and renders that entry
+- THEN it displays `482` without throwing a type-cast error
+
+#### Scenario: Countries request omits the station-only filter
+
+- GIVEN the Países screen issues its `/json/countries` request
+- WHEN the outgoing request is inspected
+- THEN it does NOT include `lastcheckok`
+
+### Requirement: Client-Side Search Sort Only
+
+Buscar MUST expose an "Ordenar" control backed exclusively by client-side
+ordering (`OrdenEmisoras`). The system MUST NOT adopt the Radio Browser
+server-side `order`/`reverse` parameters for this control, because
+server-side ordering sorts the full result set before `limit` is applied
+while the app's client-side sort only reorders the page already fetched —
+adopting it would silently change which stations users see, not just their
+order. Every sort option rendered in the UI MUST map to a real
+`OrdenEmisoras` case; the system MUST NOT render an option that does not
+actually sort.
+Source: Engram `reference/radio-browser-sort-order` (id 2505), verified
+against .
+
+#### Scenario: Selecting a sort option reorders the current page only
+
+- GIVEN a set of search results is already fetched
+- WHEN the user selects "Ordenar: calidad"
+- THEN the currently-displayed page is reordered via `OrdenEmisoras.calidad`
+- AND no new network request with an `order` parameter is issued
+
+#### Scenario: Every rendered sort option is backed by a real case (regression guard)
+
+- GIVEN the "Ordenar" control's rendered option list
+- WHEN each option is checked against `OrdenEmisoras`
+- THEN every option maps to an existing enum case with test coverage; no decorative option that does not sort is rendered
+
+### Requirement: Existing Station Calls Unchanged by Transport Extraction
+
+Extracting `_get`'s transport loop (server discovery, host rotation,
+bounded retries, User-Agent, timeout, status check, `json.decode`) into a
+shared helper for the Países call MUST NOT change the observable behavior
+of the 8 existing `ServicioRadio` station methods: `obtenerPopulares`,
+`obtenerTendencias`, `buscarPorNombre`, `buscarPorPais`, `buscarPorIdioma`,
+`buscarPorTag`, `buscar`, `registrarClick`.
+
+#### Scenario: Characterization tests pass unchanged before and after extraction
+
+- GIVEN characterization tests exist for all 8 station methods
+- WHEN the transport loop is extracted from `_get`
+- THEN all 8 tests pass identically before and after the extraction
+
+#### Scenario: Station calls still send `lastcheckok`
+
+- GIVEN any of the 8 station methods issues a request
+- WHEN the outgoing request is inspected
+- THEN it still includes `lastcheckok=1`, unchanged from before the extraction