On this page 00 — Day brief · 2026-08-23 · ONDA 0%

00-day-brief.md

00 — Day brief · 2026-08-23 · ONDA

Sustituye a RELEVO (retirado: no gustó el feed de recogidas).

Decisiones ALS-2 BRIEF

CampoValor
Fecha2026-08-23
NombreONDA
ComplejidadNivel 1 (no L3 consecutivo tras RAMAL)
SectorRadio comunitaria / archivo de emisiones (Carabanchel)
TipoGalería de programas + búsqueda en archivo + mesa CONDUCTOR JWT
PlataformaWeb responsive (galería en el móvil · rail de búsqueda · mesa desktop)
RegistroR-HY — lookbook de cubiertas + herramienta de archivo
ShellS-RAIL — rail Hoy / Buscar / Estudio
HomeH-GALLERY — cubiertas de 4 programas; lead + 3 en el recorte 16/10
FlujoF-SEARCH — el archivo se busca; pedir repetición no reserva el estudio
Por qué no RELEVONo familias, no S-DOCK, no H-FEED, no F-STORY, no tidal
Por qué no RAMALNo S-SPLIT / H-FULL / F-ONB; no telecom
Por qué no TEMPLENo kiosk de horas; repetición ≠ coger sala
Por qué no PIZARRANo catálogo municipal; ONDA no es el distrito

Terna unicidad

R-HY · S-RAIL · H-GALLERY · F-SEARCH

0 códigos iguales a N−1 (RAMAL: R-CL · S-SPLIT · H-FULL · F-ONB).
≥2 distintos vs N−2 (TEMPLE) y N−3 (PREGÓN).

S-RAIL, H-GALLERY y F-SEARCH no están en los últimos 5.

Ground (CRON §5.6)

  • Familia: G-SIGN (papel de cartel, no hielo, no asfalto)
  • Hex: #FFFFFF
  • Por qué: la radio de barrio se lee como un cartel en la portería. El acento es amarillo de cinta aislante, no el fondo.

Mood visual (Paper)

  • Candidatos: nocturnal/phosphor (primer instinto radio), signage, rusted, mineral, chapel
  • Elegido: signage — no el primer instinto estudio negro. Una onda se pega en el cristal con cinta amarilla.
  • Paleta:
    • #FFFFFF papel de cartel (ground)
    • #111111 tinta de rotulador
    • #E6C200 cinta aislante / chrome yellow (mark, poco)
    • #5C5C5C mute
    • #C41E3A sello de «en antena» (poco)
    • #F3F3F3 rail
  • Tipo: DM Serif Display · IBM Plex Sans

Alcance L1 must

  1. Home galería: 4 programas (lead + 3 visibles)
  2. Buscar en archivo (query + resultados)
  3. Ficha de programa (título, hora, estado)
  4. Pedir repetición (pedir ≠ reservar estudio)
  5. Login JWT CONDUCTOR
  6. Mesa de hoy (lista, no kanban)
  7. Empty / error / loading / mobile gallery
  8. ≥12 UX + ≥10 UI

Microcopy duro

  • Onda ≠ playlist. No hay cola de Spotify.
  • Pedir repetición ≠ reservar estudio. No se apunta un slot de grabación.
  • ONDA no es la SER. No sustituye a una emisora nacional.
  • Escuchar ≠ suscribirse. Pedir no crea una cuenta de oyente.

Supuestos

  • S1: Una emisora atiende 2–4 programas/día; la demo tiene 4 (1 EN_ANTENA, 1 PROGRAMADO, 2 ARCHIVO).
  • S2: Iker busca «patio» sin cuenta. JWT solo para la mesa.
  • S3: Nerea conduce. Pilar (técnica de antena) está fuera del producto.
  • S4: Sin streaming embebido, sin chat, sin calendario semanal. [DECISIÓN]

Hipótesis

IDSeñal
H1Una galería de cubiertas reduce «¿qué hay hoy?» por WhatsApp
H2Separar pedir repetición de reservar estudio evita el conflicto con TEMPLE
H3Cuatro programas bastan; no hace falta parrilla de la semana

00-paper-reference.md

Paper reference · ONDA

URLhttps://app.paper.design/file/01M0TSYSNQFZ8FVKD38ZGQT8AW
File ID01M0TSYSNQFZ8FVKD38ZGQT8AW

UX-count: 12 UI-count: 12

UX-00 Cover · UX-01 Stakeholders · UX-02 Personas · UX-03 JTBD · UX-04 Stories · UX-05 Journey · UX-06 Blueprint · UX-07 Site map · UX-08 Flujos · UX-09 Datos+permisos · UX-10 Métricas · UX-11 Research UI-00 Tokens · UI-01 Gallery · UI-02 Gallery mobile · UI-03 Search · UI-04 Programa · UI-05 Programa mobile · UI-06 Success · UI-07 Login · UI-08 Mesa hoy · UI-09 Empty · UI-10 Error · UI-11 Loading

UX process (prefijo UX- · mínimo 12)

IDNombreContenido
UX-00CoverONDA · «Hoy suena esto.» · R-HY · S-RAIL · H-GALLERY · F-SEARCH · L1 radio · signage · 2026-08-23
UX-01StakeholdersCONDUCTOR Nerea, OYENTE Iker, técnica Pilar fuera, SER fuera
UX-02PersonasNerea Gil 34 · Iker Soto 52 · Pilar Ruiz 41 (docs)
UX-03JTBDJob «encontrar el programa de esta tarde y pedir la repetición sin el grupo»
UX-04StoriesMust: galería, buscar, ficha, pedir, login, mesa, estados
UX-05JourneyPortería → galería → Patio 17 → pedir repetición
UX-06BlueprintFrontstage cartel · backstage estudio/antena · Nest/Neon/JWT
UX-07Site map/ gallery · /buscar · /programa/:code · /ok/:code · /login · /hoy
UX-08FlujosF-SEARCH público; mesa CONDUCTOR
UX-09DatosUser CONDUCTOR · Programa; JWT en /hoy
UX-10MétricasNorth star % de peticiones de repetición servidas en 48 h
UX-11ResearchComprobado / supuesto / hipótesis / decisión

UI producto (prefijo UI- · mínimo 10)

IDNombreFlujo
UI-00TokensDS signage: paper, ink, tape yellow
UI-01GalleryH-GALLERY 1440: lead + 3 cubiertas. Rail
UI-02Gallery mobile390
UI-03SearchArchivo; query «patio»
UI-04ProgramaFicha Patio 17 en antena
UI-05Programa mobile390
UI-06SuccessPedida. Pedir ≠ reservar estudio
UI-07LoginJWT CONDUCTOR
UI-08Mesa hoyLista de hoy
UI-09EmptySin resultados
UI-10ErrorRed
UI-11LoadingSkeletons

Mapeo Paper → Angular

ArtboardRutaComponente
UI-01 Gallery/GalleryPage
UI-03 Search/buscarSearchPage
UI-04 Programa/programa/:codeProgramaPage
UI-06 Success/ok/:codeOkPage
UI-07 Login/loginLoginPage
UI-08 Mesa/hoyHoyPage

01-project-definition.md

01 — Definición de proyecto — ONDA

1. Identidad

CampoValor
NombreONDA
SignificadoOnda es la radio del bloque que se pega en el cristal con cinta amarilla, no una playlist ni una cadena nacional. Se entra a la galería; se sale habiendo pedido una repetición.
Eslogan«Hoy suena esto.»
Una fraseGalería de cubiertas + búsqueda en archivo + mesa CONDUCTOR JWT (lista de hoy).
SectorRadio comunitaria / archivo de emisiones (Carabanchel)
TipoWeb L1 — galería H-GALLERY + búsqueda F-SEARCH + mesa rail — Nivel 1, herramienta de archivo usable
PlataformaWeb responsive (galería en el móvil · rail de búsqueda · mesa desktop)
Mercado demoEspaña · Madrid · Carabanchel (una emisora de barrio, no una cadena)
Idiomaes-ES
Fecha caso2026-08-23
TernaR-HY · S-RAIL · H-GALLERY · F-SEARCH

[COMPROBADO] Nombre, terna, nivel, sector, ground y paleta salen de docs/00-day-brief.md.
[SUPUESTO de catálogo] Carabanchel / Patio 17 / Mercado Usera es atrezzo de mercado, no la parrilla de una emisora real.

Este case sustituye a RELEVO (retirado: no gustó el feed de recogidas). RELEVO no es N−1: N−1 es RAMAL. RELEVO se cita solo como anti-patrón. [DECISIÓN]

2. Problema

Principal (hipótesis de diseño)

[HIPÓTESIS] El oyente de Carabanchel no necesita una app de streaming: necesita saber qué suena hoy y pedir que se repita un programa sin el grupo de WhatsApp. Hoy eso vive en un cartel de portería, un audio de voz y una expectativa de playlist. La galería se lee como Spotify; pedir repetición se lee como reservar el estudio; el producto se lee como la SER. [SUPUESTO] Una emisora atiende 2–4 programas/día; no opera un streaming embebido ni un chat de oyentes.

Secundarios

ProblemaQuién lo sufreEfecto
«¿Qué hay hoy?» por WhatsAppOYENTE (Iker)Espera, se pierde Patio 17, no pide repetición
Galería se lee como playlistOYENTEEspera cola, shuffle, suscripción
Pedir repetición se lee como reservar estudioOYENTE + NereaEsperan un slot de grabación (eso fue TEMPLE)
La mesa se lee como kanbanCONDUCTOR (Nerea)Esperan Recibido / En curso / Cerrado
La búsqueda se lee como catálogo municipalIkerEsperan talleres del distrito (eso fue PIZARRA)
El dueño pide player y parrilla semanal el día 1EmisoraEl L1 se vuelve cadena

Supuestos (no investigación primaria propia)

  • S1: Una emisora atiende 2–4 programas/día; la demo tiene 4 (1 EN_ANTENA, 1 PROGRAMADO, 2 ARCHIVO). [SUPUESTO]
  • S2: Iker busca «patio» sin cuenta. JWT solo para la mesa. [SUPUESTO]
  • S3: Nerea conduce. Pilar (técnica de antena) está fuera del producto. [SUPUESTO]
  • S4: Sin streaming embebido, sin chat, sin calendario semanal. [DECISIÓN]

Hipótesis de producto

IDHipótesisSeñal de validación (futura)
H1Una galería de cubiertas reduce «¿qué hay hoy?» por WhatsApp% de visitas a / que terminan en POST /repetir
H2Separar pedir repetición de reservar estudio evita el conflicto con TEMPLE↓ «pensé que me guardabais la sala»; copy visible en acuse
H3Cuatro programas bastan; no hace falta parrilla de la semana0 calendario; Nerea decide el hoy sin grid semanal

No se afirman estadísticas de radio comunitaria inventadas. Todo lo anterior es razonamiento de diseño etiquetado.

3. Propuesta de valor

ParaValor
OYENTE (Iker Soto)Ve 4 cubiertas, busca «patio», abre Patio 17, pide la repetición, se lleva el código. Nadie le pide cuenta ni suscripción.
CONDUCTOR (Nerea Gil)Ve la lista de hoy, lee estados y peticiones. No mueve columnas. No reserva estudio.
TÉCNICA (Pilar Ruiz)Fuera del slice de código. El oficio de antena no entra en v1. Aparece como conductora de un programa de archivo («fuera»).
Emisora ONDAUna superficie mínima: galería + archivo + mesa, sin playlist y sin cadena.

No es ONDA

ExcluidoPor qué
Playlist / cola / shuffle / SpotifyOnda ≠ playlist
Reservar estudio / slot de grabaciónPedir repetición ≠ reservar estudio
Cadena nacional / la SER / podcast networkONDA no es la SER
Cuenta de oyente / newsletter / followEscuchar ≠ suscribirse
Player embebido / streaming en vivoS4
Chat de oyentes / kanbanEso fue PREGÓN (H-CHAT / F-KAN)
Quiosco de horas / bloques de 2 hEso fue TEMPLE (H-KIOSK / F-BOOK)
Catálogo municipal / talleres / distritoEso fue PIZARRA; ONDA no es el distrito
Feed de recogidas / familias / coleRELEVO, retirado. Anti-patrón. No es el producto
Portada de nodos + checklist de altaEso fue RAMAL (H-FULL / F-ONB)
Hero 2-col + 3 cards de «tu radio»Anti-patrón AP-12; contradice H-GALLERY
Parrilla de la semana / EPGH3 · S4
App de 40 programas y CMSL1 compacto, no radio nacional

4. Objetivos

Negocio / caso de estudio

  • Demostrar vertical slice L1 radio comunitaria / archivo con Programa + repetición + JWT CONDUCTOR.
  • Portfolio coherente: Paper (12 UX + 12 UI) + docs + app runnable.
  • Terna R-HY · S-RAIL · H-GALLERY · F-SEARCH frente a RAMAL (split/portada/alta), TEMPLE (kiosk/horas) y RELEVO retirado (dock/feed/relato).
  • Sustituir RELEVO en el mismo día: el contrato vivo es ONDA, no un leftover de recogidas.

Usuario

RolObjetivo medible en demo
OYENTEVer 4 cubiertas, buscar «patio», pedir repetición de Patio 17 y ver el acuse en < 90 s, sin cuenta
CONDUCTOREntrar a mesa, ver 4 programas de hoy, leer ON-2301 Patio 17 EN_ANTENA en < 45 s
TÉCNICANo encuentra login; el oficio de antena no está en la app

No objetivos v1 (explícitos)

  • Streaming, player, podcast embed, radio en vivo embebida
  • Playlist, cola, shuffle, suscripción de oyente
  • Reserva de estudio, slot de grabación, cuadrante de Pilar
  • Chat, kanban, columnas Recibido / En curso
  • Cuenta de oyente, seguimiento con login, push
  • Parrilla semanal, EPG, calendario
  • Multi-emisora, multi-conductor
  • App nativa ni notificaciones push
  • Rol TÉCNICA autenticado
  • Feed de recogidas, familias, cole (RELEVO retirado)
  • Nodos, altas, vocal (RAMAL es otro día)

5. Roles y permisos (resumen)

AcciónAnónimo (oyente)CONDUCTORTÉCNICA
Ver galería / programasNo hay cuenta
Buscar archivo ?q=
Ver ficha /programa/:code
POST repeticiónSí (no es el job)No
Ver acuse /ok/:code (código en URL)
Ver mesa /hoyNo (401 / redirect)No
GET /api/hoy401
Ver lista de peticionesNoSí (mesa)
Reservar estudioNo (no existe)NoFuera
Subir a la antenaCopy: fueraCopy: fueraOficio fuera de app
Recoger a un niñoNo. RELEVO retiradoNoNo

[DECISIÓN] Un solo rol autenticado: CONDUCTOR. El oyente no es un usuario del sistema: es autor/autora de una repeticion. La técnica no es un rol de sistema el día 1.

6. Métricas (modelo, no instrumentadas en v1 salvo base)

TipoMétricaDefinición
North Star% de peticiones de repetición servidas en 48 hOficio de emisora, no pageviews
Activación oyente1.er POST /repetir con codeH1
Integridad de expectativaReclamaciones «¿dónde está el play?» / «¿es la SER?» / «¿me guardáis el estudio?»H2 + microcopy
Integridad de authGET /api/hoy sin token → 401; POST /api/programas/:code/repetir sin token → 201Galería pública + mesa cerrada
SaludError rate API programas / repeticiones4xx/5xx

7. Alcance funcional v1 (L1)

MóduloIncluido
GaleríaGET /api/programas · 4 programas (lead + 3 visibles)
BúsquedaGET /api/programas?q= · archivo, query «patio»
FichaGET /api/programas/:code · título, hora, estado, cubierta
RepeticiónPOST /api/programas/:code/repetir → acuse; pedir ≠ reservar
AuthPOST /api/auth/login → JWT CONDUCTOR
MesaGET /api/hoy JWT · lista de hoy, no kanban
Estados UIEmpty (0 programas / 0 resultados), loading skeleton, error de red
MobileGalería ~390px · cubiertas apiladas · targets ≥44px
Seed1 CONDUCTOR, 4 programas (1 EN_ANTENA, 1 PROGRAMADO, 2 ARCHIVO)
SaludGET /api/health{ ok: true, service: "onda-api" }

8. Criterios de aceptación de producto

  1. / es la galería de 4 cubiertas (lead + 3). No pide cuenta. No es un hero 2-col + 3 cards. No es un feed.
  2. Un oyente puede ver 4 programas en el recorte 16/10 (lead + 3), tocar Patio 17, pedir repetición y obtener acuse en /ok/ON-2301.
  3. /ok/:code dice explícitamente que pedir repetición no es reservar estudio y que Onda no es playlist.
  4. La UI dice explícitamente que ONDA no es la SER y que escuchar no es suscribirse.
  5. Sin token, GET /api/hoy responde 401. POST /api/programas/:code/repetir y GET /api/programas son públicos.
  6. Un POST de repetición no crea user, no reserva un slot, no abre un player.
  7. Un CONDUCTOR inicia sesión y ve 4 programas (Patio 17 EN_ANTENA, Voces PROGRAMADO, dos ARCHIVO) en el seed.
  8. El tablero es una lista de hoy. No es un kanban de 3 columnas. No es un split lista | ficha.
  9. Fotos reales en las cubiertas seed (assets programas/*).
  10. Chrome S-RAIL: Estudio / Buscar / Hoy. Galería pública sin top-nav de marketing.
  11. Rutas solo las del doc 03. No existen /nodos, /alta, /mesa, /relevo, /recoger, /playlist, /directo.
  12. GET /api/health devuelve service: "onda-api" (no relevo-api, no ramal-api).
  13. La galería no muestra nombres de otros oyentes; la mesa sí puede mostrar recuento de peticiones.
  14. /buscar?q=patio devuelve Patio 17.

9. Stack y artefactos

CapaDetalle
FrontendAngular + Tailwind · puerto 4200
BackendNestJS · puerto 3023
DBNeon PostgreSQL · project falling-fog-92644546
AuthJWT (CONDUCTOR)
DiseñoPaper 01M0TSYSNQFZ8FVKD38ZGQT8AW
Repo app/Users/cristian/orca/onda-app/
Packagepnpm

[COMPROBADO] Puerto, Neon, Paper file ID y path de app constan en el encargo.
El código se escribe en paralelo a esta suite. Este doc es el contrato. No se espera al rewrite.

10. Riesgos y mitigaciones

RiesgoImpactoMitigación v1
Expectativa de playlist«¿Dónde está el play?»Copy «Onda ≠ playlist»
Expectativa de estudioSlot, TEMPLE, «¿a qué hora grabamos?»Copy «Pedir repetición ≠ reservar estudio»
Expectativa de cadenaLa SER, podcast nacionalCopy «ONDA no es la SER»
Expectativa de cuentaSignup, follow, newsletterCopy «Escuchar ≠ suscribirse»; POST anónimo
Expectativa de kanbanRecibido / En cursoLista de hoy, no columnas de estado
Leftover RELEVOFeed de recogidas, TUTOR, coleContrato ONDA; health onda-api; 0 copy de recogida
Leftover RAMALnodes, altas, VOCAL, RM-Dominio programas + repeticiones + CONDUCTOR
Player el día 1Alcance L2 disfrazadoS4: sin streaming
Cuenta de oyenteRompe S2 / H3Sin signup; POST público
Home percibida como landing de emisoraPérdida de H-GALLERY / AP-124 cubiertas; no hero 2-col
Confundir con TEMPLE (kiosk + horas)Expectativa de bloques de 2 hGalería de programas; no slots
Confundir con PIZARRA (catálogo + plaza)Expectativa de taller municipalONDA no es el distrito
Confundir con PREGÓN (chat + kanban)Expectativa de parteBúsqueda + pedir; no hilo
0 programasGalería vacía sin oficioEmpty «Aún no hay programas.»
WhatsApp paraleloH1 no se mideNorth star etiquetada como modelo
Amarillo de cinta como textoContraste WCAGMark pinta cinta/CTA; ink escribe

11. Glosario

TérminoDefinición en ONDA
OndaRadio de barrio; el producto, no una playlist ni la SER
ProgramaEmisión (programas); no es un track de cola
CubiertaFoto 16/10 del programa en galería
ArchivoProgramas que ya sonaron; se buscan, no se shufflean
RepeticiónPOST 201; no significa reservar el estudio
AcuseCódigo (ON-XXXX); no es una suscripción
En antenaEstado EN_ANTENA; sello rojo, poco
ProgramadoEstado PROGRAMADO; suena más tarde hoy
Archivo (estado)Estado ARCHIVO; se puede pedir repetición
GaleríaSuperficie H-GALLERY pública
Mesa / HoySuperficie JWT del CONDUCTOR (lista, no kanban)
RailChrome S-RAIL: Estudio / Buscar / Hoy
EstudioSlot del rail que vuelve a la galería; no es una reserva de sala
KanbanLenguaje de diseño de otros días; no hay columnas aquí
CONDUCTORUsuario autenticado que opera la mesa
OYENTEAutor/autora de la repetición; no tiene fila en users
TÉCNICAOficio de antena; fuera del slice de código
PlaylistCola tipo Spotify; fuera
SERCadena nacional; fuera. ONDA no es la SER
RELEVOProducto retirado (feed de recogidas); anti-patrón
SignageMood: papel de cartel + cinta amarilla
GroundPapel #FFFFFF
InkTinta de rotulador #111111
MarkCinta aislante #E6C200
Mute#5C5C5C
SealSello en antena #C41E3A, poco
Rail (color)#F3F3F3

12. Decisiones de diseño (cierre de brief)

IDDecisiónAlternativa descartada
D1Home = galería de cubiertas (H-GALLERY)Hero 2-col + 3 cards (AP-12) · feed (RELEVO) · portada a sangre (RAMAL) · kiosk (TEMPLE)
D2Shell rail Estudio / Buscar / Hoy (S-RAIL)Split mesa · dock · bottom tabs · kiosk mínimo · wizard chrome
D3Registro híbrido lookbook + archivo (R-HY)Estudio negro phosphor (primer instinto radio) · ice RAMAL · tidal RELEVO
D4Flujo F-SEARCH (el archivo se busca)F-BOOK, F-KAN, F-ONB, F-STORY, F-EDIT
D5Mood signageNocturnal/phosphor (primer instinto), rusted, mineral, chapel, ice, tidal
D6Un rol autenticado CONDUCTORMulti-rol técnica / oyente / cadena
D7Status EN_ANTENA / PROGRAMADO / ARCHIVOKanban de 3 columnas; «en directo streaming»; «publicado podcast»
D8Oyente sin cuentaSignup, follow, newsletter
D9Galería no lista nombres de oyentesTablero público enumerable
D10POST público de repeticiónFormulario de DNI + pago + reserva de sala
D11Ground G-SIGN #FFFFFFG-ICE, G-INK, G-FOG, G-CHAPEL, G-WATER, G-CONCRETE
D12Success de acuse en /ok/:codeToast único sin pantalla
D13Búsqueda en /buscar, no como homeH-SEARCH tipo PIZARRA (rompería H-GALLERY)
D14Mesa = lista de hoy, no kanban ni splitRecibido / En curso / Cerrado; lista | ficha RAMAL
D15Galería pública + mesa JWT (no auth-first total)Todo cerrado (rompería S2) · todo abierto (rompería mesa)
D16Sin streamingPlayer en ficha
D17Sustituye a RELEVO; 0 copy de recogidaMantener leftover TUTOR / feed / cole
D18Health onda-api · puerto 3023leftover relevo-api

13. Relación con el día anterior

CaseTernaPor qué ONDA no lo copia
RAMAL (N−1)R-CL · S-SPLIT · H-FULL · F-ONBNo telecom, no nodos, no altas, no vocal, no split
TEMPLE (N−2)R-HY · S-MIN · H-KIOSK · F-BOOKNo kiosk, no horas, no concierto, no turno; repetición ≠ coger sala
PREGÓN (N−3)R-IN · S-SIDE · H-CHAT · F-KANNo admin pública, no chat, no kanban, no 112
MATIZ (N−4)R-HY · S-BOTTOM · H-BENTO · F-EDITNo belleza, no PWA de oficio, no editor de gramos
RELEVO (retirado, mismo día)R-IN · S-DOCK · H-FEED · F-STORYAnti-patrón. No familias, no feed de recogidas, no cole, no TUTOR
PIZARRAR-CL · S-CMD · H-SEARCH · F-SEARCHComparte F-SEARCH; no clona H-SEARCH ni el distrito. ONDA no es el distrito

[COMPROBADO] El brief fija terna R-HY · S-RAIL · H-GALLERY · F-SEARCH. 0 códigos iguales a N−1 (RAMAL). ≥2 distintos vs N−2 (TEMPLE) y N−3 (PREGÓN). S-RAIL, H-GALLERY y F-SEARCH no están en los últimos 5. Ventana 4 días: R-IN (PREGÓN) + R-HY (TEMPLE) + R-CL (RAMAL) + R-HY. Ground G-SIGN #FFFFFF, no G-ICE ni G-WATER.

02-ux-research-strategy.md

02 — Estrategia de investigación UX — ONDA

Importante: este documento contiene artefactos de diseño y razonamiento secundario.
No hay entrevistas de campo primarias ni estadísticas inventadas presentadas como dato medido.
Etiquetas: [COMPROBADO] en producto, [SUPUESTO], [HIPÓTESIS], [DECISIÓN].

1. Objetivos de investigación (del caso)

ObjetivoMétodo en este casoSalida
Entender actores de la emisoraModelado de stakeholders + personas§3–4
Definir job de «encontrar el programa de esta tarde y pedir la repetición sin el grupo»JTBD + stories Must§5–6
Mapear fricción WhatsApp / playlist / estudio → galeríaJourney + service blueprint§7–8
Traducir a requisitos L1 galería + archivo + mesaMatriz hallazgo → requisito → feature§9

2. Fuentes y límites

Fuentes admisibles (secundarias / operativas)

  • Conocimiento general de radios comunitarias de barrio (cartel en portería, programa de patio, archivo en cinta, conductora que no es una cadena, técnica de antena que no entra el día 1).
  • Analogía operativa con superficie pública + panel JWT de la serie daily.
  • Restricciones ALS-2: no Spotify, no la SER, no chat, no kanban, no kiosk de horas, no nodos/altas, no feed de recogidas (RELEVO retirado).

Límites éticos de verdad

ProhibidoPermitido
«El 62% de oyentes de Carabanchel pide repetición por WhatsApp» sin fuente«[SUPUESTO] Iker abre la galería desde el portal»
Citas de entrevistas ficticias como campo realQuotes de persona etiquetadas como constructo de diseño
Volumen medido «17 repeticiones/semana en Patio 17» como KPI de mercadoProgramas de seed de producto, etiquetados como demo
Parrilla oficial de una emisora comunitaria 2026Programas de seed; Nerea, Iker y Pilar son personas de diseño
«Iker existe y nos dijo…»Iker Soto es persona de diseño

3. Stakeholders

StakeholderInfluenciaInterésNecesidad principal
CONDUCTOR (Nerea)AltaMuy altaVer el hoy y las peticiones sin kanban ni player
OYENTE (Iker)Alta en la repeticiónMuy altaEncontrar el programa de esta tarde y pedirlo sin cuenta
TÉCNICA (Pilar)Media formal, baja en producto día 1AltaOficio de antena; fuera del slice de código
Emisora / localAlta formalAltaQue se vea qué suena; que no se confunda con la SER ni con Spotify
SER / cadena nacionalCompetencia / analogía falsaFuera de alcance; no clonar parrilla nacional
Spotify / playlistCanal paralelo / analogía falsaFuera; Onda ≠ playlist
TEMPLE / local de ensayoAnalogía falsa de «reservar sala»Fuera; pedir ≠ reservar estudio
Grupo de WhatsApp del bloqueCanal paraleloNo es el backoffice
RELEVO (retirado)Anti-patrónFuera; no familias, no recogida, no cole

Mapa de poder (resumen)

  • Decisor de adopción: la emisora (quién pone el cartel) y, en uso diario, Nerea (quien mira el hoy).
  • Usuario frecuente de pedir repetición: OYENTE (galería + búsqueda + ficha, sin cuenta).
  • Usuario frecuente de oficio: CONDUCTOR (mesa /hoy).
  • Riesgo de rechazo: si el acuse parece una reserva de estudio; si la cubierta parece un track; si hay que crear cuenta; si la mesa parece un kanban; si el rail parece un dock de recogidas.

4. Personas

P1 — Nerea Gil · CONDUCTOR

CampoDetalle
Edad / contexto34 años; conduce en Carabanchel; mesa de hoy, no de instalación de antena
DigitalFluida en desktop de mesa; la galería pública no es su herramienta principal, pero la mira
GoalsVer qué suena, qué está programado, qué se ha pedido repetir
PainsWhatsApp a deshora; «¿me guardáis el estudio?»; un kanban que no es su oficio; un player que no es su job
Quote de diseño«Quiero ver el hoy. No un kanban y no una playlist.»
Email demonerea@onda.radio / password123

Escenario: Entra a /login, abre /hoy, lee ON-2301 Patio 17 EN_ANTENA, ve el recuento de repeticiones. El copy dice que una petición no reserva el estudio.

[COMPROBADO] Nombre, edad, email demo y rol salen del encargo.

P2 — Iker Soto · OYENTE

CampoDetalle
Edad / contexto52 años; vive en Carabanchel; oye Patio 17 cuando puede; no quiere «otra cuenta de radio»
DigitalMóvil desde el portal o el salón; no se suscribe si puede evitarlo
GoalsVer si suena Patio 17; buscarlo en archivo; pedir que lo repitan; llevarse un código
PainsEl grupo dice «creo que es a las cinco»; abre Spotify; le venden una playlist; cree que pide el estudio
Quote de diseño«Solo quiero oír Patio 17 otra vez. No me hagas una cuenta.»

Escenario: Abre / en el portal, ve la cubierta de Patio 17 EN_ANTENA, o busca «patio» en /buscar, toca la ficha, lee «pedir ≠ reservar estudio» y el código ON-2301.

[COMPROBADO] Nombre, edad y gesto (oyente, 52) salen del encargo. Es constructo de diseño, no entrevistado. [DECISIÓN]
En el seed Iker también aparece como conductor de Mercado Usera (archivo). Eso no le da login: sigue sin fila en users. [DECISIÓN]

P3 — Pilar Ruiz · TÉCNICA (docs)

CampoDetalle
Edad / contexto41 años; técnica de antena; oficio de azotea y cable, no de mesa
DigitalNo tiene login en el slice L1
GoalsSubir cuando la emisora lo pida (fuera de app)
PainsQue pedir repetición se lea como una reserva suya del estudio; que la app le pida cuadrante
Quote de diseño«La repetición no me programa. Yo no entro aquí.»

[COMPROBADO] Nombre, edad y exclusión de login salen del encargo. [DECISIÓN] no hay cuenta TÉCNICA en v1.
En el seed aparece como conductora de Banda municipal con la marca (fuera).

5. JTBD

Job principal (oyente)

Cuando quiero saber qué suena esta tarde, quiero encontrar el programa y pedir la repetición sin el grupo, para dejar constancia en la emisora sin que eso signifique playlist, cuenta ni reserva de estudio.

Job principal (conductora)

Cuando hay programas hoy y peticiones nuevas, quiero ver la lista de hoy, para mover el oficio de la emisora sin un kanban ni un player.

Jobs que ONDA no hace

JobPor qué no
Reproducir un streamOnda ≠ playlist · S4
Reservar el estudioPedir repetición ≠ reservar estudio
Suscribirse / followEscuchar ≠ suscribirse
Sustituir a la SERONDA no es la SER
Coger hora de ensayo≠ TEMPLE
Pedir plaza de taller≠ PIZARRA
Recoger en el cole≠ RELEVO (retirado)
Pedir nodo / alta de bloque≠ RAMAL

6. Stories Must (v1)

IDComoQuieroPara
US-01OyenteVer 4 cubiertas (lead + 3)Distinguir radio de barrio de playlist
US-02OyenteBuscar «patio» en archivoEncontrar el programa sin el grupo
US-03OyenteVer ficha (título, hora, estado)Saber si está en antena, programado o archivo
US-04OyentePedir repetición y ver acuse ON-XXXXDejar constancia, no reservar estudio
US-05ConductoraEntrar con JWTAbrir la mesa de hoy
US-06ConductoraVer lista de hoyOficio sin kanban
US-07AmbosVer empty / error / loadingNo quedarse en blanco

Could (fuera de L1): login de Pilar, streaming, parrilla semanal, chat, cuenta de oyente, player.

7. Journey (resumen)

  1. Iker abre /. Ve 4 cubiertas. Lead Patio 17 EN_ANTENA. [H1]
  2. Toca Patio 17 → /programa/ON-2301. (O busca «patio» en /buscar.)
  3. Lee hora 17:00, estado En antena, conductora Nerea. Copy irrenunciable.
  4. POST repetición → /ok/ON-2301. Lee las cuatro oposiciones.
  5. Nerea entra a /login/hoy. Lista de hoy.
  6. Lee ON-2301 EN_ANTENA y el recuento. Copy: pedir no reserva el estudio.

Backstage: Nest + Neon + JWT. Pilar no entra. La antena no se mueve. No hay player.

8. Blueprint (resumen)

CapaQué pasa
Frontstage (cartel)Galería en portería / móvil. Cubiertas. Rail Estudio / Buscar. Ficha. Acuse.
Backstage (estudio / antena)Nerea mira /hoy. Pilar está en la azotea, fuera de app. La cinta y el micro son oficio, no UI.
SoporteNest · Neon falling-fog-92644546 · JWT CONDUCTOR · Paper signage

9. Matriz hallazgo → requisito → feature

Hallazgo (etiquetado)RequisitoFeature v1
[HIPÓTESIS H1] Galería de cubiertas reduce WhatsApp4 cubiertas en fold, lead + 3/ H-GALLERY
[HIPÓTESIS H2] Pedir ≠ reservar evita TEMPLECopy irrenunciable + POST que no crea slot/programa/:code + /ok/:code
[HIPÓTESIS H3] Cuatro programas bastanSin parrilla semanalSeed 4 · no calendario
[SUPUESTO S2] Sin cuenta de oyentePOST público + GET ?q= públicoPOST /api/programas/:code/repetir
[SUPUESTO S3] Una conductoraUn rol JWTCONDUCTOR
[DECISIÓN S4] Sin streaming / chat / semanaNo player, no hilo, no EPGFuera de alcance

10. Preguntas que este case no responde

  • ¿Cuántas repeticiones reales hay en Carabanchel en 2026?
  • ¿Qué emisora opera un micro en Patio 17?
  • ¿Iker preferiría WhatsApp?
  • ¿Pilar necesita un cuadrante de antena?
  • ¿Nerea serviría la repetición en 48 h de verdad?

Esas preguntas piden campo. Aquí hay un vertical slice de diseño, no un estudio.

11. Cierre

Investigación = modelado etiquetado + restricciones ALS-2.
No hay entrevistas inventadas.
Las personas son constructos. Carabanchel / Patio 17 / Mercado Usera son [SUPUESTO de catálogo].
RELEVO no se investiga: está retirado.

03-information-architecture.md

03 — Arquitectura de información — ONDA

1. Principios de IA

PrincipioAplicación
Galería firstLa home son 4 cubiertas. No hay landing de marca por delante. No hay search como home
Público / mesaIker pide repetición sin cuenta; Nerea entra con JWT
Cubierta → ficha → acuseProfundidad 2 desde la galería hasta el código
El archivo se buscaF-SEARCH vive en /buscar, no sustituye H-GALLERY
Mesa = lista de hoyUna columna; no kanban, no split lista | ficha
Lenguaje de dominioPrograma, cubierta, archivo, repetición, hoy — no «track», «lead», «expediente», «relevo»
Pedir no es reservarEl acuse no tiene slot ni hora de estudio
Onda no es playlistNo hay cola, play, shuffle
Chrome de oficioS-RAIL Estudio / Buscar / Hoy; galería sin top-nav de marketing

2. Sitemap

/                                      Galería pública (4 cubiertas)                 H-GALLERY
/buscar                                Archivo · query + resultados                  F-SEARCH · público
/programa/:code                        Ficha de programa                             público
/ok/:code                              Acuse post-repetición                         público
/login                                 Login JWT CONDUCTOR                           público
/hoy                                   Lista de hoy                                  auth
/**                                    → redirect /

Árbol por audiencia

AudienciaNodos relevantes
OYENTEGalería → (Buscar) → Programa → Ok. No ve /hoy
CONDUCTORLogin → Hoy · (puede leer la galería y buscar)
TÉCNICANinguno como rol de sistema

[COMPROBADO] Seis rutas del encargo L1. No se inventan /nodos, /alta, /mesa, /relevo, /recoger, /playlist, /directo, /estudio-reserva.

La búsqueda es ruta (/buscar): el archivo se busca; la home sigue siendo galería. [DECISIÓN]
La ficha es ruta (/programa/:code) porque F-SEARCH y la galería necesitan URL compartible. [DECISIÓN]
/ok/:code usa el código de programa (ON-2301), no un código de petición distinto. L1 compacto. [DECISIÓN]

3. Navegación

Rail (S-RAIL · todas las superficies salvo login/ok pueden mostrarlo)

SlotDestinoVisibilidadNotas
Estudio/TodosGalería de cubiertas. No es reserva de sala
Buscar/buscarTodosArchivo. Placeholder «patio»
Hoy/hoyCONDUCTOR; si guest → /login?next=/hoyLista de oficio

[DECISIÓN] El rail es la navegación primaria. Tres destinos: encaja L1 compacto. No hay top-nav de 7 ítems, no dock flotante (RELEVO), no split (RAMAL), no kiosk mínimo (TEMPLE).

Desktop ≥960px: rail vertical izquierdo (~56–72px, ground #F3F3F3) + panel.
Mobile ~390px: rail inferior de tres slots; la galería se apila. [DECISIÓN] En 390 el rail no compite con un hamburger.

ElementoDestinoNotas
Wordmark ONDA/Sello de cartel; no es hero 2-col
Eslogan«Hoy suena esto.»
Cubierta lead/programa/:codePatio 17 · EN_ANTENA
Cubiertas 2–4/programa/:codeMercado Usera · Voces · Banda
Link Conductora (mute)/login o /hoy si hay tokenSecundario, no CTA de marca

[DECISIÓN] La galería no tiene tabs de marketing, no tiene «Directo / Podcasts / Suscríbete». El rail basta.

SlotDestinoNotas
← Estudio/Un paso atrás, no stepper
Campo query/buscar?q=GET programas
Resultado/programa/:codeMisma ficha que la galería
Pedir repeticiónPOST → /ok/:codeUna acción, no wizard

Chrome de mesa (/hoy autenticado)

SlotDestinoVisibilidadNotas
Hoy/hoyAuthTitular de oficio
ListaAuthUna columna; status en texto
Estudio/AuthSecundario vía rail
Salirlimpia token → /AuthCabecera de mesa, no cuarto slot de rail

[DECISIÓN] No hay S-SPLIT (RAMAL). No hay kanban (PREGÓN). No hay /hoy/:code: L1 lee la lista. Could L+1: ficha de petición.

4. Etiquetas de IA (UI)

ConceptoLabel
HomeEstudio / Galería (no «Inicio», no «Directo», no «Feed»)
BúsquedaBuscar / Archivo
FichaPrograma (no «track», no «episodio SKU»)
SuccessPedida / código
StaffHoy (no «Backoffice», no «Turno», no «Kanban», no «Mesa» tipo RAMAL en UI)
AuthConductora (no «Área cliente», no «Tutor»)
CTAPedir repetición (no «Reservar», no «Play», no «Suscribirse»)

En docs puede decirse «mesa» para el oficio JWT. En UI el slot se llama Hoy. [DECISIÓN]

5. Contenido de cada nodo de IA

RutaQué hayQué no hay
/4 cubiertas, lead + hora/conductora/estado, eslogan, railPlayer, precios, login de oyente, parrilla semanal
/buscarCampo, query «patio», resultados, emptyFacetas municipales, chips de distrito
/programa/:codeCubierta, título, hora, estado, host, copy, CTAPlayer, reserva de sala, follow
/ok/:codeCódigo + 4 oposicionesContrato, playlist, «ya está reservado»
/loginEmail / clave CONDUCTORSignup de oyente
/hoyLista de 4 programas + recuento de peticionesColumnas Recibido / En curso; split ficha

6. Error / empty / loading (sitio)

Viven en las mismas rutas, no en URLs extra.

EstadoSuperficie
LoadingSkeletons de 4 cubiertas / resultados / lista de hoy
Empty galería«Aún no hay programas.»
Empty búsqueda«Sin resultados.»
Empty hoy«No hay programas hoy.»
Error red«La red no responde» + Reintentar
404 programa«Ese programa no está» en ficha

7. Profundidad y búsqueda

La búsqueda es Must (F-SEARCH). 4 programas caben en galería; el archivo se demuestra con q=patio → Patio 17. [DECISIÓN]
Nerea no filtra la mesa en v1: ve el hoy entero. Could L+1: filtro por estado.

Query: min 1 carácter tras submit o debounce; case-insensitive sobre title, host, code, lead. Vacío en /buscar sin q muestra prompt, no los 4 (eso es la galería). [DECISIÓN]

8. Códigos y URLs

PiezaForma
ProgramaON-2301ON-2304 (ON versales, guion, 4 dígitos)
Ficha/programa/ON-2301
Acuse/ok/ON-2301
Búsqueda/buscar?q=patio

API puede normalizar on-2301ON-2301. En UI siempre versales.

9. Lo que no entra en el mapa

/nodos, /alta, /mesa, /mesa/:code, /relevo, /recoger, /playlist, /directo, /epg, /estudio/:slot, /inbox, /kanban.
Si el rewrite de onda-app todavía tiene esas rutas, no son el producto. Gana este doc.

04-user-flows.md

04 — Flujos de usuario — ONDA

1. Convenciones

TokenSignificado
F-SEARCHEl archivo se busca; la galería no es el buscador
HoyLista CONDUCTOR, JWT
201Repetición creada (pedida)
401Hoy sin token
404Programa inexistente

2. F1 — Oyente pide repetición desde la galería (Must)

/  →  GET /api/programas
   →  tap cubierta Patio 17  →  /programa/ON-2301
   →  GET /api/programas/ON-2301
   →  tap Pedir repetición
   →  POST /api/programas/ON-2301/repetir
   →  201 { code: ON-2301, title, statusPedida: true }
   →  /ok/ON-2301

Entrada: galería, sin cuenta.
Salida: código visible + copy «pedir ≠ reservar estudio».
Errores: red (reintentar), 404 programa, 0 programas (empty).

AC

  • El CTA no pide email ni signup.
  • POST no cobra, no crea user, no reserva slot, no abre player.
  • /ok/ON-2301 muestra las cuatro oposiciones.

3. F2 — Oyente busca en archivo (Must)

/buscar  →  escribe «patio»  →  GET /api/programas?q=patio
         →  resultados (Patio 17)
         →  tap  →  /programa/ON-2301
         →  (sigue F1 desde la ficha)

Entrada: rail Buscar, sin cuenta.
Salida: al menos Patio 17 para q=patio.
Errores: q=zzzz → empty «Sin resultados.»; red → UI-10.

AC

  • Home sigue siendo galería. /buscar no redirige a / con un hero.
  • Query visible en el campo. Compartible /buscar?q=patio.
  • Cero facetas de distrito / precio (eso fue PIZARRA).

4. F3 — Conductora entra a hoy (Must)

/login  →  POST /api/auth/login
        →  JWT CONDUCTOR
        →  /hoy
        →  GET /api/hoy

AC

  • Sin token, /hoy redirige a /login?next=/hoy.
  • GET /api/hoy sin Bearer → 401.
  • Lista de hoy: 4 filas. EN_ANTENA primero. No columnas Recibido / En curso.
  • Desktop: lista en el panel del rail. 390: lista a pantalla bajo el rail inferior.

5. F4 — Estados de sistema

EstadoTriggerUI
Loading galeríaGET programas en vuelo4 skeletons de cubierta
Empty galería200 []«Aún no hay programas.»
Error galería5xx / redReintentar
Loading buscarGET ?q= en vueloSkeletons de resultado
Empty buscar200 []«Sin resultados.»
Loading fichaGET :codeSkeleton de ficha
404 fichacódigo inexistente«Ese programa no está.»
Loading hoyGET hoy en vuelo4 skeletons de fila
Empty hoy200 { programas: [] }«No hay programas hoy.»
Login 401credenciales«Email o contraseña no valen.»

6. Flujos que no existen

FlujoPor qué
Signup oyenteS2 · Escuchar ≠ suscribirse
Play / streamS4 · Onda ≠ playlist
Reservar estudioPedir ≠ reservar · ≠ TEMPLE
Chat≠ PREGÓN
Alta de nodo≠ RAMAL
Recoger en el cole≠ RELEVO (retirado)
Wizard 1—2—3Pedir es un POST, no onboarding
Login PilarSlice L1
PATCH estado de programaNerea lee; no mueve columnas en v1
Parrilla de la semanaH3 · S4

7. Tiempos de demo (modelo)

ActorRecorridoTecho
IkerGalería → Patio 17 → pedida< 90 s
Iker (archivo)Buscar «patio» → ficha → pedida< 90 s
NereaLogin → hoy → leer ON-2301< 45 s

No son KPIs medidos. Son techos de diseño para el guión. [HIPÓTESIS]

8. Guión de demo canónico

  1. / — lead Patio 17 + 3 cubiertas. Eslogan. Rail.
  2. Tap Patio 17 — ficha EN_ANTENA · 17:00 · Nerea.
  3. Pedir repetición — /ok/ON-2301. Copy ×4.
  4. /buscar?q=patio — un resultado.
  5. /loginnerea@onda.radio / password123.
  6. /hoy — lista; ON-2301 En antena; recuento ≥1 si se hizo el POST.
  7. 390: cubiertas apiladas, no carousel.
  8. API down: error + Reintentar.

9. Contratos de error de API (flujo)

CasoHTTPUI
POST repetición, code inexistente404«Ese programa no está.»
POST repetición, redseal + Reintentar; no navega a ok
GET hoy sin JWT401redirect login
Login malo401copy de login
Query vacía en buscar (sin submit)no fetchprompt de archivo

05-data-model.md

05 — Modelo de datos — ONDA

1. Visión general

Dominio L1 de galería de programas + archivo + mesa autenticada:

EntidadTabla SQLPropósito
UserusersIdentidad de oficio (rol CONDUCTOR)
SessionsessionsSesión JWT / token persistido
ProgramaprogramasEmisión (código ON-XXXX, cubierta, hora, estado)
RepeticionrepeticionesPetición anónima de oyente sobre un programa

Base: PostgreSQL (Neon project falling-fog-92644546) · IDs: serial + texto estable (ON-2301).
[DECISIÓN] ensureSchema es válido si el contrato de tablas se cumple; el brief de stack no exige Prisma.

Contrato de producto: programas + repeticiones + JWT CONDUCTOR.
No nodes / altas / relevos / rooms / bookings.

2. Enums (aplicación; persistidos como TEXT + check)

Role

ValorDescripción
CONDUCTORConductora de la mesa; único rol autenticado v1

ProgramaStatus

ValorDescripción
EN_ANTENASuena ahora; sello rojo en UI, poco
PROGRAMADOSuena más tarde hoy; no es EPG de la semana
ARCHIVOYa sonó; se busca y se pide repetición

Pedir repetición está permitido en los tres estados: «otra vez» no es reserva. [DECISIÓN]

3. Diagrama ER (texto)

users
  id, email, password_hash, name, role(CONDUCTOR)
  created_at
  1 ──< sessions

sessions
  id, user_id → users
  expires_at, revoked_at
  created_at

programas
  id
  code (unique, ON-XXXX)
  title, lead
  status (EN_ANTENA|PROGRAMADO|ARCHIVO)
  hour (TEXT, HH:MM)
  host_name
  cover
  sort_order
  created_at, updated_at
  1 ──< repeticiones

repeticiones
  id
  programa_id → programas
  created_at

v1: una sola CONDUCTOR opera sobre el conjunto global.
No hay FK de oyente-usuario: el oyente no se loguea.
repeticiones no apunta a users: la petición es anónima de cuenta.

4. Tablas

users

ColumnaTipoNotas
idBIGSERIAL PK
emailTEXT UNIQUEnerea@onda.radio
password_hashTEXTbcrypt 12 · seed $2b$12$OEKcNck63neDJ.CrVP0lNOaXp2NG2zd2aMiGmnYY221h4ZAZ58x1m
nameTEXTNerea Gil
roleTEXTCONDUCTOR
created_atTIMESTAMPTZ

sessions

ColumnaTipoNotas
idUUID PKsid del JWT
user_idBIGINT FK
expires_atTIMESTAMPTZ
revoked_atTIMESTAMPTZlogout
created_atTIMESTAMPTZ

programas

ColumnaTipoNotas
idBIGSERIAL PK
codeTEXT UNIQUEON-2301
titleTEXTPatio 17
leadTEXTUna frase. Onda ≠ playlist.
statusTEXTEN_ANTENA | PROGRAMADO | ARCHIVO
hourTEXT17:00 (24 h, es-ES)
host_nameTEXTNerea · Iker · Pilar (fuera)
coverTEXT/assets/programas/micro.jpg
sort_orderINTEGER1 = lead de galería
created_at / updated_atTIMESTAMPTZ

repeticiones

ColumnaTipoNotas
idBIGSERIAL PK
programa_idBIGINT FK
created_atTIMESTAMPTZ

Sin listener_name, sin email, sin teléfono. Escuchar ≠ suscribirse. [DECISIÓN]

Índices: programas(code), programas(status), programas(sort_order), users(email), repeticiones(programa_id), repeticiones(created_at DESC).

Búsqueda q: ILIKE sobre title, host_name, code, lead. Trigram Could L+1; 4 filas no lo necesitan.

5. Seed

CONDUCTOR

nerea@onda.radio / password123 · Nerea Gil · CONDUCTOR
Hash bcrypt fijo: $2b$12$OEKcNck63neDJ.CrVP0lNOaXp2NG2zd2aMiGmnYY221h4ZAZ58x1m

Programas

codetitlestatushourhostcover
ON-2301Patio 17EN_ANTENA17:00Nerea/assets/programas/micro.jpg
ON-2302Mercado UseraARCHIVO11:00Iker/assets/programas/cinta.jpg
ON-2303Voces del bloquePROGRAMADO20:00Nerea/assets/programas/antena.jpg
ON-2304Banda municipalARCHIVO18:00Pilar (fuera)/assets/programas/cinta.jpg
codelead (catálogo)sort
ON-2301El patio cuenta lo que el bloque no pone en el tablón.1
ON-2302Pregones, cajas y un micro prestado.2
ON-2303Vecinas al aire. Sin playlist.3
ON-2304Ensayo grabado. Pilar no entra en la app.4

[SUPUESTO de catálogo] Títulos, leads y horas son atrezzo. No es una parrilla oficial.

Repeticiones

Seed sin filas de repetición. El POST de smoke crea la primera sobre ON-2301.
Nerea puede ver repeatCount: 0 al entrar en frío y ≥1 tras el guión.

6. JSON (camelCase)

API: hostName, sortOrder, createdAt, repeatCount, cover, hour, status, title, code, lead.
SQL: host_name, sort_order, created_at.

GET /api/programas (item)

{
  "code": "ON-2301",
  "title": "Patio 17",
  "lead": "El patio cuenta lo que el bloque no pone en el tablón.",
  "status": "EN_ANTENA",
  "hour": "17:00",
  "hostName": "Nerea",
  "cover": "/assets/programas/micro.jpg",
  "sortOrder": 1
}

Lista pública: no incluye repeatCount (evita enumerar demanda). [DECISIÓN]

GET /api/hoy (item, JWT)

Igual + "repeatCount": 0. Orden: EN_ANTENA, PROGRAMADO, ARCHIVO, luego hour.

POST /api/programas/:code/repetir → 201

{
  "code": "ON-2301",
  "title": "Patio 17",
  "status": "EN_ANTENA",
  "hour": "17:00",
  "hostName": "Nerea",
  "pedida": true
}

Cuerpo de entrada: {} vacío. No nombre, no email.

7. Generador de código

Los códigos de programa son de catálogo, no se generan en el POST de repetición. Seed ON-2301ON-2304. L+1 que añada programas usa ON- + 4 dígitos correlativos; no es Must.

El acuse reutiliza el code del programa. No hay ON-R-…. [DECISIÓN]

8. Qué no se modela

FueraMotivo
Tabla techniciansPilar no entra
Tabla listeners / subscriptionsEscuchar ≠ suscribirse
Tabla streams / audio_urlS4
Tabla slots / rooms / bookingsPedir ≠ reservar · ≠ TEMPLE
nodes / altasDominio RAMAL; no es el contrato
relevos / pickups / tutorsRELEVO retirado
EPG / week_gridH3 · S4

06-tech-stack.md

06 — Stack tecnológico — ONDA

1. Visión

CapaTecnologíaNotas
FrontendAngular standalone + signalsPuerto 4200 · web responsive
EstilosTailwind CSSTokens signage (ground, ink, mark, mute, seal, rail)
BackendNestJS TypeScriptPuerto 3023, prefijo /api
PersistenciaPostgreSQL (ensureSchema)Tablas users, programas, repeticiones, sessions
DBNeon PostgreSQLproject falling-fog-92644546
AuthJWTRol CONDUCTOR (/hoy); galería, búsqueda y POST repetición públicos
DiseñoPaper01M0TSYSNQFZ8FVKD38ZGQT8AW
Repo/Users/cristian/orca/onda-app/Rewrite en paralelo; este doc es el contrato
Packagepnpmpnpm --filter · prohibido npm workspaces que rompen Angular (AP-06)

Stack fijo del cron (D-P0-03). No React/Next/Firebase.
[COMPROBADO] Puerto 3023, Neon, Paper y path de app constan en el encargo.

El código se escribe en paralelo a esta suite. Si el repo todavía contiene leftover RAMAL (nodes, altas, VOCAL) o leftover RELEVO (relevo-api, TUTOR), no es el estado del producto. Gana el contrato ONDA.

2. Estructura

onda-app/
├── apps/api/     # Nest (paquete independiente)
├── apps/web/     # Angular + Tailwind (paquete independiente)
└── package.json  # scripts api / start · apps independientes

D-P1-03: pnpm --filter. Prohibido npm workspaces que rompen Angular (AP-06).

Scripts raíz esperados:

ScriptAcción
apiArranca Nest en :3023
start / webArranca Angular en :4200

3. API pública vs JWT

MétodoRutaAuth
GET/api/healthPúblico · { ok: true, service: "onda-api" }
GET/api/programasPúblico
GET/api/programas?q=Público · archivo
GET/api/programas/:codePúblico
POST/api/programas/:code/repetirPúblico
POST/api/auth/loginPúblico
GET/api/hoyJWT CONDUCTOR

CORS: origen http://localhost:4200 en local.

[COMPROBADO] Superficie de dominio del encargo: programas, ?q=, :code, repetir, /hoy JWT. Health y login son el contrato de stack de la serie.
No se añaden Must extra (/nodos, /altas, /relevos, /directo).

Health: leftover relevo-apionda-api. Puerto leftover de RELEVO :3023 se reutiliza a propósito (mismo día, producto distinto). [DECISIÓN]

4. Justificación

ElecciónRazón
Angular + NestAlineación con la serie daily y el handoff del estudio
Neon serverlessVolumen bajo (S1: 2–4 programas/día; demo 4 × peticiones anónimas); no cola Redis
JWT CONDUCTORMesa interna cerrada; la galería sí es pública
Auth en servidor para /hoySource of truth = guard; el cliente no «esconde» recuentos
Puerto 3023Evitar colisión con RAMAL :3022 / TEMPLE :3021 / PREGÓN :3020; relevo retirado cede el puerto
Web (no PWA Must)S2: portal + mesa desktop; install Could L+1
code ON-XXXXAcuse humano, no UUID en la portería
POST vacío de repeticiónEscuchar ≠ suscribirse
pnpmEncargo; filtro por app

5. Variables

.env local (nunca git):

VariableServicioDescripción
DATABASE_URLAPINeon falling-fog-92644546
JWT_SECRETAPIFirma tokens
PORTAPIopcional, 3023
SESSION_TTL_SECONDSAPIopcional (default 86400)

URI canónica (demo local, no commitear en frontend):

postgresql://neondb_owner:npg_ZYutTBi1PQW0@ep-lively-bread-avlpx7l2-pooler.c-11.us-east-1.aws.neon.tech/neondb?sslmode=require

Región: c-11.us-east-1 · pooler · sslmode=require.
.env.example solo placeholders.
Web: URL de API en ApiService (default http://localhost:3023/api). Proxy Angular proxy.conf.json:3023.

6. Módulos API

health · auth · users · programas · hoy · database · common.

Health: HealthService.name = 'onda-api'.
main.ts PORT default 3023.

No módulos nodes, altas, mesa, relevos. Si el rewrite los encuentra, se borran o se sustituyen. [DECISIÓN]

7. Frontend

Páginas: GalleryPage / · SearchPage /buscar · ProgramaPage /programa/:code · OkPage /ok/:code · LoginPage /login · HoyPage /hoy.

Guard JWT en /hoy. Interceptor Bearer. Token key onda.accessToken.

8. Lo que el stack no es

No es un player. No es Spotify. No es la SER. No es Prisma obligatorio.
No es un leftover de nodos/altas: el contrato es programas + repeticiones + CONDUCTOR.
No es un leftover de recogidas: health no dice relevo-api.

07-creative-direction.md

07 — Dirección creativa — ONDA

1. Concepto

ONDA es un cartel de portería con cinta aislante amarilla: se entra a cuatro cubiertas y se sale habiendo pedido una repetición, no una landing de «tu radio». El ground es papel. El amarillo es la cinta, poco. El sello rojo dice en antena, poco.

Mood elegido: signage.
Primer instinto descartado: nocturnal / phosphor (estudio negro). Una onda se pega en el cristal con cinta amarilla, no se ve desde un mixer a oscuras.

[COMPROBADO] Mood, ground y paleta salen de docs/00-day-brief.md.

2. Territorio visual

EjeNo
SuperficiePapel #FFFFFF, tinta #111111, cinta #E6C200, rail #F3F3F3Hielo RAMAL, ink TEMPLE, fog PREGÓN, yeso MATIZ, water RELEVO, hormigón ZAGUÁN
TipoDM Serif Display + IBM Plex SansNewsreader+Figtree, Fraunces+Atkinson, Ibarra+Jost
FotoMicro con cinta, cassette y rundown, antena de azotea, estudio de porteríaEstudio negro de cadena, auriculares de Spotify, familia en el cole, mástil de operador como marca
GridGalería 16/10; lead + 3 cubiertas; rail izquierdoHero 2-col + 3 cards (AP-12) · feed de cards (RELEVO) · nodos a sangre (RAMAL)
AcentoMark #E6C200 en CTA, foco, cinta; seal #C41E3A en antenaAmber dashboard TEMPLE, cobre RAMAL, tidal RELEVO
ChromeRail Estudio / Buscar / HoySplit, dock, kiosk mínimo, wizard 1—2—3

3. Paleta

TokenHexRol
Ground / G-SIGN#FFFFFFPapel de cartel. Fondo de página.
Ink#111111Texto, wordmark, rotulador.
Mark#E6C200Cinta aislante / chrome yellow: CTA Pedir, foco, filete. Poco.
Mute#5C5C5CMeta, labels, hora secundaria, idle
Seal#C41E3ASello «en antena» y error. Poco. No pinta el header.
Rail#F3F3F3Fondo del S-RAIL. No es el ground de página.

Jerarquía de color: el papel carga; la cinta puntúa; el sello marca en antena. Si el mark pinta fondos enteros, deja de ser cinta y se vuelve «brand amarillo». El seal no decora el rail: solo puntual (EN_ANTENA, error). El texto sobre mark es ink, nunca mute. [DECISIÓN]

Candidatos de mood descartados: nocturnal/phosphor (primer instinto radio), rusted, mineral, chapel.

4. Tipografía

UsoFamiliaTamaños de referencia
WordmarkDM Serif Display 40024–28 desktop · 22 mobile
Titular de galería / hoyDM Serif Display 40032–44
Código ON-XXXXDM Serif Display 40028–36
Nombre de programaDM Serif Display 40022–28
Cuerpo / disclaimersIBM Plex Sans 40016–17 / 26–28 lh
UI, rail, form, listaIBM Plex Sans 400/500/60014 / 16
Meta (hora, host, status)IBM Plex Sans 60012–13, tracking 0.12em, ink o mute

Prohibido: DM Serif Display por debajo de 18px (se rompe). IBM Plex Sans en el wordmark de galería (se vuelve SaaS). Mark como color de texto de párrafo (contraste).

Fuentes vetadas de la serie reciente: Newsreader / Figtree / Fraunces / Atkinson Hyperlegible / Ibarra Real Nova / Jost / Libre Baskerville / Nunito / Instrument Serif / Sora / Cormorant / Public Sans / Spectral / Manrope / Zilla / Mulish / Outfit / Lora / Karla.

5. Fotografía

AssetDebeNo debe
hero.jpgEstudio de portería: mesa, mesa de mezclas, micro con cinta, patio al fondoRack de cadena, familia con auriculares, puerta del cole
micro.jpgMicro de mano con cinta aislante amarilla; ON AIR al fondoMicro de estudio nacional pulido
cinta.jpgCassette y rundown de radio comunitaria; rotuladorSpotify Wrapped, CD de major
antena.jpgAntena en azotea de barrio al atardecerMástil 5G de operador como marca; NOC

Alt: programa + objeto, no «imagen1». Ver doc 09.

La cubierta de ON-2302 y ON-2304 comparte cinta.jpg (archivo). El lead y el título distinguen. [COMPROBADO] en seed.

6. Registro R-HY

Lookbook de cubiertas (clásico de cartel) + herramienta de archivo (búsqueda y mesa). No es un dashboard de radio. No es un kiosk de horas. No es un feed de recogidas. No es un estudio negro.

La innovación está en H-GALLERY + S-RAIL + F-SEARCH, no en un look «radio phosphor». [DECISIÓN]

7. Anti-moods

MoodPor qué no
Nocturnal / phosphorPrimer instinto; es estudio de cadena
Ice institucionalFue RAMAL
Vehicle dashboardFue TEMPLE
Highlighter fogFue PREGÓN
Chapel amatistaFue MATIZ
Tidal / waterFue RELEVO (retirado)
Hormigón oxidadoFue ZAGUÁN

8. Densidad (AP-21)

En el recorte 16/10 de galería: eslogan + lead (Patio 17) + 3 cubiertas con hora, host y estado visibles sin hover. Headline huérfano + foto = card hueca. [COMPROBADO] CRON §5.8.

08-design-system.md

08 — Sistema de diseño — ONDA

1. Tokens

TokenValorUso
--ground#FFFFFFFondo de página (papel)
--ink#111111Texto
--mark#E6C200CTA, foco, cinta
--mute#5C5C5CMeta
--seal#C41E3AEn antena, error
--rail#F3F3F3Fondo S-RAIL
--font-displayDM Serif DisplayTitulares
--font-sansIBM Plex SansUI
--target44×44 minToques

Tailwind: bg-ground, text-ink, bg-mark, text-mute, text-seal, bg-rail, font-display.
No hay token --card distinto: la cubierta vive sobre papel; un filete ink/10 o mark basta. Si hace falta superficie de form, bg-ground + borde. [DECISIÓN]

2. Escala de tipo

RolSize / leading
Display galeríaclamp(2.4rem, 6vw, 4.4rem) / 0.95
H1 hoy / ficha3rem / 1
H2 programa28px / 32px
Cuerpo16–17 / 26
Meta12–13 / 16, tracking 0.12em, uppercase
Rail label11–12 / 14, IBM Plex 500

3. Componentes

Cubierta de programa (galería)

Foto 16/10 recortada + hora · host + título + lead + estado. Toda la card es el hit. Destino /programa/:code. Lead (Patio 17) ocupa el primer slot, más ancha en 1440.

Resultado de archivo (buscar)

Misma anatomía en fila: thumb 16/10 + título + hora + host + estado. Hit → ficha.

Ficha de programa

Cubierta, título display, dl hora/host/estado, bloque de copy irrenunciable, CTA Pedir repetición (ink sobre mark).

Acuse

Código DM Serif enorme + bloque mark con título y hora + 4 oposiciones en mute.

Fila de hoy

Código · status · título · hora · host · recuento. Sin acciones PATCH en v1.

Login

Wordmark + dos inputs + CTA mark. Ayuda mute con demo.

Rail (S-RAIL)

Tres slots: Estudio · Buscar · Hoy. Icono + label. Activo: filete mark 2px. Fondo --rail. Hoy guest → login.

Skeleton

animate-pulse bg-rail. aria-busy.

Error

Titular display + cuerpo mute + Reintentar mark.

Sello EN_ANTENA

Texto «En antena» + seal. Nunca solo el color. Poco: un sello por vista, no un header rojo.

4. Layouts

SuperficieGrid
Galería 1440Rail 64px + panel; lead 16/10 a ancho de panel; debajo 3 cols de cubiertas
Galería 390Cubiertas apiladas; rail inferior 56px
BuscarColumna max-w-3xl en el panel
Programa / Ok / loginColumna max-w-lg (ok/login) · max-w-2xl (ficha)
HoyLista en el panel; stacked < lg

Max page 1440px. Padding 20 / 40 / 48.

5. Estados de control

EstadoTratamiento
DefaultPapel, ink
Hover cubierta desktopRing 2px mark. Sin lift
FocusRing mark 2px
Disabledopacity 50–60
ErrorTexto seal, no solo color
Activo (rail)Filete mark
Pedida (CTA after 201)Navega a ok; no deja el botón en «Play»

6. Iconografía

Cero iconos decorativos de «play / auriculares / antena 5G / mochila escolar». El oficio es tipográfico + foto + cinta. Rail: tres pictogramas mínimos (estudio / lupa / lista), stroke ink, 24px, no pack de color. [DECISIÓN]

7. Motion

120–160ms ease-out. Reduced motion: 0ms. Skeletons pulse; no spinners de cadena ni de player.

8. Densidad (AP-21)

En el recorte 16/10 de galería: eslogan + lead + 3 cubiertas con hora, host y estado. [COMPROBADO] CRON §5.8.

9. Contraste de cinta

#E6C200 sobre #FFFFFF no es texto. CTA = text-ink sobre bg-mark. Meta = mute o ink sobre ground. Seal sobre ground ≥ 4.5:1 para el rótulo «En antena» (el sello lleva texto, no solo círculo).

09-content-guide.md

09 — Guía de contenido — ONDA

1. Voz

Cercana de portería y de mesa. Tuteo en UI al oyente. Segunda persona cuando la interfaz habla. Tercera cuando habla el hoy (Patio 17 ocupa…).

Sin jerga de cadena («tu parrilla», «directo nacional», «suscríbete»). Sin player. Sin «reserva tu slot». Sin sirena. Sin kanban en UI. Sin recogida escolar.

El producto se mira y se pide. No vende playlist, no es la SER, no reserva el estudio, no crea cuenta.

2. Palabras permitidas / prohibidas

UsarNo usar
Programa / cubierta / archivoTrack, episodio SKU, podcast network
Pedir repeticiónReservar estudio, coger sala, play, suscribirse
En antena / programado / archivoRecibido, En curso, En directo streaming, Publicado
HoyKanban, NOC, backoffice, turno, mesa (en UI)
ConductoraComercial, locutora de cadena, tutor, vocal
OyenteCliente, lead, abonado, tutor
Estudio (rail = galería)Reserva de sala, TEMPLE, slot
ONDA no es la SERCadena, dial nacional
Onda ≠ playlistSpotify, cola, shuffle

«Kanban», «F-SEARCH» y «H-GALLERY» son lenguaje de diseño, no de UI: Iker no lee «search pattern»; Nerea lee En antena / Programado / Archivo.

«Estudio» en el rail es la galería. Si hay ambigüedad en ficha, no usar «estudio» como CTA. [DECISIÓN]

3. Microcopy duro (literales irrenunciables)

El brief fija oposiciones. Aparecen donde el usuario puede confundirse.

OposiciónDóndeCopy
Onda ≠ playlistGalería · buscar · ficha · acuse«Onda no es una playlist. No hay cola ni play.»
Pedir repetición ≠ reservar estudioFicha · acuse · pie de hoy«Pedir repetición no reserva el estudio.»
ONDA no es la SERGalería · acuse«ONDA no es la SER. Es la radio del barrio.»
Escuchar ≠ suscribirseFicha · acuse · login«Pedir no crea una cuenta de oyente.»

[DECISIÓN] Los cuatro textos son obligatorios en UI. No se diluyen a un tooltip.
Pie de galería puede condensar dos en dos líneas; acuse muestra los cuatro.

4. Microcopy por superficie

SuperficieCopy
EsloganHoy suena esto.
Línea de lugarCarabanchel
Galería titularONDA
Cubierta dato 117:00 / 11:00 / 20:00 / 18:00
Cubierta dato 2Nerea / Iker / Nerea / Pilar (fuera)
Cubierta dato 3En antena / Archivo / Programado / Archivo
Buscar placeholderpatio
Buscar titularArchivo
Buscar submitBuscar
Ficha volver← Estudio
Ficha CTAPedir repetición
Login titularEntrar al hoy
Login ayudaSolo conductora. El oyente pide repetición en la galería, sin cuenta.
Login submitEntrar
Login errorEmail o contraseña no valen.
Empty galeríaAún no hay programas.
Empty buscarSin resultados.
Empty hoyNo hay programas hoy.
Success titularPedida.
Success códigoON-2301
Success cuerpoPedir repetición no reserva el estudio. Onda no es una playlist.
Success extraONDA no es la SER. Pedir no crea una cuenta.
Success CTAVolver al estudio
ErrorLa red no responde.
Error retryReintentar
Loading galeríaCargando
Loading buscarCargando
Loading hoyCargando
404 programaEse programa no está.
Hoy titularHoy
Rail EstudioEstudio
Rail BuscarBuscar
Rail HoyHoy
SalirSalir
Link Conductora (galería)Conductora
SelloEn antena
Status ARCHIVOArchivo
Status PROGRAMADOProgramado

5. Programas seed (tono)

Los programas demo se escriben como emisiones de barrio, no como ficha de cadena ni como anuncio de Spotify.

PiezaTono
Patio 17Patio. Micro con cinta. En antena.
Mercado UseraPregón. Cinta. Archivo. Iker al micro, no como cuenta.
Voces del bloqueAzotea. Antena. Programado a las 20:00.
Banda municipalArchivo. Pilar (fuera): oficio de antena, no login.

No inventar declaraciones de oyentes reales. Si hay una voz, es de persona de diseño etiquetada.

En ARCHIVO no se reescribe a «ya está en Spotify». El cierre es el estado. [DECISIÓN]

6. Nombres propios del seed

NombreUso
Nerea GilCONDUCTOR; nerea@onda.radio; Patio 17 y Voces del bloque
Iker SotoOYENTE; sin cuenta; también host de Mercado Usera (sin login)
Pilar RuizTÉCNICA; sin cuenta; host de Banda municipal (fuera)
ONDA · CarabanchelEmisora demo; Patio 17 / Mercado Usera

No inventar CIF de emisora, frecuencia de dial, ni streaming real como si fuera el producto. [SUPUESTO de catálogo demo]
Carabanchel / Usera son topónimos reconocibles, no un inventario oficial de una radio 2026.

7. Tono por estado

StatusFrase UIFrase que Nerea podría decir (fuera de app)
EN_ANTENAEn antena«Está sonando. No es un play de Spotify.»
PROGRAMADOProgramado«A las ocho. No es una reserva de sala.»
ARCHIVOArchivo«Ya sonó. Se puede pedir otra vez.»

La app no envía esos mensajes al oyente en v1. El copy solo prepara el habla. No hay email de «ya está en el aire».

8. Capitalización y números

  • Wordmark: ONDA en versales DM Serif Display.
  • Código: ON-2301 (ON versales, guion, 4 dígitos).
  • Nombres: Nerea Gil, Iker Soto, Pilar Ruiz.
  • Programas: Patio 17 (número en el título, no patio-17 en UI).
  • Hora: 17:00 en UI (24 h).
  • Fecha: 23 ago si hay línea larga.
  • No mezclar on-2301 en minúsculas en UI. API puede normalizar a versales.

9. Idioma

es-ES. Fácil lectura. EN no en v1.
No mezclar «playlist» ni «on air» en UI salvo el sello En antena. En API sí: programas, hostName, status.

«Oyente» en UI cuando hablamos de Iker; «la conductora» para Nerea; «la técnica» solo en docs y en el host «Pilar (fuera)». [DECISIÓN]

10. Accesibilidad de copy

  • No transmitir estado solo con color (el seal no «significa» en antena él solo).
  • Filas llevan texto: En antena / Programado / Archivo.
  • Aviso de no-estudio lleva palabras, no solo la cinta.
  • Alt de fotos: objeto + lugar, no «imagen1».
    • hero: «Estudio de radio de barrio en Carabanchel; mesa, micro y cinta amarilla, patio al fondo.»
    • micro: «Micrófono de mano con cinta aislante amarilla; cartel ON AIR al fondo. Cubierta de Patio 17.»
    • cinta: «Cassette y rundown manuscrito de radio comunitaria. Cubierta de archivo.»
    • antena: «Antena en una azotea de barrio al atardecer. Cubierta de Voces del bloque.»

11. Piezas que no se escriben

NoMotivo
«Pon play» / «Escuchar ahora»Onda ≠ playlist · S4
«Estudio reservado» / «Slot 18:00»Pedir ≠ reservar estudio
«Suscríbete» / «Follow»Escuchar ≠ suscribirse
«Como en la SER» / dial nacionalONDA no es la SER
Recibido / En curso / CerradoNo es kanban
«Nodo aceptado» / «Alta RM-»Leftover RAMAL
«Relevo recogido» / «El cole»RELEVO retirado
Lorem / «lorem ipsum programa»Prohibido en el case
Declaraciones inventadas de una emisora realVerdad de catálogo

10-accessibility.md

10 — Accesibilidad — ONDA

1. Alcance

WCAG 2.2 AA orientada al slice: galería, buscar, ficha, acuse, login, lista de hoy, rail. No es una auditoría certificada. [DECISIÓN]

2. Contraste

ParRelación esperada
Ink #111111 sobre papel #FFFFFF> 10:1
Ink sobre mark #E6C200Texto de CTA ≥ 4.5:1 (rotulador sobre cinta)
Mute #5C5C5C sobre papelMeta ≥ 4.5:1
Seal #C41E3A sobre papel«En antena» / error ≥ 4.5:1
Ink sobre rail #F3F3F3Labels del rail ≥ 4.5:1

Prohibido: texto mark sobre ground (cinta como cuerpo). El amarillo es superficie, no párrafo.

Texto sobre foto: gradient from-ink/80 bajo el titular de cubierta. No titular suelto sobre micro claro.

3. Teclado y foco

  • Orden: wordmark → rail (Estudio, Buscar, Hoy) → cubiertas / resultados → ficha CTA → form login → lista hoy.
  • Foco visible: ring mark 2px (ink si el fondo ya es mark).
  • Cubiertas y filas operables con Enter / Espacio.
  • Pedir repetición no se activa al pasar el foco.

4. Targets

≥44×44 en CTA, cubiertas, filas de hoy, slots del rail, link Conductora. Campo de búsqueda altura ≥44px.

5. Nombre accesible

ControlNombre
CubiertaTítulo del programa (h2)
Rail EstudioEstudio
Rail BuscarBuscar
Rail HoyHoy
Pedir repeticiónPedir repetición
Buscar submitBuscar
EntrarEntrar
SalirSalir

Fotos: alt de doc 09. Decorative? No: las cubiertas son contenido.

aria-current="page" en el slot activo del rail.

6. Estados no solo por color

En antena / Programado / Archivo en texto. Pedida: pantalla de acuse, no solo el botón en mark. Error: texto seal + mensaje. Empty búsqueda: «Sin resultados.»

7. Motion

prefers-reduced-motion: sin pulse. Skeletons estáticos. Cero animación de «ondas» decorativas.

8. Mobile 390

Galería: cubiertas apiladas (lead primero). No carousel.
Buscar: campo sticky bajo el wordmark; resultados en columna.
Ficha: una columna; CTA al fondo del fold o sticky inferior por encima del rail.
Hoy: lista a pantalla.
Rail: inferior, tres slots, no hamburger.

9. Autocomplete

Login: username / current-password. Buscar: type="search" · name="q". Sin autocomplete de dirección.

10. Idioma

html lang="es". Números y códigos leídos como texto (ON-2301). Horas 17:00 no se convierten a AM/PM.

11. Búsqueda

El campo tiene label visible o aria-label="Buscar en el archivo". Resultados anuncian recuento (1 programa / Sin resultados) vía texto, no solo el grid vacío.

11-privacy-security.md

11 — Privacidad y seguridad — ONDA

1. Superficie pública vs cerrada

RecursoAuthPII
GET programasNoNo (catálogo de emisiones)
GET programas ?q=NoNo
GET programas/:codeNoNo (host de programa es oficio, no oyente)
POST repetirNoNo (cuerpo vacío; no nombre)
GET /ok/:codeNo (solo código en URL)No hidrata peticiones ajenas
GET hoyJWT CONDUCTORRecuento de peticiones

[DECISIÓN] El acuse no lista otras peticiones. Evita enumerar demanda del barrio.

2. Datos de oyente

v1 no guarda nombre, email ni teléfono del oyente. La repetición es un contador por programa.
El oyente no tiene fila en users. Escuchar ≠ suscribirse.

Hosts del catálogo (Nerea, Iker, Pilar) son nombres de oficio en programas.host_name, no cuentas salvo Nerea.

3. Auth

  • JWT HS256. Claims: sub userId, sid session, role.
  • Password bcrypt 12. Demo password123 solo en seed local. Hash canónico $2b$12$OEKcNck63neDJ.CrVP0lNOaXp2NG2zd2aMiGmnYY221h4ZAZ58x1m.
  • Guard: Bearer + sesión no revocada.
  • Role CONDUCTOR para /hoy. 401 sin token, 403 si no CONDUCTOR.
  • Rate limit en login.

4. Secretos

DATABASE_URL y JWT_SECRET en apps/api/.env (gitignore). Nunca en Angular.
La URI Neon del encargo es para el rewrite local; no se embebe en el bundle.

5. CORS

Allowlist localhost:4200. Previews Vercel opcionales.

6. Amenazas del slice

AmenazaMitigación
Enumerar /ok/ON-2301 y ver peticionesOk no GET de repeticiones; state de navegación + catálogo público
POST spam de repeticionesVolumen demo; rate Could L+1
Token en localStorageXSS = compromiso; slice L1 acepta el tradeoff de SPA [DECISIÓN]
Leftover TUTOR / VOCAL en el mismo JWTSeed solo CONDUCTOR; claims role=CONDUCTOR
Login con email de PilarNo existe la fila

7. RGPD (modelo, no dictamen)

Base: interés de la emisora en gestionar el archivo y las peticiones. Minimización: cero PII de oyente en v1. Sin cesión a cadena. Derecho de supresión Could L+1 (Nerea no borra en v1).

Pilar no tiene acceso. No hay logs de «quién subió a la antena».

8. Lo que no hacemos

No OAuth. No cookies de marketing. No analytics de terceros en v1. No cifrado extra más allá de TLS. No cuenta de oyente «por si acaso».

12-analytics.md

12 — Analítica — ONDA

1. North star

% de peticiones de repetición servidas en 48 h.

Oficio de emisora. No pageviews. No «engagement con el micro». Servir = Nerea pone el programa otra vez (fuera de app en v1; el modelo cuenta el oficio, no un PATCH). [DECISIÓN]

En v1 no hay estado SERVIDA: la north star es de producto, no un campo SQL. Could L+1: servida_at.

2. Métricas de producto (modelo)

MétricaDefiniciónHipótesis
Activación oyentePOST /api/programas/:code/repetir 201 / visitas a /H1
Uso de archivoGET /api/programas?q= con q no vacío / visitasF-SEARCH
Tiempo a acuse< 90 s en demoDiseño
Tiempo a lectura de hoyGET hoy desde loginOficio
Integridad auth401 en GET hoy anónimoSeguridad
ExpectativaTickets «¿dónde está el play?» / «¿me reserváis el estudio?» (fuera de app)H2

3. Eventos (si se instrumentara)

No hay tracker en v1. Contrato de nombres por si L+1:

EventoProps
galeria_viewprogramas_count
buscar_queryq, results_count
repetir_submitcode
hoy_openprogramas_count

Prohibido: play, subscribe, studio_booked, pickup_done, alta_submit.

4. Lo que no medimos

Minutos de streaming. Tiempo en antena. Aperturas de WhatsApp (no hay dato). NPS de cadena. Recogidas escolares.

5. Salud

GET /api/health{ ok: true, service: "onda-api" }. Error rate 4xx/5xx en programas/repetir/hoy.
Si health dice relevo-api o ramal-api, el slice no está cerrado.

13-qa-test-plan.md

13 — Plan de QA — ONDA

1. Smoke API (pnpm test:api)

  1. GET /api/healthonda-api (no relevo-api, no ramal-api)
  2. GET /api/programas → 4 codes ON-2301…ON-2304 + lead + hora + host + status + cover
  3. GET /api/programas/ON-2301 → Patio 17 · EN_ANTENA · 17:00 · Nerea
  4. GET /api/programas?q=patio → incluye ON-2301; no exige los 4
  5. GET /api/programas?q=zzzz[]
  6. POST /api/programas/ON-2301/repetir → 201 pedida: true
  7. POST /api/programas/ON-9999/repetir → 404
  8. GET /api/hoy sin JWT → 401
  9. POST /api/auth/login Nerea → CONDUCTOR
  10. GET /api/hoy → 4 programas; ON-2301 EN_ANTENA; repeatCount ≥ 1 tras el POST

2. Smoke UI

#PasosEsperado
1Abrir /4 cubiertas, lead Patio 17, eslogan, rail
2Tap Patio 17/programa/ON-2301 · En antena · 17:00 · Nerea
3Pedir repetición/ok/ON-2301 + 4 oposiciones
4/buscar + «patio»Patio 17 en resultados
5/buscar + «zzzz»«Sin resultados.»
6/login Nerea/hoy lista
7Leer ON-2301En antena; no botones Aceptar/Rechazar RAMAL
8LogoutGalería; /hoy pide login
9/ 390cubiertas apiladas, no carousel; rail inferior
10API downError + Reintentar

3. Regresión de unicidad

  • No copy RAMAL (nodo, alta, vocal, RM-, azotea como producto, cobertura).
  • No copy TEMPLE (hora de ensayo, concierto, conserje, turno, TM-).
  • No copy PREGÓN (chat, kanban, 112, parte).
  • No copy RELEVO (relevo, recoger, cole, tutor, feed de recogidas). Retirado.
  • No Spotify, no play, no la SER como CTA.
  • Health no dice relevo-api ni ramal-api.
  • Puerto 3023.

4. Accesibilidad rápida

Tab por rail y cubiertas. Focus ring mark. Alt de 4 fotos. Status en texto. CTA ink sobre mark.

5. Datos

Seed idempotente por email y code. Un POST de smoke no borra ON-2301 ni cambia su status.
Playwright futuro: esperar Patio 17, nerea@onda.radio, no Inés Soto, no RM-2201, no Lola, no «recoger».

6. Criterio de salida

Smoke API verde + guión UI completo + 0 leftover de dominio anterior en UI (RAMAL o RELEVO).

14-dev-handoff.md

14 — Handoff de desarrollo — ONDA

1. DoD del slice

  • Health { ok: true, service: "onda-api" } en :3023
  • 4 programas seed con foto, lead, hora, host, status
  • GET /api/programas?q=patio → Patio 17
  • POST repetición pública → acuse /ok/:code
  • JWT CONDUCTOR nerea@onda.radio / password123
  • /hoy lista · GET JWT · no kanban
  • Empty / error / loading en galería, buscar y hoy
  • Copy ×4 visible
  • Rutas / /buscar /programa/:code /ok/:code /login /hoy
  • Páginas GalleryPage SearchPage ProgramaPage OkPage LoginPage HoyPage
  • Paper 12 UX + 12 UI mapeados
  • Docs 01–20 + README + executive
  • 0 leftover nodes / altas / VOCAL / relevo-api / TUTOR

2. Contratos

Ver docs 05 y 20. JSON camelCase. SQL snake_case.

POST /api/programas/:code/repetir:

{}

201:

{ code, title, status, hour, hostName, pedida: true }

GET /api/hoy (JWT):

{ date, programas: [{ code, title, status, hour, hostName, cover, repeatCount, sortOrder }] }

Login:

{ email: "nerea@onda.radio", password: "password123" }

3. Mapas de pantalla

PaperRutaComponente
UI-01 Gallery/GalleryPage
UI-02 Gallery mobile/ 390GalleryPage
UI-03 Search/buscarSearchPage
UI-04 Programa/programa/:codeProgramaPage
UI-05 Programa mobile/programa/:code 390ProgramaPage
UI-06 Success/ok/:codeOkPage
UI-07 Login/loginLoginPage
UI-08 Mesa hoy/hoyHoyPage
UI-09 Emptymismas rutasempty branches
UI-10 Errormismas rutaserror branches
UI-11 Loadingmismas rutasskeletons
UI-00 Tokenstailwind.config.js + styles.scss

4. Tokens en código

tailwind.config.js + styles.scss. Fuentes Google: DM Serif Display + IBM Plex Sans.
Colores: ground #FFFFFF · ink #111111 · mark #E6C200 · mute #5C5C5C · seal #C41E3A · rail #F3F3F3.

5. Assets

Copiar 2026-08-23-onda/assets/programas/*.jpg y hero.jpg a apps/web/public/assets/.
Cubiertas: programas/micro.jpg · cinta.jpg · antena.jpg.

6. No hacer

  • No reintroducir nodes / altas / VOCAL como dominio vivo.
  • No reintroducir relevos, recogidas, TUTOR, cole. RELEVO está retirado.
  • No documentar leftover RAMAL o RELEVO como estado actual.
  • No añadir signup de oyente, player, chat, kanban, reserva de estudio, parrilla semanal.
  • No dejar HealthService.name = 'relevo-api'.

7. Arranque

cd /Users/cristian/orca/onda-app
# apps/api/.env → DATABASE_URL Neon falling-fog-92644546, JWT_SECRET, PORT=3023
pnpm --filter @repo/api start:dev
pnpm --filter @repo/web start

El rewrite corre en paralelo. Este handoff es el contrato; no se espera a Paper ni se arranca aquí.

15-roadmap.md

15 — Roadmap — ONDA

Aquí solo viven ideas de L+1 / L4 y pulido posterior.
El slice L1 del día está cerrado: galería, buscar, ficha, pedir, JWT CONDUCTOR, lista de hoy.

1. Hecho (L1)

  • Galería H-GALLERY · 4 cubiertas · lead + 3
  • Búsqueda F-SEARCH · q=patio
  • Ficha · título, hora, estado
  • Acuse /ok/:code · pedir ≠ reservar
  • Hoy S-RAIL · lista, no kanban
  • Empty / error / loading · galería 390
  • Neon falling-fog-92644546 · API :3023 · onda-api

2. L+1 (mismo producto, más oficio)

ItemNota
servida_at en repeticiónNorth star medible en SQL
Filtro de hoy por estado4 filas no lo necesitan; 20 sí
Rate limit POST repetirDemo no lo necesita
Recibo por… no: sigue sin cuentaNo email de oyente
PWA installPortería; no es Must
Trigram search4 filas no lo necesitan

3. L4 (requiere brief nuevo de diversidad)

  • Rol TÉCNICA (Pilar) + cuadrante de antena
  • Streaming / player (otro producto; no es ONDA L1)
  • Parrilla semanal / EPG
  • Multi-emisora / multi-conductor
  • Chat / kanban (fue PREGÓN)
  • Reserva de estudio (fue TEMPLE)
  • Cuenta de oyente / follow

4. Fuera para siempre en este nombre

La SER. Playlist como producto. Reserva de estudio el día de la petición. Feed de recogidas (RELEVO retirado). Nodos y altas (RAMAL es otro día).

5. Criterio para abrir L+1 / L4

Solo si un día futuro elige radio L2+ y la diversidad ALS-2 lo permite. No parche silencioso. No reabrir RELEVO.

16-interaction-specs.md

16 — Especificación de interacción — ONDA

1. Convenciones

TokenValor
Duración corta120–160ms
Easingease-out
Reduced motion0ms
Target≥44×44
Pendingcontrol disabled + aria-busy

Loading (UI-11) y empty (UI-09) / error (UI-10) viven con los gestos. [DECISIÓN]


GestoResultado
Primera cargaLoading: 4 tiles rail/papel; aria-busy
200 con 4Lead Patio 17 16/10 + 3 cubiertas. Hora, host, estado
200 con 0UI-09 empty
Error 5xx / redUI-10 + Reintentar (repite GET programas)
Tap cubierta/programa/:code
Tap Conductora (ghost)/login o /hoy si hay token
Hover cubierta desktopRing 2px mark. Sin lift
Rail Estudio/ (activo)
Rail Buscar/buscar
Rail Hoy/hoy o login

AC: título, lead y 3 datos visibles sin hover. 4 programas en el recorte 16/10 (AP-21). Cero botones Play / Suscríbete / Reservar.

El aviso «Onda ≠ playlist» es texto, no un CTA de player. [DECISIÓN]

390: mismas 4 cubiertas apiladas. No carousel. Rail inferior. [DECISIÓN]


GestoResultado
/buscar sin qCampo + prompt de archivo; no lista los 4 (eso es la galería)
Submit / debounce qGET /api/programas?q= · pending
200 con hitsFilas thumb + título + hora + host + estado
200 []UI-09 «Sin resultados.»
ErrorUI-10
Tap resultado/programa/:code
Query «patio»Incluye Patio 17

AC: no facetas de distrito. No H-SEARCH como home.


4. Programa (UI-04) + mobile (UI-05)

GestoResultado
Primera cargaLoading: skeleton de ficha
200Cubierta, título, hora, host, estado, copy × oposiciones, CTA
404«Ese programa no está.»
Tap Pedir repeticiónPOST · pending en botón
201/ok/:code con state
Error redseal + mensaje; no navega
Tap ← Estudio/

AC: una pantalla. Cero player. Cero «Reservar 18:00».


5. Acuse (UI-06)

GestoResultado
Con códigoDM Serif ON-XXXX + bloque mark + 4 oposiciones
Sin código«No hay ese código» + Volver al estudio
CTA/ galería

No GET de lista de peticiones. [DECISIÓN] doc 11.


6. Login (UI-07)

GestoResultado
SubmitPOST login · pending
200/hoy o next seguro (/hoy only)
401«Email o contraseña no valen.»
Ya hay tokenRedirect hoy

next open-redirect: allowlist /hoy únicamente. [DECISIÓN]


7. Hoy (UI-08)

GestoResultado
GET hoy loading4 skeletons
200 con programasLista. EN_ANTENA primero
200 []UI-09
ErrorUI-10
Tap filav1: no hay ficha; el gesto no navega a /programa salvo Could. [DECISIÓN] lectura
Salirlogout → /

Desktop: lista en el panel del rail. 390: lista a pantalla. [DECISIÓN]
Cero Aceptar / Rechazar (eso fue RAMAL). Cero columnas.


8. Loading / empty / error (UI-09…11)

Skeletons pulse. Empty usa titular display, no un illustration pack. Error no usa copy de 112 ni de recogida.

17-prototype-map.md

17 — Mapa de prototipo — ONDA

Paper file: 01M0TSYSNQFZ8FVKD38ZGQT8AW
https://app.paper.design/file/01M0TSYSNQFZ8FVKD38ZGQT8AW

Este mapa es el contrato Paper ↔ docs ↔ app. No se arranca Paper desde esta suite. El archivo ya existe.

UX process → docs

PaperDoc
UX-00 CoverREADME + 01
UX-01 Stakeholders02 §3
UX-02 Personas02 §4
UX-03 JTBD02 §5
UX-04 Stories02 §6
UX-05 Journey02 §7 · 04
UX-06 Blueprint02 §8 · 06
UX-07 Site map03
UX-08 Flujos04
UX-09 Datos+permisos05 · 11
UX-10 Métricas12
UX-11 Research02

UI producto → app

PaperRutaComponente
UI-00 Tokenstailwind.config.js + styles.scss
UI-01 Gallery/GalleryPage
UI-02 Gallery mobile/ 390GalleryPage
UI-03 Search/buscarSearchPage
UI-04 Programa/programa/:codeProgramaPage
UI-05 Programa mobile/programa/:code 390ProgramaPage
UI-06 Success/ok/:codeOkPage
UI-07 Login/loginLoginPage
UI-08 Mesa hoy/hoyHoyPage
UI-09 Emptymismas rutasempty branches
UI-10 Errormismas rutaserror branches
UI-11 Loadingmismas rutasskeletons

Guión de prototipo

  1. Cover → Galería (4 cubiertas, lead Patio 17).
  2. Ficha Patio 17 en antena.
  3. Acuse ON-2301 · pedir ≠ reservar.
  4. Buscar «patio».
  5. Login Nerea.
  6. Hoy · lista · ON-2301.
  7. Empty / error / loading.
  8. Galería 390 + ficha 390.

No hay artboard de player, nodos, altas, kiosk de horas, kanban ni feed de recogidas.

18-completeness-audit.md

18 — Auditoría de cierre — ONDA

1. Alcance L1

PiezaEstado
Docs 01–20 + README + executiveOK (este set)
00-day-brief / 00-paper-referenceIntactos (no tocados)
Paper 12 UX + 12 UIInventario en 00 (no se arranca Paper aquí)
App path /Users/cristian/orca/onda-appOK · rewrite en paralelo
Neon falling-fog-92644546OK
API :3023 · health onda-apiContrato (leftover relevo-api prohibido)
Código programas + repeticiones + JWT CONDUCTORContrato paralelo
Cierre sin deuda del L1OK · roadmap solo L+1 / L4

2. Terna y diversidad

CheckEvidencia
R-HY · S-RAIL · H-GALLERY · F-SEARCHBrief + 01 + 07
0 códigos = RAMALRAMAL era R-CL · S-SPLIT · H-FULL · F-ONB
≥2 vs TEMPLE / PREGÓNRail/Gallery/Search vs kiosk/book y chat/kanban
S-RAIL, H-GALLERY, F-SEARCH no en últimos 5Brief
Ventana 4: R-HYR-IN + R-HY + R-CL + R-HY
Ground G-SIGN #FFFFFF00 + 07 + Tailwind
Sustituye RELEVO0 feed de recogidas; anti-patrón explícito

3. Copy irrenunciable

Presente en galería, ficha, acuse, pie de hoy. Ver doc 09.

  • Onda ≠ playlist
  • Pedir repetición ≠ reservar estudio
  • ONDA no es la SER
  • Escuchar ≠ suscribirse

4. Anti-patrones vigilados

APMitigación
AP-12 hero 2-colH-GALLERY cubiertas
AP-21 card huecaLead + hora/host/estado
Leftover como estadoContrato ONDA; no se anota RAMAL ni RELEVO como producto vivo
Entrevistas inventadas02 etiquetado
Recogida escolarSolo como anti-patrón (retirado)
nodes/alta/VOCAL0 en dominio; 05 + 20

5. Seed

4 programas · 1 EN_ANTENA · 1 PROGRAMADO · 2 ARCHIVO · Nerea CONDUCTOR · Iker sin cuenta · Pilar fuera.

codetitlestatus
ON-2301Patio 17EN_ANTENA
ON-2302Mercado UseraARCHIVO
ON-2303Voces del bloquePROGRAMADO
ON-2304Banda municipalARCHIVO

6. Huecos conscientes (fuera)

Streaming, login Pilar, email transaccional, instrumentación analytics, parrilla semanal, servida_at.

7. Veredicto

Slice L1 cerrado a nivel de case. El código se escribe en paralelo con el mismo contrato. Si el repo diverge (leftover RAMAL o RELEVO), gana docs 05 + 20 tras alinear ambos. Paper no se abre en esta entrega de docs.

19-backlog-completo.md

19 — Backlog — ONDA

1. Done (Must L1)

IDItem
D01Galería 4 cubiertas H-GALLERY
D02Buscar archivo F-SEARCH
D03Ficha /programa/:code
D04Acuse /ok/:code · POST repetir
D05Login JWT CONDUCTOR
D06Hoy lista, no kanban
D07Empty / error / loading
D08Galería 390
D09Seed 4 programas
D10Health onda-api :3023
D11Copy ×4
D12Docs 01–20 + README + executive

2. Could L+1 (no bloquea cierre)

IDItem
C01servida_at / marcar servida
C02Filtro hoy por estado
C03Rate limit POST repetir
C04PWA
C05Trigram search

3. Explícito L4 / otro brief

IDItem
E01Login Pilar / cuadrante de antena
E02Streaming / player
E03Parrilla semanal
E04Multi-emisora
E05Chat / kanban
E06Reserva de estudio

4. No-backlog (rechazado)

Hero 2-col. Signup oyente. leftover RAMAL (nodes, altas, VOCAL) como feature. leftover RELEVO (recogida, cole, TUTOR) como feature. RELEVO retirado. La SER. Playlist. «Reservar estudio».

5. Cómo usar este archivo

Done = el día. Could = L+1. E = otro cron. Nada de esta lista es deuda del L1.

20-implementation.md

20 — Implementación — ONDA

1. Resumen ejecutivo técnico

CampoValor
App path/Users/cristian/orca/onda-app
APINestJS · puerto 3023 · prefijo /api
WebAngular standalone · puerto 4200
Packageapps independientes (pnpm --filter)
DBNeon PostgreSQL · project falling-fog-92644546
Tablasusers, programas, repeticiones, sessions
AuthJWT Bearer · role CONDUCTOR (/hoy); galería, búsqueda y POST repetir públicos
DominioUser, Programa, Repeticion
Fecha2026-08-23
Healthonda-api (leftover relevo-api → sustituir)

Este documento es la especificación de build alineada al case. No sustituye al código: si el repo diverge, gana el contrato de docs 05 + este archivo tras actualizar ambos.

El código se escribe en paralelo a esta suite. Contrato: programas + repeticiones + JWT CONDUCTOR. Puerto 3023. Health onda-api.

[COMPROBADO] Path, puerto, Neon y Paper salen del encargo.
No se arranca la app ni Paper desde esta entrega de docs.

El repo puede contener leftover RAMAL (nodes, altas, HomePage de nodos) al inicio del rewrite. Eso no es ONDA. El rewrite borra ese dominio.

2. Cómo arrancar

cd /Users/cristian/orca/onda-app
# instalar dependencias por app (pnpm --filter; no npm workspaces)

# apps/api/.env
#   DATABASE_URL=postgresql://neondb_owner:npg_ZYutTBi1PQW0@ep-lively-bread-avlpx7l2-pooler.c-11.us-east-1.aws.neon.tech/neondb?sslmode=require
#   JWT_SECRET=
#   PORT=3023

# schema + seed
# api → http://localhost:3023
# web → http://localhost:4200

Credenciales

RolEmailPassword
CONDUCTORnerea@onda.radiopassword123

Hash seed: $2b$12$OEKcNck63neDJ.CrVP0lNOaXp2NG2zd2aMiGmnYY221h4ZAZ58x1m

Oyente / técnica: sin cuenta. Iker pide en /programa/:code. Pilar no entra.

3. Schema

Al boot o vía migrate:

  1. CREATE TABLE de users, sessions, programas, repeticiones (doc 05).
  2. Índices: programas(code), programas(status), users(email), repeticiones(programa_id).
  3. Seed idempotente:
    • upsert CONDUCTOR nerea@onda.radio (hash bcrypt canónico)
    • upsert programas por code (ON-2301ON-2304)
    • no seed de repeticiones (el POST de smoke las crea)
  4. Dominio: solo programas + repeticiones + users. [DECISIÓN]

No borrar repeticiones nuevas de Iker en cada boot. Upsert de programas por code no pisa status si un operador futuro lo moviera (v1 no mueve).

No generar códigos en el POST: el acuse reusa ON-2301.

Tablas (recordatorio)

TablaContenido
usersCONDUCTOR Nerea
programas4 emisiones
repeticionesPeticiones anónimas (vacío al seed)
sessionssid + expires_at

DROP leftover nodes, altas, relevos si existen en el mismo Neon. [DECISIÓN] El project falling-fog-92644546 es de ONDA.

4. Módulos API a implementar

Auth

  • POST /api/auth/login
  • Valida email/password; compara bcrypt; emite JWT con sub, email, role.
  • Guard JWT + role === CONDUCTOR protege /hoy.
  • Públicos: health, programas, programas?q=, programas/:code, POST repetir, login.

Oficio

MétodoRutaAuthNotas
GET/api/healthNo{ ok: true, service: "onda-api" }
GET/api/programasNosort_order asc + lead + hora + host + status + cover
GET/api/programas?q=NoILIKE title/host/code/lead
GET/api/programas/:codeNoficha
POST/api/programas/:code/repetirNoINSERT repeticion; 404 si no hay programa
GET/api/hoyJWT4 programas + repeatCount; EN_ANTENA primero

Health (referencia de implementación)

HealthService.name = 'onda-api'
main.ts PORT default 3023

Si el rewrite hereda relevo-api, cambiar el string. El puerto 3023 se queda.

Repetición (referencia)

programa = programas.byCode(code) || 404
INSERT repeticiones (programa_id)
return { code, title, status, hour, hostName, pedida: true }

POST no cobra, no reserva estudio, no crea user, no abre stream, no mueve kanban.

JSON camelCase: hostName, sortOrder, createdAt, repeatCount.
SQL: host_name, sort_order, created_at.

Módulos de dominio: programas/, hoy/, auth/, health.
No nodes/, altas/, mesa/, relevos/.

5. Frontend a implementar

PáginaRutaResponsabilidad
GalleryPage/H-GALLERY · GET programas · lead + 3
SearchPage/buscarF-SEARCH · GET ?q=
ProgramaPage/programa/:codeFicha · POST repetir
OkPage/ok/:codeAcuse · state de navegación
LoginPage/loginJWT CONDUCTOR
HoyPage/hoyLista · GET hoy

Estados: loading / ready / empty / error en galería, buscar y hoy.
Token key: onda.accessToken.
Proxy: /api:3023.

Tokens Tailwind: ground / ink / mark / mute / seal / rail. Fuentes DM Serif Display + IBM Plex Sans.

Rail compartido: Estudio / · Buscar /buscar · Hoy /hoy.

Renombrar leftover HomePage / AltaPage / MesaPage → las seis páginas de arriba. [DECISIÓN]

6. Seed visible en UI

CódigoTítuloStatusHoraHostCover
ON-2301Patio 17EN_ANTENA17:00Nerea/assets/programas/micro.jpg
ON-2302Mercado UseraARCHIVO11:00Iker/assets/programas/cinta.jpg
ON-2303Voces del bloquePROGRAMADO20:00Nerea/assets/programas/antena.jpg
ON-2304Banda municipalARCHIVO18:00Pilar (fuera)/assets/programas/cinta.jpg

Playwright: esperar Patio 17, Hoy suena esto., nerea@onda.radio.
No esperar Inés Soto, Azotea Palomeras, RM-2201, recoger, tutor.

7. Smoke

apps/api/scripts/test-api.mjs · pnpm test:api contra :3023.

Cubre health onda-api, 4 programas, q=patio, POST repetir, 404 code, 401 hoy, login CONDUCTOR, lista 4, repeatCount.

8. DoD técnico

Ver doc 14. Este archivo no lista leftover de otro día como estado del producto. El contrato vivo es ONDA. RELEVO está retirado.