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
| Campo | Valor |
|---|---|
| Fecha | 2026-08-23 |
| Nombre | ONDA |
| Complejidad | Nivel 1 (no L3 consecutivo tras RAMAL) |
| Sector | Radio comunitaria / archivo de emisiones (Carabanchel) |
| Tipo | Galería de programas + búsqueda en archivo + mesa CONDUCTOR JWT |
| Plataforma | Web responsive (galería en el móvil · rail de búsqueda · mesa desktop) |
| Registro | R-HY — lookbook de cubiertas + herramienta de archivo |
| Shell | S-RAIL — rail Hoy / Buscar / Estudio |
| Home | H-GALLERY — cubiertas de 4 programas; lead + 3 en el recorte 16/10 |
| Flujo | F-SEARCH — el archivo se busca; pedir repetición no reserva el estudio |
| Por qué no RELEVO | No familias, no S-DOCK, no H-FEED, no F-STORY, no tidal |
| Por qué no RAMAL | No S-SPLIT / H-FULL / F-ONB; no telecom |
| Por qué no TEMPLE | No kiosk de horas; repetición ≠ coger sala |
| Por qué no PIZARRA | No 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:
#FFFFFFpapel de cartel (ground)#111111tinta de rotulador#E6C200cinta aislante / chrome yellow (mark, poco)#5C5C5Cmute#C41E3Asello de «en antena» (poco)#F3F3F3rail
- Tipo: DM Serif Display · IBM Plex Sans
Alcance L1 must
- Home galería: 4 programas (lead + 3 visibles)
- Buscar en archivo (query + resultados)
- Ficha de programa (título, hora, estado)
- Pedir repetición (pedir ≠ reservar estudio)
- Login JWT CONDUCTOR
- Mesa de hoy (lista, no kanban)
- Empty / error / loading / mobile gallery
- ≥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
| ID | Señal |
|---|---|
| H1 | Una galería de cubiertas reduce «¿qué hay hoy?» por WhatsApp |
| H2 | Separar pedir repetición de reservar estudio evita el conflicto con TEMPLE |
| H3 | Cuatro programas bastan; no hace falta parrilla de la semana |
00-paper-reference.md
Paper reference · ONDA
| URL | https://app.paper.design/file/01M0TSYSNQFZ8FVKD38ZGQT8AW |
| File ID | 01M0TSYSNQFZ8FVKD38ZGQT8AW |
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)
| ID | Nombre | Contenido |
|---|---|---|
| UX-00 | Cover | ONDA · «Hoy suena esto.» · R-HY · S-RAIL · H-GALLERY · F-SEARCH · L1 radio · signage · 2026-08-23 |
| UX-01 | Stakeholders | CONDUCTOR Nerea, OYENTE Iker, técnica Pilar fuera, SER fuera |
| UX-02 | Personas | Nerea Gil 34 · Iker Soto 52 · Pilar Ruiz 41 (docs) |
| UX-03 | JTBD | Job «encontrar el programa de esta tarde y pedir la repetición sin el grupo» |
| UX-04 | Stories | Must: galería, buscar, ficha, pedir, login, mesa, estados |
| UX-05 | Journey | Portería → galería → Patio 17 → pedir repetición |
| UX-06 | Blueprint | Frontstage cartel · backstage estudio/antena · Nest/Neon/JWT |
| UX-07 | Site map | / gallery · /buscar · /programa/:code · /ok/:code · /login · /hoy |
| UX-08 | Flujos | F-SEARCH público; mesa CONDUCTOR |
| UX-09 | Datos | User CONDUCTOR · Programa; JWT en /hoy |
| UX-10 | Métricas | North star % de peticiones de repetición servidas en 48 h |
| UX-11 | Research | Comprobado / supuesto / hipótesis / decisión |
UI producto (prefijo UI- · mínimo 10)
| ID | Nombre | Flujo |
|---|---|---|
| UI-00 | Tokens | DS signage: paper, ink, tape yellow |
| UI-01 | Gallery | H-GALLERY 1440: lead + 3 cubiertas. Rail |
| UI-02 | Gallery mobile | 390 |
| UI-03 | Search | Archivo; query «patio» |
| UI-04 | Programa | Ficha Patio 17 en antena |
| UI-05 | Programa mobile | 390 |
| UI-06 | Success | Pedida. Pedir ≠ reservar estudio |
| UI-07 | Login | JWT CONDUCTOR |
| UI-08 | Mesa hoy | Lista de hoy |
| UI-09 | Empty | Sin resultados |
| UI-10 | Error | Red |
| UI-11 | Loading | Skeletons |
Mapeo Paper → Angular
| Artboard | Ruta | Componente |
|---|---|---|
| UI-01 Gallery | / | GalleryPage |
| UI-03 Search | /buscar | SearchPage |
| UI-04 Programa | /programa/:code | ProgramaPage |
| UI-06 Success | /ok/:code | OkPage |
| UI-07 Login | /login | LoginPage |
| UI-08 Mesa | /hoy | HoyPage |
01-project-definition.md
01 — Definición de proyecto — ONDA
1. Identidad
| Campo | Valor |
|---|---|
| Nombre | ONDA |
| Significado | Onda 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 frase | Galería de cubiertas + búsqueda en archivo + mesa CONDUCTOR JWT (lista de hoy). |
| Sector | Radio comunitaria / archivo de emisiones (Carabanchel) |
| Tipo | Web L1 — galería H-GALLERY + búsqueda F-SEARCH + mesa rail — Nivel 1, herramienta de archivo usable |
| Plataforma | Web responsive (galería en el móvil · rail de búsqueda · mesa desktop) |
| Mercado demo | España · Madrid · Carabanchel (una emisora de barrio, no una cadena) |
| Idioma | es-ES |
| Fecha caso | 2026-08-23 |
| Terna | R-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
| Problema | Quién lo sufre | Efecto |
|---|---|---|
| «¿Qué hay hoy?» por WhatsApp | OYENTE (Iker) | Espera, se pierde Patio 17, no pide repetición |
| Galería se lee como playlist | OYENTE | Espera cola, shuffle, suscripción |
| Pedir repetición se lee como reservar estudio | OYENTE + Nerea | Esperan un slot de grabación (eso fue TEMPLE) |
| La mesa se lee como kanban | CONDUCTOR (Nerea) | Esperan Recibido / En curso / Cerrado |
| La búsqueda se lee como catálogo municipal | Iker | Esperan talleres del distrito (eso fue PIZARRA) |
| El dueño pide player y parrilla semanal el día 1 | Emisora | El 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
| ID | Hipótesis | Señal de validación (futura) |
|---|---|---|
| H1 | Una galería de cubiertas reduce «¿qué hay hoy?» por WhatsApp | % de visitas a / que terminan en POST /repetir |
| H2 | Separar pedir repetición de reservar estudio evita el conflicto con TEMPLE | ↓ «pensé que me guardabais la sala»; copy visible en acuse |
| H3 | Cuatro programas bastan; no hace falta parrilla de la semana | 0 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
| Para | Valor |
|---|---|
| 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 ONDA | Una superficie mínima: galería + archivo + mesa, sin playlist y sin cadena. |
No es ONDA
| Excluido | Por qué |
|---|---|
| Playlist / cola / shuffle / Spotify | Onda ≠ playlist |
| Reservar estudio / slot de grabación | Pedir repetición ≠ reservar estudio |
| Cadena nacional / la SER / podcast network | ONDA no es la SER |
| Cuenta de oyente / newsletter / follow | Escuchar ≠ suscribirse |
| Player embebido / streaming en vivo | S4 |
| Chat de oyentes / kanban | Eso fue PREGÓN (H-CHAT / F-KAN) |
| Quiosco de horas / bloques de 2 h | Eso fue TEMPLE (H-KIOSK / F-BOOK) |
| Catálogo municipal / talleres / distrito | Eso fue PIZARRA; ONDA no es el distrito |
| Feed de recogidas / familias / cole | RELEVO, retirado. Anti-patrón. No es el producto |
| Portada de nodos + checklist de alta | Eso 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 / EPG | H3 · S4 |
| App de 40 programas y CMS | L1 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
| Rol | Objetivo medible en demo |
|---|---|
| OYENTE | Ver 4 cubiertas, buscar «patio», pedir repetición de Patio 17 y ver el acuse en < 90 s, sin cuenta |
| CONDUCTOR | Entrar a mesa, ver 4 programas de hoy, leer ON-2301 Patio 17 EN_ANTENA en < 45 s |
| TÉCNICA | No 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ón | Anónimo (oyente) | CONDUCTOR | TÉCNICA |
|---|---|---|---|
| Ver galería / programas | Sí | Sí | No hay cuenta |
Buscar archivo ?q= | Sí | Sí | — |
Ver ficha /programa/:code | Sí | Sí | — |
| POST repetición | Sí | Sí (no es el job) | No |
Ver acuse /ok/:code | Sí (código en URL) | Sí | — |
Ver mesa /hoy | No (401 / redirect) | Sí | No |
GET /api/hoy | 401 | Sí | — |
| Ver lista de peticiones | No | Sí (mesa) | — |
| Reservar estudio | No (no existe) | No | Fuera |
| Subir a la antena | Copy: fuera | Copy: fuera | Oficio fuera de app |
| Recoger a un niño | No. RELEVO retirado | No | No |
[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)
| Tipo | Métrica | Definición |
|---|---|---|
| North Star | % de peticiones de repetición servidas en 48 h | Oficio de emisora, no pageviews |
| Activación oyente | 1.er POST /repetir con code | H1 |
| Integridad de expectativa | Reclamaciones «¿dónde está el play?» / «¿es la SER?» / «¿me guardáis el estudio?» | H2 + microcopy |
| Integridad de auth | GET /api/hoy sin token → 401; POST /api/programas/:code/repetir sin token → 201 | Galería pública + mesa cerrada |
| Salud | Error rate API programas / repeticiones | 4xx/5xx |
7. Alcance funcional v1 (L1)
| Módulo | Incluido |
|---|---|
| Galería | GET /api/programas · 4 programas (lead + 3 visibles) |
| Búsqueda | GET /api/programas?q= · archivo, query «patio» |
| Ficha | GET /api/programas/:code · título, hora, estado, cubierta |
| Repetición | POST /api/programas/:code/repetir → acuse; pedir ≠ reservar |
| Auth | POST /api/auth/login → JWT CONDUCTOR |
| Mesa | GET /api/hoy JWT · lista de hoy, no kanban |
| Estados UI | Empty (0 programas / 0 resultados), loading skeleton, error de red |
| Mobile | Galería ~390px · cubiertas apiladas · targets ≥44px |
| Seed | 1 CONDUCTOR, 4 programas (1 EN_ANTENA, 1 PROGRAMADO, 2 ARCHIVO) |
| Salud | GET /api/health → { ok: true, service: "onda-api" } |
8. Criterios de aceptación de producto
/es la galería de 4 cubiertas (lead + 3). No pide cuenta. No es un hero 2-col + 3 cards. No es un feed.- 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. /ok/:codedice explícitamente que pedir repetición no es reservar estudio y que Onda no es playlist.- La UI dice explícitamente que ONDA no es la SER y que escuchar no es suscribirse.
- Sin token, GET
/api/hoyresponde 401. POST/api/programas/:code/repetiry GET/api/programasson públicos. - Un POST de repetición no crea user, no reserva un slot, no abre un player.
- Un CONDUCTOR inicia sesión y ve 4 programas (Patio 17 EN_ANTENA, Voces PROGRAMADO, dos ARCHIVO) en el seed.
- El tablero es una lista de hoy. No es un kanban de 3 columnas. No es un split lista | ficha.
- Fotos reales en las cubiertas seed (assets
programas/*). - Chrome S-RAIL: Estudio / Buscar / Hoy. Galería pública sin top-nav de marketing.
- Rutas solo las del doc 03. No existen
/nodos,/alta,/mesa,/relevo,/recoger,/playlist,/directo. GET /api/healthdevuelveservice: "onda-api"(norelevo-api, noramal-api).- La galería no muestra nombres de otros oyentes; la mesa sí puede mostrar recuento de peticiones.
/buscar?q=patiodevuelve Patio 17.
9. Stack y artefactos
| Capa | Detalle |
|---|---|
| Frontend | Angular + Tailwind · puerto 4200 |
| Backend | NestJS · puerto 3023 |
| DB | Neon PostgreSQL · project falling-fog-92644546 |
| Auth | JWT (CONDUCTOR) |
| Diseño | Paper 01M0TSYSNQFZ8FVKD38ZGQT8AW |
| Repo app | /Users/cristian/orca/onda-app/ |
| Package | pnpm |
[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
| Riesgo | Impacto | Mitigación v1 |
|---|---|---|
| Expectativa de playlist | «¿Dónde está el play?» | Copy «Onda ≠ playlist» |
| Expectativa de estudio | Slot, TEMPLE, «¿a qué hora grabamos?» | Copy «Pedir repetición ≠ reservar estudio» |
| Expectativa de cadena | La SER, podcast nacional | Copy «ONDA no es la SER» |
| Expectativa de cuenta | Signup, follow, newsletter | Copy «Escuchar ≠ suscribirse»; POST anónimo |
| Expectativa de kanban | Recibido / En curso | Lista de hoy, no columnas de estado |
| Leftover RELEVO | Feed de recogidas, TUTOR, cole | Contrato ONDA; health onda-api; 0 copy de recogida |
| Leftover RAMAL | nodes, altas, VOCAL, RM- | Dominio programas + repeticiones + CONDUCTOR |
| Player el día 1 | Alcance L2 disfrazado | S4: sin streaming |
| Cuenta de oyente | Rompe S2 / H3 | Sin signup; POST público |
| Home percibida como landing de emisora | Pérdida de H-GALLERY / AP-12 | 4 cubiertas; no hero 2-col |
| Confundir con TEMPLE (kiosk + horas) | Expectativa de bloques de 2 h | Galería de programas; no slots |
| Confundir con PIZARRA (catálogo + plaza) | Expectativa de taller municipal | ONDA no es el distrito |
| Confundir con PREGÓN (chat + kanban) | Expectativa de parte | Búsqueda + pedir; no hilo |
| 0 programas | Galería vacía sin oficio | Empty «Aún no hay programas.» |
| WhatsApp paralelo | H1 no se mide | North star etiquetada como modelo |
| Amarillo de cinta como texto | Contraste WCAG | Mark pinta cinta/CTA; ink escribe |
11. Glosario
| Término | Definición en ONDA |
|---|---|
| Onda | Radio de barrio; el producto, no una playlist ni la SER |
| Programa | Emisión (programas); no es un track de cola |
| Cubierta | Foto 16/10 del programa en galería |
| Archivo | Programas que ya sonaron; se buscan, no se shufflean |
| Repetición | POST 201; no significa reservar el estudio |
| Acuse | Código (ON-XXXX); no es una suscripción |
| En antena | Estado EN_ANTENA; sello rojo, poco |
| Programado | Estado PROGRAMADO; suena más tarde hoy |
| Archivo (estado) | Estado ARCHIVO; se puede pedir repetición |
| Galería | Superficie H-GALLERY pública |
| Mesa / Hoy | Superficie JWT del CONDUCTOR (lista, no kanban) |
| Rail | Chrome S-RAIL: Estudio / Buscar / Hoy |
| Estudio | Slot del rail que vuelve a la galería; no es una reserva de sala |
| Kanban | Lenguaje de diseño de otros días; no hay columnas aquí |
| CONDUCTOR | Usuario autenticado que opera la mesa |
| OYENTE | Autor/autora de la repetición; no tiene fila en users |
| TÉCNICA | Oficio de antena; fuera del slice de código |
| Playlist | Cola tipo Spotify; fuera |
| SER | Cadena nacional; fuera. ONDA no es la SER |
| RELEVO | Producto retirado (feed de recogidas); anti-patrón |
| Signage | Mood: papel de cartel + cinta amarilla |
| Ground | Papel #FFFFFF |
| Ink | Tinta de rotulador #111111 |
| Mark | Cinta aislante #E6C200 |
| Mute | #5C5C5C |
| Seal | Sello en antena #C41E3A, poco |
| Rail (color) | #F3F3F3 |
12. Decisiones de diseño (cierre de brief)
| ID | Decisión | Alternativa descartada |
|---|---|---|
| D1 | Home = galería de cubiertas (H-GALLERY) | Hero 2-col + 3 cards (AP-12) · feed (RELEVO) · portada a sangre (RAMAL) · kiosk (TEMPLE) |
| D2 | Shell rail Estudio / Buscar / Hoy (S-RAIL) | Split mesa · dock · bottom tabs · kiosk mínimo · wizard chrome |
| D3 | Registro híbrido lookbook + archivo (R-HY) | Estudio negro phosphor (primer instinto radio) · ice RAMAL · tidal RELEVO |
| D4 | Flujo F-SEARCH (el archivo se busca) | F-BOOK, F-KAN, F-ONB, F-STORY, F-EDIT |
| D5 | Mood signage | Nocturnal/phosphor (primer instinto), rusted, mineral, chapel, ice, tidal |
| D6 | Un rol autenticado CONDUCTOR | Multi-rol técnica / oyente / cadena |
| D7 | Status EN_ANTENA / PROGRAMADO / ARCHIVO | Kanban de 3 columnas; «en directo streaming»; «publicado podcast» |
| D8 | Oyente sin cuenta | Signup, follow, newsletter |
| D9 | Galería no lista nombres de oyentes | Tablero público enumerable |
| D10 | POST público de repetición | Formulario de DNI + pago + reserva de sala |
| D11 | Ground G-SIGN #FFFFFF | G-ICE, G-INK, G-FOG, G-CHAPEL, G-WATER, G-CONCRETE |
| D12 | Success de acuse en /ok/:code | Toast único sin pantalla |
| D13 | Búsqueda en /buscar, no como home | H-SEARCH tipo PIZARRA (rompería H-GALLERY) |
| D14 | Mesa = lista de hoy, no kanban ni split | Recibido / En curso / Cerrado; lista | ficha RAMAL |
| D15 | Galería pública + mesa JWT (no auth-first total) | Todo cerrado (rompería S2) · todo abierto (rompería mesa) |
| D16 | Sin streaming | Player en ficha |
| D17 | Sustituye a RELEVO; 0 copy de recogida | Mantener leftover TUTOR / feed / cole |
| D18 | Health onda-api · puerto 3023 | leftover relevo-api |
13. Relación con el día anterior
| Case | Terna | Por qué ONDA no lo copia |
|---|---|---|
| RAMAL (N−1) | R-CL · S-SPLIT · H-FULL · F-ONB | No telecom, no nodos, no altas, no vocal, no split |
| TEMPLE (N−2) | R-HY · S-MIN · H-KIOSK · F-BOOK | No kiosk, no horas, no concierto, no turno; repetición ≠ coger sala |
| PREGÓN (N−3) | R-IN · S-SIDE · H-CHAT · F-KAN | No admin pública, no chat, no kanban, no 112 |
| MATIZ (N−4) | R-HY · S-BOTTOM · H-BENTO · F-EDIT | No belleza, no PWA de oficio, no editor de gramos |
| RELEVO (retirado, mismo día) | R-IN · S-DOCK · H-FEED · F-STORY | Anti-patrón. No familias, no feed de recogidas, no cole, no TUTOR |
| PIZARRA | R-CL · S-CMD · H-SEARCH · F-SEARCH | Comparte 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)
| Objetivo | Método en este caso | Salida |
|---|---|---|
| Entender actores de la emisora | Modelado 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ía | Journey + service blueprint | §7–8 |
| Traducir a requisitos L1 galería + archivo + mesa | Matriz 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
| Prohibido | Permitido |
|---|---|
| «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 real | Quotes de persona etiquetadas como constructo de diseño |
| Volumen medido «17 repeticiones/semana en Patio 17» como KPI de mercado | Programas de seed de producto, etiquetados como demo |
| Parrilla oficial de una emisora comunitaria 2026 | Programas 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
| Stakeholder | Influencia | Interés | Necesidad principal |
|---|---|---|---|
| CONDUCTOR (Nerea) | Alta | Muy alta | Ver el hoy y las peticiones sin kanban ni player |
| OYENTE (Iker) | Alta en la repetición | Muy alta | Encontrar el programa de esta tarde y pedirlo sin cuenta |
| TÉCNICA (Pilar) | Media formal, baja en producto día 1 | Alta | Oficio de antena; fuera del slice de código |
| Emisora / local | Alta formal | Alta | Que se vea qué suena; que no se confunda con la SER ni con Spotify |
| SER / cadena nacional | Competencia / analogía falsa | — | Fuera de alcance; no clonar parrilla nacional |
| Spotify / playlist | Canal paralelo / analogía falsa | — | Fuera; Onda ≠ playlist |
| TEMPLE / local de ensayo | Analogía falsa de «reservar sala» | — | Fuera; pedir ≠ reservar estudio |
| Grupo de WhatsApp del bloque | Canal paralelo | — | No es el backoffice |
| RELEVO (retirado) | Anti-patrón | — | Fuera; 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
| Campo | Detalle |
|---|---|
| Edad / contexto | 34 años; conduce en Carabanchel; mesa de hoy, no de instalación de antena |
| Digital | Fluida en desktop de mesa; la galería pública no es su herramienta principal, pero la mira |
| Goals | Ver qué suena, qué está programado, qué se ha pedido repetir |
| Pains | WhatsApp 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 demo | nerea@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
| Campo | Detalle |
|---|---|
| Edad / contexto | 52 años; vive en Carabanchel; oye Patio 17 cuando puede; no quiere «otra cuenta de radio» |
| Digital | Móvil desde el portal o el salón; no se suscribe si puede evitarlo |
| Goals | Ver si suena Patio 17; buscarlo en archivo; pedir que lo repitan; llevarse un código |
| Pains | El 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)
| Campo | Detalle |
|---|---|
| Edad / contexto | 41 años; técnica de antena; oficio de azotea y cable, no de mesa |
| Digital | No tiene login en el slice L1 |
| Goals | Subir cuando la emisora lo pida (fuera de app) |
| Pains | Que 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
| Job | Por qué no |
|---|---|
| Reproducir un stream | Onda ≠ playlist · S4 |
| Reservar el estudio | Pedir repetición ≠ reservar estudio |
| Suscribirse / follow | Escuchar ≠ suscribirse |
| Sustituir a la SER | ONDA 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)
| ID | Como | Quiero | Para |
|---|---|---|---|
| US-01 | Oyente | Ver 4 cubiertas (lead + 3) | Distinguir radio de barrio de playlist |
| US-02 | Oyente | Buscar «patio» en archivo | Encontrar el programa sin el grupo |
| US-03 | Oyente | Ver ficha (título, hora, estado) | Saber si está en antena, programado o archivo |
| US-04 | Oyente | Pedir repetición y ver acuse ON-XXXX | Dejar constancia, no reservar estudio |
| US-05 | Conductora | Entrar con JWT | Abrir la mesa de hoy |
| US-06 | Conductora | Ver lista de hoy | Oficio sin kanban |
| US-07 | Ambos | Ver empty / error / loading | No quedarse en blanco |
Could (fuera de L1): login de Pilar, streaming, parrilla semanal, chat, cuenta de oyente, player.
7. Journey (resumen)
- Iker abre
/. Ve 4 cubiertas. Lead Patio 17 EN_ANTENA. [H1] - Toca Patio 17 →
/programa/ON-2301. (O busca «patio» en/buscar.) - Lee hora 17:00, estado En antena, conductora Nerea. Copy irrenunciable.
- POST repetición →
/ok/ON-2301. Lee las cuatro oposiciones. - Nerea entra a
/login→/hoy. Lista de hoy. - 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)
| Capa | Qué 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. |
| Soporte | Nest · Neon falling-fog-92644546 · JWT CONDUCTOR · Paper signage |
9. Matriz hallazgo → requisito → feature
| Hallazgo (etiquetado) | Requisito | Feature v1 |
|---|---|---|
| [HIPÓTESIS H1] Galería de cubiertas reduce WhatsApp | 4 cubiertas en fold, lead + 3 | / H-GALLERY |
| [HIPÓTESIS H2] Pedir ≠ reservar evita TEMPLE | Copy irrenunciable + POST que no crea slot | /programa/:code + /ok/:code |
| [HIPÓTESIS H3] Cuatro programas bastan | Sin parrilla semanal | Seed 4 · no calendario |
| [SUPUESTO S2] Sin cuenta de oyente | POST público + GET ?q= público | POST /api/programas/:code/repetir |
| [SUPUESTO S3] Una conductora | Un rol JWT | CONDUCTOR |
| [DECISIÓN S4] Sin streaming / chat / semana | No player, no hilo, no EPG | Fuera 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
| Principio | Aplicación |
|---|---|
| Galería first | La home son 4 cubiertas. No hay landing de marca por delante. No hay search como home |
| Público / mesa | Iker pide repetición sin cuenta; Nerea entra con JWT |
| Cubierta → ficha → acuse | Profundidad 2 desde la galería hasta el código |
| El archivo se busca | F-SEARCH vive en /buscar, no sustituye H-GALLERY |
| Mesa = lista de hoy | Una columna; no kanban, no split lista | ficha |
| Lenguaje de dominio | Programa, cubierta, archivo, repetición, hoy — no «track», «lead», «expediente», «relevo» |
| Pedir no es reservar | El acuse no tiene slot ni hora de estudio |
| Onda no es playlist | No hay cola, play, shuffle |
| Chrome de oficio | S-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
| Audiencia | Nodos relevantes |
|---|---|
| OYENTE | Galería → (Buscar) → Programa → Ok. No ve /hoy |
| CONDUCTOR | Login → Hoy · (puede leer la galería y buscar) |
| TÉCNICA | Ninguno 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 sí es ruta (/buscar): el archivo se busca; la home sigue siendo galería. [DECISIÓN]
La ficha sí 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)
| Slot | Destino | Visibilidad | Notas |
|---|---|---|---|
| Estudio | / | Todos | Galería de cubiertas. No es reserva de sala |
| Buscar | /buscar | Todos | Archivo. Placeholder «patio» |
| Hoy | /hoy | CONDUCTOR; si guest → /login?next=/hoy | Lista 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.
Galería pública (H-GALLERY)
| Elemento | Destino | Notas |
|---|---|---|
| Wordmark ONDA | / | Sello de cartel; no es hero 2-col |
| Eslogan | — | «Hoy suena esto.» |
| Cubierta lead | /programa/:code | Patio 17 · EN_ANTENA |
| Cubiertas 2–4 | /programa/:code | Mercado Usera · Voces · Banda |
| Link Conductora (mute) | /login o /hoy si hay token | Secundario, no CTA de marca |
[DECISIÓN] La galería no tiene tabs de marketing, no tiene «Directo / Podcasts / Suscríbete». El rail basta.
Chrome de ficha y búsqueda (F-SEARCH)
| Slot | Destino | Notas |
|---|---|---|
| ← Estudio | / | Un paso atrás, no stepper |
| Campo query | /buscar?q= | GET programas |
| Resultado | /programa/:code | Misma ficha que la galería |
| Pedir repetición | POST → /ok/:code | Una acción, no wizard |
Chrome de mesa (/hoy autenticado)
| Slot | Destino | Visibilidad | Notas |
|---|---|---|---|
| Hoy | /hoy | Auth | Titular de oficio |
| Lista | — | Auth | Una columna; status en texto |
| Estudio | / | Auth | Secundario vía rail |
| Salir | limpia token → / | Auth | Cabecera 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)
| Concepto | Label |
|---|---|
| Home | Estudio / Galería (no «Inicio», no «Directo», no «Feed») |
| Búsqueda | Buscar / Archivo |
| Ficha | Programa (no «track», no «episodio SKU») |
| Success | Pedida / código |
| Staff | Hoy (no «Backoffice», no «Turno», no «Kanban», no «Mesa» tipo RAMAL en UI) |
| Auth | Conductora (no «Área cliente», no «Tutor») |
| CTA | Pedir 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
| Ruta | Qué hay | Qué no hay |
|---|---|---|
/ | 4 cubiertas, lead + hora/conductora/estado, eslogan, rail | Player, precios, login de oyente, parrilla semanal |
/buscar | Campo, query «patio», resultados, empty | Facetas municipales, chips de distrito |
/programa/:code | Cubierta, título, hora, estado, host, copy, CTA | Player, reserva de sala, follow |
/ok/:code | Código + 4 oposiciones | Contrato, playlist, «ya está reservado» |
/login | Email / clave CONDUCTOR | Signup de oyente |
/hoy | Lista de 4 programas + recuento de peticiones | Columnas Recibido / En curso; split ficha |
6. Error / empty / loading (sitio)
Viven en las mismas rutas, no en URLs extra.
| Estado | Superficie |
|---|---|
| Loading | Skeletons 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 sí 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
| Pieza | Forma |
|---|---|
| Programa | ON-2301 … ON-2304 (ON versales, guion, 4 dígitos) |
| Ficha | /programa/ON-2301 |
| Acuse | /ok/ON-2301 |
| Búsqueda | /buscar?q=patio |
API puede normalizar on-2301 → ON-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
| Token | Significado |
|---|---|
| F-SEARCH | El archivo se busca; la galería no es el buscador |
| Hoy | Lista CONDUCTOR, JWT |
| 201 | Repetición creada (pedida) |
| 401 | Hoy sin token |
| 404 | Programa 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-2301muestra 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.
/buscarno 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,
/hoyredirige a/login?next=/hoy. - GET
/api/hoysin 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
| Estado | Trigger | UI |
|---|---|---|
| Loading galería | GET programas en vuelo | 4 skeletons de cubierta |
| Empty galería | 200 [] | «Aún no hay programas.» |
| Error galería | 5xx / red | Reintentar |
| Loading buscar | GET ?q= en vuelo | Skeletons de resultado |
| Empty buscar | 200 [] | «Sin resultados.» |
| Loading ficha | GET :code | Skeleton de ficha |
| 404 ficha | código inexistente | «Ese programa no está.» |
| Loading hoy | GET hoy en vuelo | 4 skeletons de fila |
| Empty hoy | 200 { programas: [] } | «No hay programas hoy.» |
| Login 401 | credenciales | «Email o contraseña no valen.» |
6. Flujos que no existen
| Flujo | Por qué |
|---|---|
| Signup oyente | S2 · Escuchar ≠ suscribirse |
| Play / stream | S4 · Onda ≠ playlist |
| Reservar estudio | Pedir ≠ reservar · ≠ TEMPLE |
| Chat | ≠ PREGÓN |
| Alta de nodo | ≠ RAMAL |
| Recoger en el cole | ≠ RELEVO (retirado) |
| Wizard 1—2—3 | Pedir es un POST, no onboarding |
| Login Pilar | Slice L1 |
| PATCH estado de programa | Nerea lee; no mueve columnas en v1 |
| Parrilla de la semana | H3 · S4 |
7. Tiempos de demo (modelo)
| Actor | Recorrido | Techo |
|---|---|---|
| Iker | Galería → Patio 17 → pedida | < 90 s |
| Iker (archivo) | Buscar «patio» → ficha → pedida | < 90 s |
| Nerea | Login → 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
/— lead Patio 17 + 3 cubiertas. Eslogan. Rail.- Tap Patio 17 — ficha EN_ANTENA · 17:00 · Nerea.
- Pedir repetición —
/ok/ON-2301. Copy ×4. /buscar?q=patio— un resultado./login—nerea@onda.radio/password123./hoy— lista; ON-2301 En antena; recuento ≥1 si se hizo el POST.- 390: cubiertas apiladas, no carousel.
- API down: error + Reintentar.
9. Contratos de error de API (flujo)
| Caso | HTTP | UI |
|---|---|---|
| POST repetición, code inexistente | 404 | «Ese programa no está.» |
| POST repetición, red | — | seal + Reintentar; no navega a ok |
| GET hoy sin JWT | 401 | redirect login |
| Login malo | 401 | copy de login |
| Query vacía en buscar (sin submit) | no fetch | prompt 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:
| Entidad | Tabla SQL | Propósito |
|---|---|---|
| User | users | Identidad de oficio (rol CONDUCTOR) |
| Session | sessions | Sesión JWT / token persistido |
| Programa | programas | Emisión (código ON-XXXX, cubierta, hora, estado) |
| Repeticion | repeticiones | Petició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
| Valor | Descripción |
|---|---|
CONDUCTOR | Conductora de la mesa; único rol autenticado v1 |
ProgramaStatus
| Valor | Descripción |
|---|---|
EN_ANTENA | Suena ahora; sello rojo en UI, poco |
PROGRAMADO | Suena más tarde hoy; no es EPG de la semana |
ARCHIVO | Ya 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.
repeticionesno apunta ausers: la petición es anónima de cuenta.
4. Tablas
users
| Columna | Tipo | Notas |
|---|---|---|
| id | BIGSERIAL PK | |
| TEXT UNIQUE | nerea@onda.radio | |
| password_hash | TEXT | bcrypt 12 · seed $2b$12$OEKcNck63neDJ.CrVP0lNOaXp2NG2zd2aMiGmnYY221h4ZAZ58x1m |
| name | TEXT | Nerea Gil |
| role | TEXT | CONDUCTOR |
| created_at | TIMESTAMPTZ |
sessions
| Columna | Tipo | Notas |
|---|---|---|
| id | UUID PK | sid del JWT |
| user_id | BIGINT FK | |
| expires_at | TIMESTAMPTZ | |
| revoked_at | TIMESTAMPTZ | logout |
| created_at | TIMESTAMPTZ |
programas
| Columna | Tipo | Notas |
|---|---|---|
| id | BIGSERIAL PK | |
| code | TEXT UNIQUE | ON-2301 |
| title | TEXT | Patio 17 |
| lead | TEXT | Una frase. Onda ≠ playlist. |
| status | TEXT | EN_ANTENA | PROGRAMADO | ARCHIVO |
| hour | TEXT | 17:00 (24 h, es-ES) |
| host_name | TEXT | Nerea · Iker · Pilar (fuera) |
| cover | TEXT | /assets/programas/micro.jpg |
| sort_order | INTEGER | 1 = lead de galería |
| created_at / updated_at | TIMESTAMPTZ |
repeticiones
| Columna | Tipo | Notas |
|---|---|---|
| id | BIGSERIAL PK | |
| programa_id | BIGINT FK | |
| created_at | TIMESTAMPTZ |
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
| code | title | status | hour | host | cover |
|---|---|---|---|---|---|
| ON-2301 | Patio 17 | EN_ANTENA | 17:00 | Nerea | /assets/programas/micro.jpg |
| ON-2302 | Mercado Usera | ARCHIVO | 11:00 | Iker | /assets/programas/cinta.jpg |
| ON-2303 | Voces del bloque | PROGRAMADO | 20:00 | Nerea | /assets/programas/antena.jpg |
| ON-2304 | Banda municipal | ARCHIVO | 18:00 | Pilar (fuera) | /assets/programas/cinta.jpg |
| code | lead (catálogo) | sort |
|---|---|---|
| ON-2301 | El patio cuenta lo que el bloque no pone en el tablón. | 1 |
| ON-2302 | Pregones, cajas y un micro prestado. | 2 |
| ON-2303 | Vecinas al aire. Sin playlist. | 3 |
| ON-2304 | Ensayo 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-2301…ON-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
| Fuera | Motivo |
|---|---|
| Tabla technicians | Pilar no entra |
| Tabla listeners / subscriptions | Escuchar ≠ suscribirse |
| Tabla streams / audio_url | S4 |
| Tabla slots / rooms / bookings | Pedir ≠ reservar · ≠ TEMPLE |
| nodes / altas | Dominio RAMAL; no es el contrato |
| relevos / pickups / tutors | RELEVO retirado |
| EPG / week_grid | H3 · S4 |
06-tech-stack.md
06 — Stack tecnológico — ONDA
1. Visión
| Capa | Tecnología | Notas |
|---|---|---|
| Frontend | Angular standalone + signals | Puerto 4200 · web responsive |
| Estilos | Tailwind CSS | Tokens signage (ground, ink, mark, mute, seal, rail) |
| Backend | NestJS TypeScript | Puerto 3023, prefijo /api |
| Persistencia | PostgreSQL (ensureSchema) | Tablas users, programas, repeticiones, sessions |
| DB | Neon PostgreSQL | project falling-fog-92644546 |
| Auth | JWT | Rol CONDUCTOR (/hoy); galería, búsqueda y POST repetición públicos |
| Diseño | Paper | 01M0TSYSNQFZ8FVKD38ZGQT8AW |
| Repo | /Users/cristian/orca/onda-app/ | Rewrite en paralelo; este doc es el contrato |
| Package | pnpm | pnpm --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:
| Script | Acción |
|---|---|
| api | Arranca Nest en :3023 |
| start / web | Arranca Angular en :4200 |
3. API pública vs JWT
| Método | Ruta | Auth |
|---|---|---|
| GET | /api/health | Público · { ok: true, service: "onda-api" } |
| GET | /api/programas | Público |
| GET | /api/programas?q= | Público · archivo |
| GET | /api/programas/:code | Público |
| POST | /api/programas/:code/repetir | Público |
| POST | /api/auth/login | Público |
| GET | /api/hoy | JWT 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-api → onda-api. Puerto leftover de RELEVO :3023 se reutiliza a propósito (mismo día, producto distinto). [DECISIÓN]
4. Justificación
| Elección | Razón |
|---|---|
| Angular + Nest | Alineación con la serie daily y el handoff del estudio |
| Neon serverless | Volumen bajo (S1: 2–4 programas/día; demo 4 × peticiones anónimas); no cola Redis |
| JWT CONDUCTOR | Mesa interna cerrada; la galería sí es pública |
Auth en servidor para /hoy | Source of truth = guard; el cliente no «esconde» recuentos |
| Puerto 3023 | Evitar 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-XXXX | Acuse humano, no UUID en la portería |
| POST vacío de repetición | Escuchar ≠ suscribirse |
| pnpm | Encargo; filtro por app |
5. Variables
.env local (nunca git):
| Variable | Servicio | Descripción |
|---|---|---|
DATABASE_URL | API | Neon falling-fog-92644546 |
JWT_SECRET | API | Firma tokens |
PORT | API | opcional, 3023 |
SESSION_TTL_SECONDS | API | opcional (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
| Eje | Sí | No |
|---|---|---|
| Superficie | Papel #FFFFFF, tinta #111111, cinta #E6C200, rail #F3F3F3 | Hielo RAMAL, ink TEMPLE, fog PREGÓN, yeso MATIZ, water RELEVO, hormigón ZAGUÁN |
| Tipo | DM Serif Display + IBM Plex Sans | Newsreader+Figtree, Fraunces+Atkinson, Ibarra+Jost |
| Foto | Micro con cinta, cassette y rundown, antena de azotea, estudio de portería | Estudio negro de cadena, auriculares de Spotify, familia en el cole, mástil de operador como marca |
| Grid | Galería 16/10; lead + 3 cubiertas; rail izquierdo | Hero 2-col + 3 cards (AP-12) · feed de cards (RELEVO) · nodos a sangre (RAMAL) |
| Acento | Mark #E6C200 en CTA, foco, cinta; seal #C41E3A en antena | Amber dashboard TEMPLE, cobre RAMAL, tidal RELEVO |
| Chrome | Rail Estudio / Buscar / Hoy | Split, dock, kiosk mínimo, wizard 1—2—3 |
3. Paleta
| Token | Hex | Rol |
|---|---|---|
| Ground / G-SIGN | #FFFFFF | Papel de cartel. Fondo de página. |
| Ink | #111111 | Texto, wordmark, rotulador. |
| Mark | #E6C200 | Cinta aislante / chrome yellow: CTA Pedir, foco, filete. Poco. |
| Mute | #5C5C5C | Meta, labels, hora secundaria, idle |
| Seal | #C41E3A | Sello «en antena» y error. Poco. No pinta el header. |
| Rail | #F3F3F3 | Fondo 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
| Uso | Familia | Tamaños de referencia |
|---|---|---|
| Wordmark | DM Serif Display 400 | 24–28 desktop · 22 mobile |
| Titular de galería / hoy | DM Serif Display 400 | 32–44 |
| Código ON-XXXX | DM Serif Display 400 | 28–36 |
| Nombre de programa | DM Serif Display 400 | 22–28 |
| Cuerpo / disclaimers | IBM Plex Sans 400 | 16–17 / 26–28 lh |
| UI, rail, form, lista | IBM Plex Sans 400/500/600 | 14 / 16 |
| Meta (hora, host, status) | IBM Plex Sans 600 | 12–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
| Asset | Debe | No debe |
|---|---|---|
| hero.jpg | Estudio de portería: mesa, mesa de mezclas, micro con cinta, patio al fondo | Rack de cadena, familia con auriculares, puerta del cole |
| micro.jpg | Micro de mano con cinta aislante amarilla; ON AIR al fondo | Micro de estudio nacional pulido |
| cinta.jpg | Cassette y rundown de radio comunitaria; rotulador | Spotify Wrapped, CD de major |
| antena.jpg | Antena en azotea de barrio al atardecer | Má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
| Mood | Por qué no |
|---|---|
| Nocturnal / phosphor | Primer instinto; es estudio de cadena |
| Ice institucional | Fue RAMAL |
| Vehicle dashboard | Fue TEMPLE |
| Highlighter fog | Fue PREGÓN |
| Chapel amatista | Fue MATIZ |
| Tidal / water | Fue RELEVO (retirado) |
| Hormigón oxidado | Fue 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
| Token | Valor | Uso |
|---|---|---|
--ground | #FFFFFF | Fondo de página (papel) |
--ink | #111111 | Texto |
--mark | #E6C200 | CTA, foco, cinta |
--mute | #5C5C5C | Meta |
--seal | #C41E3A | En antena, error |
--rail | #F3F3F3 | Fondo S-RAIL |
--font-display | DM Serif Display | Titulares |
--font-sans | IBM Plex Sans | UI |
--target | 44×44 min | Toques |
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
| Rol | Size / leading |
|---|---|
| Display galería | clamp(2.4rem, 6vw, 4.4rem) / 0.95 |
| H1 hoy / ficha | 3rem / 1 |
| H2 programa | 28px / 32px |
| Cuerpo | 16–17 / 26 |
| Meta | 12–13 / 16, tracking 0.12em, uppercase |
| Rail label | 11–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
| Superficie | Grid |
|---|---|
| Galería 1440 | Rail 64px + panel; lead 16/10 a ancho de panel; debajo 3 cols de cubiertas |
| Galería 390 | Cubiertas apiladas; rail inferior 56px |
| Buscar | Columna max-w-3xl en el panel |
| Programa / Ok / login | Columna max-w-lg (ok/login) · max-w-2xl (ficha) |
| Hoy | Lista en el panel; stacked < lg |
Max page 1440px. Padding 20 / 40 / 48.
5. Estados de control
| Estado | Tratamiento |
|---|---|
| Default | Papel, ink |
| Hover cubierta desktop | Ring 2px mark. Sin lift |
| Focus | Ring mark 2px |
| Disabled | opacity 50–60 |
| Error | Texto 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
| Usar | No usar |
|---|---|
| Programa / cubierta / archivo | Track, episodio SKU, podcast network |
| Pedir repetición | Reservar estudio, coger sala, play, suscribirse |
| En antena / programado / archivo | Recibido, En curso, En directo streaming, Publicado |
| Hoy | Kanban, NOC, backoffice, turno, mesa (en UI) |
| Conductora | Comercial, locutora de cadena, tutor, vocal |
| Oyente | Cliente, lead, abonado, tutor |
| Estudio (rail = galería) | Reserva de sala, TEMPLE, slot |
| ONDA no es la SER | Cadena, dial nacional |
| Onda ≠ playlist | Spotify, 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ón | Dónde | Copy |
|---|---|---|
| Onda ≠ playlist | Galería · buscar · ficha · acuse | «Onda no es una playlist. No hay cola ni play.» |
| Pedir repetición ≠ reservar estudio | Ficha · acuse · pie de hoy | «Pedir repetición no reserva el estudio.» |
| ONDA no es la SER | Galería · acuse | «ONDA no es la SER. Es la radio del barrio.» |
| Escuchar ≠ suscribirse | Ficha · 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
| Superficie | Copy |
|---|---|
| Eslogan | Hoy suena esto. |
| Línea de lugar | Carabanchel |
| Galería titular | ONDA |
| Cubierta dato 1 | 17:00 / 11:00 / 20:00 / 18:00 |
| Cubierta dato 2 | Nerea / Iker / Nerea / Pilar (fuera) |
| Cubierta dato 3 | En antena / Archivo / Programado / Archivo |
| Buscar placeholder | patio |
| Buscar titular | Archivo |
| Buscar submit | Buscar |
| Ficha volver | ← Estudio |
| Ficha CTA | Pedir repetición |
| Login titular | Entrar al hoy |
| Login ayuda | Solo conductora. El oyente pide repetición en la galería, sin cuenta. |
| Login submit | Entrar |
| Login error | Email o contraseña no valen. |
| Empty galería | Aún no hay programas. |
| Empty buscar | Sin resultados. |
| Empty hoy | No hay programas hoy. |
| Success titular | Pedida. |
| Success código | ON-2301 |
| Success cuerpo | Pedir repetición no reserva el estudio. Onda no es una playlist. |
| Success extra | ONDA no es la SER. Pedir no crea una cuenta. |
| Success CTA | Volver al estudio |
| Error | La red no responde. |
| Error retry | Reintentar |
| Loading galería | Cargando |
| Loading buscar | Cargando |
| Loading hoy | Cargando |
| 404 programa | Ese programa no está. |
| Hoy titular | Hoy |
| Rail Estudio | Estudio |
| Rail Buscar | Buscar |
| Rail Hoy | Hoy |
| Salir | Salir |
| Link Conductora (galería) | Conductora |
| Sello | En antena |
| Status ARCHIVO | Archivo |
| Status PROGRAMADO | Programado |
5. Programas seed (tono)
Los programas demo se escriben como emisiones de barrio, no como ficha de cadena ni como anuncio de Spotify.
| Pieza | Tono |
|---|---|
| Patio 17 | Patio. Micro con cinta. En antena. |
| Mercado Usera | Pregón. Cinta. Archivo. Iker al micro, no como cuenta. |
| Voces del bloque | Azotea. Antena. Programado a las 20:00. |
| Banda municipal | Archivo. 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
| Nombre | Uso |
|---|---|
| Nerea Gil | CONDUCTOR; nerea@onda.radio; Patio 17 y Voces del bloque |
| Iker Soto | OYENTE; sin cuenta; también host de Mercado Usera (sin login) |
| Pilar Ruiz | TÉCNICA; sin cuenta; host de Banda municipal (fuera) |
| ONDA · Carabanchel | Emisora 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
| Status | Frase UI | Frase que Nerea podría decir (fuera de app) |
|---|---|---|
| EN_ANTENA | En antena | «Está sonando. No es un play de Spotify.» |
| PROGRAMADO | Programado | «A las ocho. No es una reserva de sala.» |
| ARCHIVO | Archivo | «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-17en UI). - Hora:
17:00en UI (24 h). - Fecha:
23 agosi hay línea larga. - No mezclar
on-2301en 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
| No | Motivo |
|---|---|
| «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 nacional | ONDA no es la SER |
| Recibido / En curso / Cerrado | No 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 real | Verdad 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
| Par | Relación esperada |
|---|---|
Ink #111111 sobre papel #FFFFFF | > 10:1 |
Ink sobre mark #E6C200 | Texto de CTA ≥ 4.5:1 (rotulador sobre cinta) |
Mute #5C5C5C sobre papel | Meta ≥ 4.5:1 |
Seal #C41E3A sobre papel | «En antena» / error ≥ 4.5:1 |
Ink sobre rail #F3F3F3 | Labels 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
| Control | Nombre |
|---|---|
| Cubierta | Título del programa (h2) |
| Rail Estudio | Estudio |
| Rail Buscar | Buscar |
| Rail Hoy | Hoy |
| Pedir repetición | Pedir repetición |
| Buscar submit | Buscar |
| Entrar | Entrar |
| Salir | Salir |
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
| Recurso | Auth | PII |
|---|---|---|
| GET programas | No | No (catálogo de emisiones) |
GET programas ?q= | No | No |
| GET programas/:code | No | No (host de programa es oficio, no oyente) |
| POST repetir | No | No (cuerpo vacío; no nombre) |
GET /ok/:code | No (solo código en URL) | No hidrata peticiones ajenas |
| GET hoy | JWT CONDUCTOR | Recuento 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:
subuserId,sidsession,role. - Password bcrypt 12. Demo
password123solo 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
| Amenaza | Mitigación |
|---|---|
Enumerar /ok/ON-2301 y ver peticiones | Ok no GET de repeticiones; state de navegación + catálogo público |
| POST spam de repeticiones | Volumen demo; rate Could L+1 |
| Token en localStorage | XSS = compromiso; slice L1 acepta el tradeoff de SPA [DECISIÓN] |
| Leftover TUTOR / VOCAL en el mismo JWT | Seed solo CONDUCTOR; claims role=CONDUCTOR |
| Login con email de Pilar | No 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étrica | Definición | Hipótesis |
|---|---|---|
| Activación oyente | POST /api/programas/:code/repetir 201 / visitas a / | H1 |
| Uso de archivo | GET /api/programas?q= con q no vacío / visitas | F-SEARCH |
| Tiempo a acuse | < 90 s en demo | Diseño |
| Tiempo a lectura de hoy | GET hoy desde login | Oficio |
| Integridad auth | 401 en GET hoy anónimo | Seguridad |
| Expectativa | Tickets «¿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:
| Evento | Props |
|---|---|
galeria_view | programas_count |
buscar_query | q, results_count |
repetir_submit | code |
hoy_open | programas_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)
- GET
/api/health→onda-api(norelevo-api, noramal-api) - GET
/api/programas→ 4 codesON-2301…ON-2304+ lead + hora + host + status + cover - GET
/api/programas/ON-2301→ Patio 17 · EN_ANTENA · 17:00 · Nerea - GET
/api/programas?q=patio→ incluye ON-2301; no exige los 4 - GET
/api/programas?q=zzzz→[] - POST
/api/programas/ON-2301/repetir→ 201pedida: true - POST
/api/programas/ON-9999/repetir→ 404 - GET
/api/hoysin JWT → 401 - POST
/api/auth/loginNerea → CONDUCTOR - GET
/api/hoy→ 4 programas; ON-2301 EN_ANTENA;repeatCount≥ 1 tras el POST
2. Smoke UI
| # | Pasos | Esperado |
|---|---|---|
| 1 | Abrir / | 4 cubiertas, lead Patio 17, eslogan, rail |
| 2 | Tap Patio 17 | /programa/ON-2301 · En antena · 17:00 · Nerea |
| 3 | Pedir repetición | /ok/ON-2301 + 4 oposiciones |
| 4 | /buscar + «patio» | Patio 17 en resultados |
| 5 | /buscar + «zzzz» | «Sin resultados.» |
| 6 | /login Nerea | /hoy lista |
| 7 | Leer ON-2301 | En antena; no botones Aceptar/Rechazar RAMAL |
| 8 | Logout | Galería; /hoy pide login |
| 9 | / 390 | cubiertas apiladas, no carousel; rail inferior |
| 10 | API down | Error + 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-apiniramal-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 -
/hoylista · 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
GalleryPageSearchPageProgramaPageOkPageLoginPageHoyPage - 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
| Paper | Ruta | Componente |
|---|---|---|
| UI-01 Gallery | / | GalleryPage |
| UI-02 Gallery mobile | / 390 | GalleryPage |
| UI-03 Search | /buscar | SearchPage |
| UI-04 Programa | /programa/:code | ProgramaPage |
| UI-05 Programa mobile | /programa/:code 390 | ProgramaPage |
| UI-06 Success | /ok/:code | OkPage |
| UI-07 Login | /login | LoginPage |
| UI-08 Mesa hoy | /hoy | HoyPage |
| UI-09 Empty | mismas rutas | empty branches |
| UI-10 Error | mismas rutas | error branches |
| UI-11 Loading | mismas rutas | skeletons |
| UI-00 Tokens | — | tailwind.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/VOCALcomo 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)
| Item | Nota |
|---|---|
servida_at en repetición | North star medible en SQL |
| Filtro de hoy por estado | 4 filas no lo necesitan; 20 sí |
| Rate limit POST repetir | Demo no lo necesita |
| Recibo por… no: sigue sin cuenta | No email de oyente |
| PWA install | Portería; no es Must |
| Trigram search | 4 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
| Token | Valor |
|---|---|
| Duración corta | 120–160ms |
| Easing | ease-out |
| Reduced motion | 0ms |
| Target | ≥44×44 |
| Pending | control disabled + aria-busy |
Loading (UI-11) y empty (UI-09) / error (UI-10) viven con los gestos. [DECISIÓN]
2. Galería (UI-01) + mobile (UI-02) · H-GALLERY
| Gesto | Resultado |
|---|---|
| Primera carga | Loading: 4 tiles rail/papel; aria-busy |
| 200 con 4 | Lead Patio 17 16/10 + 3 cubiertas. Hora, host, estado |
| 200 con 0 | UI-09 empty |
| Error 5xx / red | UI-10 + Reintentar (repite GET programas) |
| Tap cubierta | /programa/:code |
| Tap Conductora (ghost) | /login o /hoy si hay token |
| Hover cubierta desktop | Ring 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]
3. Buscar (UI-03) · F-SEARCH
| Gesto | Resultado |
|---|---|
/buscar sin q | Campo + prompt de archivo; no lista los 4 (eso es la galería) |
| Submit / debounce q | GET /api/programas?q= · pending |
| 200 con hits | Filas thumb + título + hora + host + estado |
| 200 [] | UI-09 «Sin resultados.» |
| Error | UI-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)
| Gesto | Resultado |
|---|---|
| Primera carga | Loading: skeleton de ficha |
| 200 | Cubierta, título, hora, host, estado, copy × oposiciones, CTA |
| 404 | «Ese programa no está.» |
| Tap Pedir repetición | POST · pending en botón |
| 201 | /ok/:code con state |
| Error red | seal + mensaje; no navega |
| Tap ← Estudio | / |
AC: una pantalla. Cero player. Cero «Reservar 18:00».
5. Acuse (UI-06)
| Gesto | Resultado |
|---|---|
| Con código | DM 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)
| Gesto | Resultado |
|---|---|
| Submit | POST login · pending |
| 200 | /hoy o next seguro (/hoy only) |
| 401 | «Email o contraseña no valen.» |
| Ya hay token | Redirect hoy |
next open-redirect: allowlist /hoy únicamente. [DECISIÓN]
7. Hoy (UI-08)
| Gesto | Resultado |
|---|---|
| GET hoy loading | 4 skeletons |
| 200 con programas | Lista. EN_ANTENA primero |
| 200 [] | UI-09 |
| Error | UI-10 |
| Tap fila | v1: no hay ficha; el gesto no navega a /programa salvo Could. [DECISIÓN] lectura |
| Salir | logout → / |
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
| Paper | Doc |
|---|---|
| UX-00 Cover | README + 01 |
| UX-01 Stakeholders | 02 §3 |
| UX-02 Personas | 02 §4 |
| UX-03 JTBD | 02 §5 |
| UX-04 Stories | 02 §6 |
| UX-05 Journey | 02 §7 · 04 |
| UX-06 Blueprint | 02 §8 · 06 |
| UX-07 Site map | 03 |
| UX-08 Flujos | 04 |
| UX-09 Datos+permisos | 05 · 11 |
| UX-10 Métricas | 12 |
| UX-11 Research | 02 |
UI producto → app
| Paper | Ruta | Componente |
|---|---|---|
| UI-00 Tokens | — | tailwind.config.js + styles.scss |
| UI-01 Gallery | / | GalleryPage |
| UI-02 Gallery mobile | / 390 | GalleryPage |
| UI-03 Search | /buscar | SearchPage |
| UI-04 Programa | /programa/:code | ProgramaPage |
| UI-05 Programa mobile | /programa/:code 390 | ProgramaPage |
| UI-06 Success | /ok/:code | OkPage |
| UI-07 Login | /login | LoginPage |
| UI-08 Mesa hoy | /hoy | HoyPage |
| UI-09 Empty | mismas rutas | empty branches |
| UI-10 Error | mismas rutas | error branches |
| UI-11 Loading | mismas rutas | skeletons |
Guión de prototipo
- Cover → Galería (4 cubiertas, lead Patio 17).
- Ficha Patio 17 en antena.
- Acuse ON-2301 · pedir ≠ reservar.
- Buscar «patio».
- Login Nerea.
- Hoy · lista · ON-2301.
- Empty / error / loading.
- 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
| Pieza | Estado |
|---|---|
| Docs 01–20 + README + executive | OK (este set) |
00-day-brief / 00-paper-reference | Intactos (no tocados) |
| Paper 12 UX + 12 UI | Inventario en 00 (no se arranca Paper aquí) |
App path /Users/cristian/orca/onda-app | OK · rewrite en paralelo |
Neon falling-fog-92644546 | OK |
API :3023 · health onda-api | Contrato (leftover relevo-api prohibido) |
| Código programas + repeticiones + JWT CONDUCTOR | Contrato paralelo |
| Cierre sin deuda del L1 | OK · roadmap solo L+1 / L4 |
2. Terna y diversidad
| Check | Evidencia |
|---|---|
| R-HY · S-RAIL · H-GALLERY · F-SEARCH | Brief + 01 + 07 |
| 0 códigos = RAMAL | RAMAL era R-CL · S-SPLIT · H-FULL · F-ONB |
| ≥2 vs TEMPLE / PREGÓN | Rail/Gallery/Search vs kiosk/book y chat/kanban |
| S-RAIL, H-GALLERY, F-SEARCH no en últimos 5 | Brief |
| Ventana 4: R-HY | R-IN + R-HY + R-CL + R-HY |
Ground G-SIGN #FFFFFF | 00 + 07 + Tailwind |
| Sustituye RELEVO | 0 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
| AP | Mitigación |
|---|---|
| AP-12 hero 2-col | H-GALLERY cubiertas |
| AP-21 card hueca | Lead + hora/host/estado |
| Leftover como estado | Contrato ONDA; no se anota RAMAL ni RELEVO como producto vivo |
| Entrevistas inventadas | 02 etiquetado |
| Recogida escolar | Solo como anti-patrón (retirado) |
| nodes/alta/VOCAL | 0 en dominio; 05 + 20 |
5. Seed
4 programas · 1 EN_ANTENA · 1 PROGRAMADO · 2 ARCHIVO · Nerea CONDUCTOR · Iker sin cuenta · Pilar fuera.
| code | title | status |
|---|---|---|
| ON-2301 | Patio 17 | EN_ANTENA |
| ON-2302 | Mercado Usera | ARCHIVO |
| ON-2303 | Voces del bloque | PROGRAMADO |
| ON-2304 | Banda municipal | ARCHIVO |
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)
| ID | Item |
|---|---|
| D01 | Galería 4 cubiertas H-GALLERY |
| D02 | Buscar archivo F-SEARCH |
| D03 | Ficha /programa/:code |
| D04 | Acuse /ok/:code · POST repetir |
| D05 | Login JWT CONDUCTOR |
| D06 | Hoy lista, no kanban |
| D07 | Empty / error / loading |
| D08 | Galería 390 |
| D09 | Seed 4 programas |
| D10 | Health onda-api :3023 |
| D11 | Copy ×4 |
| D12 | Docs 01–20 + README + executive |
2. Could L+1 (no bloquea cierre)
| ID | Item |
|---|---|
| C01 | servida_at / marcar servida |
| C02 | Filtro hoy por estado |
| C03 | Rate limit POST repetir |
| C04 | PWA |
| C05 | Trigram search |
3. Explícito L4 / otro brief
| ID | Item |
|---|---|
| E01 | Login Pilar / cuadrante de antena |
| E02 | Streaming / player |
| E03 | Parrilla semanal |
| E04 | Multi-emisora |
| E05 | Chat / kanban |
| E06 | Reserva 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
| Campo | Valor |
|---|---|
| App path | /Users/cristian/orca/onda-app |
| API | NestJS · puerto 3023 · prefijo /api |
| Web | Angular standalone · puerto 4200 |
| Package | apps independientes (pnpm --filter) |
| DB | Neon PostgreSQL · project falling-fog-92644546 |
| Tablas | users, programas, repeticiones, sessions |
| Auth | JWT Bearer · role CONDUCTOR (/hoy); galería, búsqueda y POST repetir públicos |
| Dominio | User, Programa, Repeticion |
| Fecha | 2026-08-23 |
| Health | onda-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
| Rol | Password | |
|---|---|---|
| CONDUCTOR | nerea@onda.radio | password123 |
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:
CREATE TABLEdeusers,sessions,programas,repeticiones(doc 05).- Índices:
programas(code),programas(status),users(email),repeticiones(programa_id). - Seed idempotente:
- upsert CONDUCTOR
nerea@onda.radio(hash bcrypt canónico) - upsert
programasporcode(ON-2301…ON-2304) - no seed de
repeticiones(el POST de smoke las crea)
- upsert CONDUCTOR
- 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)
| Tabla | Contenido |
|---|---|
users | CONDUCTOR Nerea |
programas | 4 emisiones |
repeticiones | Peticiones anónimas (vacío al seed) |
sessions | sid + 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 === CONDUCTORprotege/hoy. - Públicos: health, programas, programas?q=, programas/:code, POST repetir, login.
Oficio
| Método | Ruta | Auth | Notas |
|---|---|---|---|
| GET | /api/health | No | { ok: true, service: "onda-api" } |
| GET | /api/programas | No | sort_order asc + lead + hora + host + status + cover |
| GET | /api/programas?q= | No | ILIKE title/host/code/lead |
| GET | /api/programas/:code | No | ficha |
| POST | /api/programas/:code/repetir | No | INSERT repeticion; 404 si no hay programa |
| GET | /api/hoy | JWT | 4 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ágina | Ruta | Responsabilidad |
|---|---|---|
GalleryPage | / | H-GALLERY · GET programas · lead + 3 |
SearchPage | /buscar | F-SEARCH · GET ?q= |
ProgramaPage | /programa/:code | Ficha · POST repetir |
OkPage | /ok/:code | Acuse · state de navegación |
LoginPage | /login | JWT CONDUCTOR |
HoyPage | /hoy | Lista · 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ódigo | Título | Status | Hora | Host | Cover |
|---|---|---|---|---|---|
| ON-2301 | Patio 17 | EN_ANTENA | 17:00 | Nerea | /assets/programas/micro.jpg |
| ON-2302 | Mercado Usera | ARCHIVO | 11:00 | Iker | /assets/programas/cinta.jpg |
| ON-2303 | Voces del bloque | PROGRAMADO | 20:00 | Nerea | /assets/programas/antena.jpg |
| ON-2304 | Banda municipal | ARCHIVO | 18:00 | Pilar (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.