Repository navigation
Releases: AngBan2x/epk-dashboard
Release list
v4.0.0-rc.36: P12 cerrada y P13 hasta la frontera
v4.0.0-rc.36
Cierra P12 (auditoría) y abre P13 (publicación en redes) hasta su frontera.
P12 — auditoría post-rc.35
Cerrada con el E2E de producción en verde: 136/136 en 40,7 minutos, sin
fallos ni saltados. Era el paso 5 del Flujo Obligatorio.
- Corrección de compactación. La compactación automática saltaba al ~50 %
del contexto, yAGENTS.mdculpaba areservedy proponía subirlo a 100000.
Era falso:space-bunny-freedeclaralimit.output = 524288, exactamente la
mitad de sus 1.048.576. La salida máxima se descuenta del presupuesto de
entrada, y de ahí el 50 %.muse-spark-1.3comparte los mismos 1M y llega al
87,5 % porque pide 4× menos salida. Se sobrescribelimit.outputa32000. - Tests que no podían fallar.
scripts/turso-check.tscomparaba de verdad y
devuelve código de error;admin-catalogdejó de leer el fuente; y hay un
meta-test que cazait()vacíos yexpect(true). - Puertas de SQL en runtime.
updateTrackyupsertDossierfiltran por
allowlist: el nombre de una columna ya no sale del cuerpo delPOST. - Capturas de pantalla con guardas.
ReleaseTracklistSectionera
inalcanzable (solo se montaba en la ramaorphan, que no puede tener hijas) y
se borró. YbuildReleaseQueueya no hereda el vídeo de la cabecera. sync-data.ymlborrado, no arreglado. Sincronizabatrackscon
INSERT OR REPLACEomitiendostatus,release_idystart_time.
Arreglarlo lo habría hecho destructivo.- Landing en modo oscuro. El hero se capturaba a medias porque el generador
esperaba 600 ms en vez de a que terminaran las animaciones. No era un bug del
producto.
P13 — publicación en redes sociales
Hasta la frontera, sin ninguna llamada real a ninguna plataforma.
- 20 capturas en
public/pressplay/(3,21 MB), servidas por CDN de Vercel, con
dos guardas que abortan ante una captura en blanco. - Las cuatro páginas públicas se capturan sin sesión: la primera versión
salía con el nombre de una persona real y su contador de notificaciones en una
imagen destinada a ser pública. social-drafter: redacta borradores y no puede publicar.bash,edit,
write,task,webfetchywebsearchestán endeny, y hay un test que lo
comprueba.- Seis adaptadores (
bluesky,threads,instagram,facebook,linkedin,
mastodon) con la capa HTTP inyectada, y una tabla única de límites con fecha
de verificación. - Bug encontrado por los tests:
validarde Mastodon comparaba siempre
contra el 500 de la tabla, así que con una instancia que permite 280 un texto
de 300 pasaba la validación. - Corrección de diseño:
AdaptadorSocialno teníapublicar, así que
crearAdaptador()devolvía la mitad de un adaptador.
Fuera, con su motivo
- TikTok: gratis, pero sin auditar queda en private viewing mode.
- X: ya no tiene plan gratuito. 0,20 $/post con URL.
Pendiente declarado
- La landing y la ficha de release no tienen landmark
<main>. admin-catalog.test.tssigue leyendo el fuente; sus dos aserciones de datos
son ejecutables y no lo están.- Los límites de caracteres de Instagram, Facebook y LinkedIn no están
publicados en ninguna parte. No se han rellenado de memoria.
Gate: 1657 tests en 84 ficheros, tsc, next lint y build limpios.
v4.0.0-rc.35
v4.0.0-rc.35 — Release completo (reproductor + datos + ficha + admin)
Reproductor consolidado (0b5efa5)
- AudioPlayer con queue (playsQueue), hasChildren y isQueueTrackActive para álbumes con hijas; etiqueta de fuente honesta (
esolvePlayingLabel en lib/audio-priority.ts); sin reinicio al pausar. StemsPlayer y LyricsModal borrados.
Datos normalizados (a35fa6f)
- 83 tracks (�lbum 56, �p 6, single 21);
elease_type minúsculas (�lbum/�p/single); Vulnicura Strings (
el-869486ab) marcado como �p por decisión de catálogo. Guard urso-check.ts case-insensitive (lower(release_type)='ep'). - Previews exactos aplicados en Turso (5 pistas): Heroes ( rk-37bd32a1), Stonemilker ( rk-5b53f1b7), Black Lake ( rk-c30ee43d), Family ( rk-74854e56), Notget ( rk-fd59c7ab). 3 aproximaciones rechazadas documentadas. Dry-run idempotente (0 cambios nuevos).
Ficha de release rediseñada (9e1746f)
- Letras interactivas por pista (LyricsSectionWrapper + LyricsSection existente, no LyricsModal); ProductionDetailsWrapper en �side debajo de enlaces; navegación prev/
ext borrada (getReleaseNeighbours eliminado); ests/unit/release-page.test.ts actualizado (Single sobre 2 pistas, no EP).
Admin consolidado (268b573)
- Pestaña catalog (AdminTab) con filas expandibles (�uildReleaseQueue);
elease_type y �xternal_links editables (PUT /api/admin/releases); ImageUploader disponible para artistas (kind=profile|banner + �rtistId). ests/unit/admin-catalog.test.ts cubre 4 arreglos reales.
Shows cleanup (parte del bloque datos)
- .github/workflows/cleanup-shows.yml: cron diario �6:17 UTC con workflow_dispatch; DELETE /api/shows/cleanup con corte 48h exactas (lib/show-cleanup.ts); �nv leído en tiempo de llamada; aviso �est-effort; TEST_ADMIN_PASSWORD pendiente (docs/CONFIG_TODO.md C12).
Documentación
- docs/AI_LOG.md: referencias a arreglos; docs/PHASE_P3.md: P11 actualizado con referencias exactas (lib/release-page.ts, ests/unit/release-page.test.ts); docs/CONFIG_TODO.md: C12 pendiente; docs/modelos gratuitos disponibles.txt: intacto (384b48f).
Invariantes protegidas
eleaseTypeLabel (lib/release-page.ts) dice la columna (Single), no inventa EP (reverso verificado con test); ests/unit/no-audio-y-enlace-al-artista.test.ts protege el helper; ests/unit/release-page.test.ts cubre 77 casos; ests/unit/admin-catalog.test.ts cubre 4; ests/unit/stems-dead-code.test.ts confirma archivos borrados (StemsPlayer/LyricsModal sin importaciones en todo el repo).
Fotos para los 12 artistas, ficha de release legible y una suite E2E que por fin corre contra produccion
Los 12 artistas del catálogo tienen ya foto de perfil y banner, la ficha de release se rediseñó para que se lea, y —lo que más tiempo llevó— la suite de Playwright podía correr, pero no contra producción: 76 de sus tests eran inútiles fuera de pnpm dev.
Fotos de los artistas (F6)
Los 12 artistas tienen las dos imágenes. scripts/apply-artist-images.ts compara antes de escribir y se niega a pisar un valor revisado sin --force.
- Siete URLs con el host alias
thumb.wikimedia.orgcanonicalizadas aupload.wikimedia.org. Quedan 0 alias. - Björk y David Bowie con perfil y banner, y Pink Floyd, Radiohead y Kraftwerk con perfil. Las tres bandas entran con foto de escenario: en Commons no hay retrato de grupo de una banda. Se eligió mirando las fotos una a una, no por la puntuación del verificador, que ya había aceptado candidatas inservibles con 75.
- Dos candidatas de Kraftwerk se descartaron por ser retratos de una persona concreta (Pfaffe, Ralf Hütter) por mucho que el título dijera "Kraftwerk".
- Los avatares se verificaron a 80 px en producción, que es donde se commoda el recorte en círculo.
La ficha de release (F3/F7)
/releases/[id] usa ahora el mismo esqueleto que la ficha de pista: migas de pan, hero con portada 1:1, bloque de hechos y sidebar sticky. El tipo de release se resuelve en un único sitio (releaseTypeLabel), el icono de silencio es uno solo, y "Ver su catálogo" lleva al artista por ancla. AudioPlayer ya resolvía preview → YouTube → estado vacío, así que no hizo falta lógica nueva.
Modo oscuro
Cuatro sitios más con dark: a medias: sugerencias (admin y pública), aprobaciones y su filtro. La comprobación vive en tests/unit/dark-mode-pairing.test.ts.
La suite E2E: podía correr, pero no donde debía
PLAYWRIGHT_BASE_URL=https://epk-dashboard.vercel.app es lo que documentaba AGENTS.md, y no funcionaba: baseURL estaba fijo a localhost:3000 y webServer levantaba next dev siempre. Todos los specs se iban al dev server local en silencio, que además es la configuración que peor funciona (ABI de better-sqlite3, aserto nativo de teardown de V8).
Corregido eso, aparecieron las capas de debajo:
networkidleen 43 sitios de 6 specs: contra producción nunca se cumple —hay polling de notificaciones y conexiones vivas—, así que cadapage.gotomoría a los 60 s. Solo se cumplía en local.- Expectativas de hace fases:
StemsPlayerse eliminó a propósito en P3.15 y el componente sigue en disco sin usarse;/track/<id>es un shim de 301 a/releases/<id>desde P8 y la mitad de los tests esperaban la URL vieja;/es la landing pública y no una redirección a/dashboard. - Specs sin sesión:
multimedia.spec.tsno iniciaba sesión, y sin sesión/dashboardno pinta catálogo. - Datos del seed local: se buscaba el show "teatro" cuando producción tiene uno en Valencia; se pedía el venue "Teatro Municipal".
- Un bug de test con efecto de bug de producto:
auth-qaejecutaba en el navegador una función que referenciabaADMIN_EMAIL, un import de Node (ReferenceError: _credentials is not defined), yphase-n-register-deleterellenaba el formulario antes de la hidratación, así que React lo enviaba como formulario nativo y la página se recargaba. El síntoma era "La contraseña debe tener al menos 8 caracteres" con una contraseña de 12.
Uno sí era producto: approvals-qa pedía 403 cuando un artista intentaba auto-aprobar y recibía 200. Se comprobó en Turso que los releases seguían en draft: resolveSubmitStatus ignora el estado pedido y conserva el vigente, que es el diseño que sustituyó al 403 porque con las 83 filas en approved ningún artista podía guardar ningún release. El test, no el producto, estaba obsoleto.
Correcciones de producto
NotificationBell:showSkeletonno exigía que hubierauser, y sin sesiónload()ni siquiera se llama, así que el estado de carga no podía resolverse nunca. Es una corrección defensiva: en/dashboardla campana no se pinta sin sesión, así que no se observó un fallo visible; lo que sí era un fallo era el test, que contaba.animate-pulseen el documento entero cuando esa clase la usan tres componentes.qa-cleanup.ts: solo buscaba[QA]entre corchetes, pero los specs titulan sus releasesQA P45 Draft <timestamp>. Informaba 0 tracks con dos filas QA ahí dentro.- 74 métricas de relleno a
NULL, en dry-run y con su error documentado. - Vocabulario cerrado de shows: 6 estados elegibles más 2 derivados.
Rotación de credenciales (P7-P10)
Las semillas leen la contraseña del entorno en el punto exacto donde insertan. Se añadió una guía de rotación de credenciales, claves de redes y reescritura de main, con el hallazgo de por qué esa reescritura no funcionaba en Windows: un fichero Zone.Identifier commiteado cuyo : es inválido en NTFS.
Verificación
tsclimpio, lint sin warnings, 1259/1259 tests unitarios (67 ficheros), build correcto.- E2E contra producción: 135/138 en la última corrida completa de 45 min. El fallo restante era la campana de notificaciones, arreglado y verificado en solitario (3/3); la suite completa no se volvió a pasar después de ese último arreglo.
- Producers datos: 12 artistas, 0 sin perfil, 0 sin banner, 0 usuarios QA y 0 releases QA tras la limpieza.
Catalogo real, buzon anonimo, una pagina por release y credenciales fuera del codigo
Catalogo real, credenciales fuera del codigo, buzon y una pagina por release
Cierra las diez olas de RC.33 y las cuatro subfases de la Fase P.
1146/1146 tests en 61 ficheros, tsc y lint limpios, build correcto.
Seguridad
PATCH /api/tracks/[id]no comprobaba ownership: cualquier usuario
autenticado podia reescribir la pista de otro. Corregido con el mismo criterio
deisArtistOwnerOfTrackNameque usan el resto de rutas.- La credencial de una cuenta real estaba en 41 ficheros (16 scripts, 12 specs
de Playwright y helpers). Ver P1 abajo.
Una sola pagina por release
/track/[id] pasa a ser un shim de 301. Un single es fila de tracks y
cabecera de release con el mismo id, asi que eran la misma pagina con dos
plantillas: SEO partido, metricas partidas y 535 lineas de ficha sin nada que la
enlace.
Las hijas redirigen al release padre. Una hija cuyo padre ya no existe
(release_id no es foreign key, y la cascada no siempre corre) se renderiza
con aviso, no 404: el contenido existe y es reproducible.
Correccion del bug de duracion: un single sin hijas mostraba 0:00 y
"0 pistas", porque la suma se hacia sobre las hijas. Ademas el filtro de "no hay
dato" no puede ser un isNaN, porque sumDurations(["00:00"]) parsea bien:
"00:00" es el relleno del seed, no un disco de cero segundos.
Fuera las hijas del sitemap, del prev/next y de las notificaciones.
Correo
RESEND_API_KEY esta configurada pero FROM_EMAIL no, porque no hay dominio
verificado. Sin dominio verificado Resend si envia, pero solo desde
onboarding@resend.dev y solo al titular: a otra direccion, 403 garantizado.
Por eso el envio es ahora inyectable y el correo tiene tres estados, no
dos: sent, failed y not_attempted. Sin FROM_EMAIL no hay llamada, y
contarlo como fallo haria que el panel dijera que se perdieron correos que nadie
iba a mandar.
Suite normal con transporte falso (verde siempre). Envio real solo con
RESEND_SMOKE=1, y en un script, no en un spec: gasta cuota real de Resend
(100/mes en el plan gratis) y un fallo de cuota no puede apagar el gate.
Buzon de sugerencias (anonimo, solo admin)
Cinco capas de anti-spam, todas en el servidor: honeypot, tiempo de formulario,
rate limit por IP (10/h), 1 por correo cada 24 h y topes de longitud.
Las cuatro capas de descarte devuelven exactamente el mismo 201: si
distinguieran, un bot solo tendria que recorrerlas en orden. La excepcion es el
429, porque quien comparte IP con media oficina y se bloquea sin explicacion no
tiene forma de saber que tiene que esperar.
El limite duro es el de correo, no el de IP: castiga mas al spam y no castiga
a quien comparte NAT. ip_hash es un HMAC con SESSION_SECRET, no un SHA: el
espacio IPv4 son 2^32 valores y un SHA suelto es la IP con tres pasos de mas.
El aviso al admin es best-effort y va despues de escribir: un 502 de Resend no
puede llevarse por delante el mensaje de alguien que solo queria reportar un
problema.
Catalogo
- 29 de 74 videos oficiales con
video_kind; las 14 coincidencias aproximadas se
dejaron sin escribir a proposito. - 5 canales oficiales, incluidos los
- Topic. - 3 banners de Wikimedia (Pink Floyd, Radiohead, Kraftwerk), revisados a ojo.
Bowie y Bjork se quedan sin avatar: su composicion no funciona en circulo ni en
wide, y la inicial con degradado es mejor que un recorte malo. - Last.fm real como fallback de metricas; un album hereda los playcounts de sus
hijas. - Carrusel responsive 1/2/3/4.
El patron UC... sobre la descripcion del canal no dio ni uno de los 5: las
descripciones estan en espanol o vacias. Lo que funciono fue la allowlist
explicita.
Accesibilidad
ReleaseTrackList.tsx tenia el mismo aria-label="Reproducir" en todas las
filas, botones de 32 px (por debajo del minimo tactil de 36 px) y sin anillo de
foco. Ahora: 44 px, anillo visible, y etiqueta con numero y titulo.
P1 — las credenciales de test
Regla: contrasena sin default, correo con default solo si no es PII ni secreto.
Si el helper hubiera sido process.env.TEST_ARTIST_PASSWORD ?? "12345678",
funcionaria y dejaria el secreto en el repo: un cambio cosmetico con forma de
arreglo. Contrasenas a "". TEST_ADMIN_EMAIL conserva admin@epk.local
(dominio inventado). TEST_ARTIST_EMAIL tampoco: la unica cuenta de artista
conocida es de una persona real.
playwright.config.ts ahora carga .env.local, porque Playwright no hereda la
carga de Next y sin eso los 12 specs leian undefined.
Verificacion
Cinco reversiones de arreglo, cinco tests rojos. Los de resolveTrackRoute leen el
SQLite local de verdad, no un doble: prueban que parseTrack expone
release_id. Si esa columna deja de mapearse, el redirect se apaga en silencio y
los tests lo detectan solos.
Pendientes que siguen abiertos
scripts/seed-admin.tssigue conhashSync("admin123"). Es una decision de
producto, no un olvido: si el admin de produccion se rota, el seed debe leer del
entorno y fallar sinTEST_ADMIN_PASSWORD.- La credencial sigue en el historial de git. Rotarla y reescribir
mainson
los dos pasos que quedan, y estan endocs/PLAN_FASE_P.md. deleteUserno borra las sugerencias de ese usuario: decision de privacidad
pendiente.POST /api/exportsigue siendo publico aunque la UI oculte dossier/rider.lib/resend.tsqueda huerfano tras P3.- 14 videos "parecidos" y los canales
- Topicde Bowie y Bjork esperan revision. - Las pruebas E2E de Playwright no se ejecutaron: necesitan
pnpm deven :3000.
Edicion de releases sin perdida de datos, cola de album y catalogo con artwork real
name: RC.32 - contrato de edicion, audio, aprobaciones y catalogo real
tagPrefix: v4.0.0-rc
isPrerelease: true
createdAt: 2026-10-01T00:00:00Z
Que cambia
Tres cosas que el usuario puede notar, y cuatro que no, que son las que
importan.
El editor de releases dejo de perder datos y de auto-aprobar
Editar un release sin ser admin ya no funciona: el reenvio de status hacia
que un no-admin se auto-aprobara poniendolo a approved, y el backend lo
aceptaba. El estado de decision se ignora siempre en esa rama.
Editar un release siendo su dueno tampoco funcionaba: tres redirects por un
403. Y cuando se conseguia guardar, se perdian las pistas hijas nuevas y se
borraba el motivo de rechazo del admin. Todo arreglado y verificado.
La lista de correcciones encadenadas esta en el commit 4109794, porque el
quinto bug solo aparecio al arreglar el cuarto.
El reproductor de album
Un preview de 30 s arrastraba el final de capitulo del album y el scrubber
saltaba de pista a mitad. start_time/end_time son milisegundos dentro del
preview en un caso y offsets dentro del video en el otro, y se estaban
tratando como la misma magnitud.
El tracklist de OK Computer pasa a mostrar las 12 duraciones reales (antes
todos a 00:00), y el dashboard monta una cola de las 12 pistas en vez de
anunciar que no hay audio.
Catálogo con caratulas reales
9 filas tenian una foto de Unsplash. Revisadas a ojo, no eran "imágenes
genericas": eran retratos creibles de bandas que no tienen nada que ver, como
Brothers in Arms con un hombre mayor. Sustituidas por artwork real de Cover Art
Archive. unsplash: 0.
Lo que no va a aparecer, y por que
0 videos de YouTube. Se implemento la verificacion de canal oficial — el
canal tiene que enlazar en su descripcion al sitio del artista, con el nombre
coincidente — y los 5 artistas fallan. Radiohead viene con la descripcion
vacia. No se relajo la regla para tener algo que mostrar: eso seria
exactamente el bug que la regla evita.
0 previews de Deezer. Su CDN permite CORS, pero la URL va firmada y caduca
a los 900 s. Se comprobo guardando una y consultandola a la hora: 403. Persistir
esa URL es escribir un enlace muerto.
Correcciones internas
- Paginacion del catalogo sin orden.
getAllTracks()eraSELECT *sin
ORDER BYy encima pagina, asi que una pagina podia repetir o saltar filas.
El fallo de un test lo destapo; la causa era que la base local de pruebas
estaba contaminada. - Aprobacion en cascada. Aprobar un release deja las hijas en
draft, y el
contador del dashboard no cuadraba con las tarjetas. Ahora es transaccional. - 5 timestamps de album corregidos en produccion, con minutos de desviacion
sembrados, y verificacion para que no vuelvan. lib/artist-promotion.tsusaba el patron de escritura que las propias
reglas del repo prohibian, y la llamada que creates el perfil no existia: el
metodo lanzaba siempre. Los 7 artistas ya tienen perfil./api/syncdeshabilitado (410). Era unINSERT OR REPLACEque omitia
campos, incluido el motivo de rechazo del admin, y su mapeo no includia el
estado de aprobacion. Ejecutarlo revertia el catalogo entero adraft.- Dos definiciones distintas de "hay Turso". Una leia el entorno al
importar y otra al llamar, y con el entorno cargando tarde las rutas
respondian 500. Esto habia costado tres despliegues. - El video embebido no se podia auditar. La ruta de YouTube no pedia el
estado del video, y las politicas de la plataforma exigen comprobar la
marca "hecho para niños" en cada embebido. Ahora el dato esta disponible.
Documentacion
El README afirmaba cosas que eran falsas: el numero de tests (110 -> 800), que
habia una tabla releases (no existe: un release es una fila de tracks con
release_id nulo), y el numero y las rutas protegidas. Anadidas las secciones
que faltaban, entre ellas la de credenciales.
Seguridad
README.md contenia una contrasena real. Sustituida por instrucciones. El
secreto sigue en el historial de git, asi que hay que rotar la contrasena y
reescribir el historial: eso requiere una accion manual.
Gates
tsc limpio · lint sin avisos · 800/800 tests en 47 ficheros · verificacion
en produccion de /api/sync, /api/tracks, /api/youtube, la cola del
catalogo, el scrubber y los controles del reproductor.
Los tests necesitan --no-file-parallelism. En paralelo, el reciclado de workers
dispara una asertacion nativa de teardown de V8 y el proceso muere. No es un test
rojo: los 800 pasan. Esta documentado con el hallazgo.
S0, audio real, catalogo real y las 15 correcciones
Cierra 15 problemas reportados, 2 huecos estructurales y el flujo de promocion de suscriptor a artista. Ocho agentes en cuatro olas con propiedad exclusiva de ficheros; los commits los hizo el orquestador.
La fuga que no era una fuga
El sintoma era "Error al iniciar reproduccion - archivo no disponible" y la causa documentada era CORS. Medido con curl -I: el CDN de Apple si manda Access-Control-Allow-Origin: *. El arreglo de AI_LOG:1268 resolvia un problema inexistente. Lo que fallaba eran 6 URLs de preview caducadas (404).
Mismo patron que el Helvetica del PDF: un arreglo plausible que se llevaba el credito de un sintoma con otra causa. Lo desmonto un curl de una linea.
El parser que duraba mal
tracks.duration es texto con cinco convenciones conviviendo. El parser estaba duplicado en dos ficheros y destruia solo [m, s], asi que "1:02:03" sumaba 62 s en vez de 3723, y el guard isNaN no salta porque [1,2,3] no tiene NaN. No era hipotetico: lib/youtube.ts emite H:MM:SS y se escribe en duration.
El suscriptor no podia llegar a ser artista
AGENTS.md decia que si; el codigo devolvia 403. Pero aflojar el rol habria sido peor: userHasApprovedContent solo mira track_submissions, asi que un pending en tracks no dispara la promocion. El camino correcto ya existia en servidor (POST /api/submissions, sin chequeo de rol); faltaba la UI.
Seguridad (S0)
GET /api/releases?id=Xsinuser_idhaciaSELECT *a secas: devolvia la fila cruda conadmin_notes(motivo interno de rechazo) y sin filtro de estado. Un borrador era legible por cualquiera con el id.GET /api/trackscon la misma fuga./api/dashboarddevolvia shows no aprobados a anonimos.ReleaseActions.tsxeran 33 lineas sin ningun chequeo de rol, renderizado siempre en pagina publica./releases/:id/editsolo comprobaba que el token HMAC fuera valido: cualquier cuenta autenticada entraba.GET /api/dossierssin sesion ni propiedad.
Datos
- P3: de 6 a 63 pistas con audio real (previews de iTunes verificados con HTTP antes de escribir).
- P1: de 0 a 71 filas con caratula real de album.
- Los 9 padres tenian
duration = "00:00": ahora la suma de sus hijas. - Corregidas erratas vivas en Turso: "Ashe to Ashes", las comillas literales de "Heroes", Vulnicura Strings marcado EP siendo album.
- Arreglada una colision de datos en
--cleanupque podia dejar padres con 10 hijas huerfanas. release_id,disc_numberytrack_numberal catalogo JSON, con test de forma (no habia ninguno).- La ficha tecnica descargaba el catalogo global de los 83 tracks de todos los artistas mientras el copy prometia "el catalogo completo del EPK". Resuelve el artista y el copy dice lo que es.
Descargas
5 opciones -> 3 filas x 3 formatos centrados. El backend ya soportaba las 9 combinaciones; faltaba UI. Container query en vez de breakpoint de viewport, porque vive en una celda de 304-347px.
UI
- Las tres tarjetas del dashboard envolvian
EPKCarden un<a>, conteniendo los botones de like y play. Era el defecto que la propia tarjeta evita yaxe-checklo visitaba. - 4 releases por pagina con 0 titulos truncados en los 5 breakpoints. El 4 es el dato facil; los 0 truncados son el importante, porque son el motivo del commit
bfdda30. /admin/approvals: 16 controles llevabanfocus:ring-*, queglobals.cssNO suprime, asi que cada boton verde, rojo o azul llevaba su anillo mas un contorno global rosa.
Audio
Cola completa con avance automatico en modo YouTube y HTML5. extractAppleMusicId no casaba con ninguna URL real de Apple Music.
Verificado en produccion
admin_notes ausente · duracion 8:21 en vez de 00:00 · 12 artistas (ya no 7) · sin "Editar Release" ni "Aprobado" para anonimo · /api/dossiers 401 · ficha tecnica limpia en 5 anchors, dos ramas · rejilla 4/4/3/2/1 columnas con 0 truncados y 0 interactivos anidados.
tsc limpio, lint sin avisos, 542/542 en 39 ficheros. Cada fix comprobado revirtiendolo.
Sin resolver, a proposito
- 3 releases sin caratula real: el single no esta en iTunes y poner la del album habria repetido el fallo de Unsplash.
- 20 pistas sin audio, con motivo documentado (desambiguacion que no se pudo confirmar).
lib/artist-promotion.ts:139usagetDbWrite(): merece su propia ola.lib/turso.tssigue capturandoprocess.enval importar; suisTursoConfigured()colisiona con el de@/lib/dbcon semantica distinta.- El MCP de Vercel quedo autenticado pero con scope vacio:
list_teamsdevuelve[].
Releases que se ven bien: ficha de album con tracklist y ficha tecnica legible
Releases que se ven bien: ficha de album con tracklist y ficha tecnica legible
La ficha de /track/[id] estaba maquetada para una pista suelta y nunca se habia
mirado con un album, que es justo lo que D2 dejo en la base. Con una captura de
Kid A salieron tres sintomas y una sola causa de fondo.
La ficha de un album
Era la de una pista, aplicada a algo que no tiene letra, ni videoclip, ni
detalles de produccion, ni una cancion en Last.fm, ni audio propio. Seis
tarjetas vacias, un play que no hacia nada, y sin la lista de pistas, que es
justo lo que viene a buscar un periodista a un album.
- Nueva lista de pistas por disco y numero, con duracion y enlace a cada ficha.
- Letra, videoclip, enlaces y detalle de produccion se ocultan si no tienen
nada que mostrar. Cada tarjeta se oculta por sus propios datos, no por ser un
album: un padre con un enlace real lo seguiria viendo. - Sin reproductor, y sin metricas de Last.fm, que miden canciones.
- Las metricas de streams/likes/listas se sustituyen por pistas, discos y
duracion total. Antes ponia "0 Streams" en Kid A, que suena a que el album no
se ha escuchado nunca. - El SEO decia "Álbum - 00:00 - 0 streams" en buscadores y previews.
Las 9 portadas estaban rotas
Los 9 releases apuntaban a example.com, el dominio reservado por la RFC que
nunca resuelve. De 19 candidatas de Unsplash, 18 resuelven y 1 da 404: ahora hay
un script que las comprueba y descarta las que no devuelven 200 + image/*, en
vez de asumir. Se verifican antes de sembrar y despues, leyendo la propia base.
Y las portadas muertas ahora caen a un marcador con la inicial, que antes solo
pasaba con la URL vacia.
La ficha tecnica estaba rota, y no por su cuenta
sm:grid-cols-2 responde al viewport, y en la barra lateral de /track/[id] el
componente mide 347px aunque el viewport sea de 1440. Se partia en 2 celdas de
146px, y la fila se come el ancho con badge, dos huecos y el boton: al texto le
quedaban 0px, asi que cada palabra salia en su propia linea. Ahora decide
columnas por container query, con el umbral puesto por medicion, no a ojo.
Verificacion
Un barrido recorre un album y una pista suelta en cinco anchos, en local y en
produccion: 10/10 en ambos. Y se comprobo que el barrido falla al revertir
el arreglo (7 fallos en 1024-1440px, que es donde la barra es estrecha). Un
check que siempre pasa no protege de nada.
Gates: tsc 0, lint 0, vitest 378/378, build OK.
Tambien en esta tanda: MCP de Vercel y Turso, con los endpoints confirmados en
la documentacion oficial y una nota en AGENTS.md sobre lo que sigue siendo
tarea del repo y no de un servidor.
Catalogo multi-track, PDF de prensa y UI legible
Catalogo multi-track, PDF de prensa y UI legible en la pagina de artista
Ola 3 del plan rc29 -> rc31, completa y verificada en produccion.
12 commits. 378 tests (32 ficheros), antes 249.
PDF de prensa (B5) — nuevo
Boton "Dossier completo en PDF" en Descargas para prensa: dossier, rider y
catalogo en un archivo, con portada, navegacion por secciones y pie con
numeracion de pagina.
lib/pdf/genera el PDF con pdfkit, con Noto Serif incrustada en el bundle
para que los acentos del castellano no se pierdan.- Acentos, eñes y simbolos verificados leyendo el texto del PDF, no a ojo.
Catalogos agrupados por release (C2) — el cambio de fondo
Un album padre y un single suelto eran indistinguibles: ambos son
release_id IS NULL y no habia ninguna columna que los separase. Ahora
getArtistCatalog los agrupa con una sola consulta, cada album se lee como
disco (portada + lista de pistas con numeracion por disco) en vez de 11
tarjetas indistinguibles, y la ficha de la pista enlaza a /track/ o
/releases/.
Semilla con nombres reales (D2)
5 artistas, 9 releases y 65 pistas hijas en Turso (74 EPKCards), que es lo que
hace que C2 se pueda ver por fin. Albums con su tracklist real: The Dark Side
of the Moon, The Wall (2 discos), OK Computer, Kid A, Vulnicura Strings, y
cuatro singles con lados B.
Interfaz
/artists/[id]: las tarjetas ya se leen (198px -> 294px medido) y el
catalogo va a 3 columnas, porque con una lista de pistas dentro de cada celda
los titulos se cortaban ("Another ...")./shows: gradiente de marca,max-w-7xl, design system, stats en<dl>,
animaciones respetando prefers-reduced-motion y portada de reserva para los
shows sin flyer (los 2 de produccion no tienen).
Correcciones
- El header del artista se salia 194px del resto de la pagina.
createTrackdeclarabastatusy no lo escribia: todo lo que creaba
quedaba endrafty era invisible en lo publico. Los 378 tests pasaban con
la base vacia; lo destapo la semilla con datos de verdad.- Las fichas de pista se partian entre dos paginas del PDF, dejando los
metadatos de una pista huerfanos al principio de la siguiente. - El PDF daba 500 en produccion: el constructor de PDFKit carga Helvetica con
unrequiredinamico que no llega a la funcion de Vercel. Se evita sin
depender de ficheros, y sin reglas de tracing anode_modules(que Vercel
rechaza con pnpm por symlinks). - El
--cleanupde la semilla borraba solo los padres y dejaba 63 hijas
huerfanas.
Verificacion
Todo en produccion, no solo en local: el PDF se descarga desde la UI y abre
correcto, la pagina del artista muestra los albums agrupados, y 378 tests mas
typecheck, lint y build en verde.
Pendiente fuera de este alcance: WEBHOOK_SECRET (lo define el usuario) y el
email con Resend (falta FROM_EMAIL y hay cuota agotada).
Tranches 0-4: bugs de cuenta, seguridad, rendimiento, a11y, CI y SEO
Tranches 0-4: bugs que estaban rotos, seguridad, rendimiento, a11y, CI y SEO
Esta tanda no toca el envio de correo: responde a la pregunta de que mas se puede hacer o verificar sin depender de Resend. Salio de tres auditorias de solo lectura (cuenta y ciclo de vida, integraciones y claves, calidad/a11y/rendimiento).
Bugs que estaban rotos en produccion
- Ajustes de cuenta al 500.
app/api/user/settingsusabagetDbWrite(), que es el writer SQLite local y efimero: en Vercel (Turso) cambiar el email, cambiar la contrasena y eliminar la cuenta fallaban, y ningun E2E lo detectaba porque todos borran viaDELETE /api/auth/me. Reescrito sobre funciones Turso-aware nuevas enlib/db.ts. - La UI moria sobre el borrado de cuenta. Prometia 30 dias de gracia sin restauracion ni purga, y
deleted_atno bloqueaba ni el login ni la sesion; ademas esas cuentas seguian recibiendo notificaciones y correos. Ahora el login responde 403ACCOUNT_SUSPENDED,/api/auth/meresponde 403 y borra la cookie, el fan-out filtra pordeleted_at, y existe recuperacion con email y contrasena dentro de los 30 dias. - Contrasena minima unificada a 8, validada en backend (antes
/accountpedia 6 y el endpoint de cambio aceptaba"a"). - Paginacion real en la consola de aprobaciones: los botones Anterior/Siguiente eran no-op con
// TODOmientras mostraban "Pagina X de Y" al admin.
Seguridad
- Cuatro endpoints sin autenticacion cerrados:
POST /api/sync(ademas ejecutaba DDL),DELETE /api/shows/cleanup,POST /api/tracks/:id/streamsyPOST /api/webhooks/metrics. - Firma HMAC con
WEBHOOK_SECRETen los webhooks, con sesion de admin como alternativa. - Notificaciones que se perdian en silencio: con 6 de 7 artistas sin cuenta vinculada, aprobar no avisaba a nadie y el admin recibia exito. Ahora la respuesta incluye el estado de la notificacion, el admin ve un aviso rojo cuando el artista no fue notificado, y la consola muestra un chip "Sin cuenta vinculada" y el contador de casos afectados.
Rendimiento
- N+1 eliminado en
/api/dashboard: era unawait getLikeCount(track.id)por track en serie, mas cuatro consultas secuenciales. ConPromise.ally una sola consulta de likes, el dashboard autenticado baja a ~150 ms. Era la causa deltestTimeout: 30 s. - Optimizacion de imagenes activada: los
unoptimizederan un workaround porquenext.config.jsno declarabaremotePatterns. Ahora hay hosts, AVIF/WebP, ylib/image-config.tsdecide por URL si una imagen se puede optimizar (las de host desconocido oblob:siguen sin optimizar). Verificado: 9 imagenes por/_next/imagey 0 rotas. googleapiseliminado (203 MB) sustituido porfetchcontra la misma API y verificado con datos reales. Fuera tambiendate-fns,@aws-sdk/client-s3,@types/bcryptjs,@eslint/jsylib/youtube-stats.ts.- Visualizador diferido con
next/dynamicy waterfall del servidor de/track/[id]resuelto conPromise.all.
Accesibilidad y calidad
- 23 controles de
DossierEditorasociados a su label (el unico incumplimiento duro que quedaba). Verificado: 0 controles sin etiqueta. - Anillo de foco global en
globals.csspara todo control interactivo, con excepcion de los que ya pintan su anillo. Cubre los ~125 botones que no declarabanfocus-visible. ui/Buttoncumple 44px en sus tres tamanos.- 0 warnings de ESLint y bug latente del
AudioPlayercorregido (la fuente de audio nunca se reseteaba al cambiar de track). - Preferencias que si se respetan:
new_release_alertsyshow_alertsse leen de verdad en el fan-out, con tests.
Verificacion, CI y SEO
- CI: ahora usa Node 24 (el ABI del binario de
better-sqlite3lo exige) y ejecuta Playwright, que antes no bloqueaba nada. - SEO:
sitemap.xmlcon 22 URLs,robots.txt, manifest,metadataBase, OpenGraph y Twitter globales, ygenerateMetadata+ JSON-LDMusicGroupen la pagina de artista. - Contraste AA: axe da 0 violaciones criticas o serias en
/dashboard,/showsy/artists. - Lighthouse contra el build de produccion:
/shows95 de performance y SEO 100;/dashboard60 de performance y SEO 100, con el LCP bajado de 5.1 s a 4.6 s marcando la portada del primer card como prioritaria. El dashboard sigue siendo la ruta mas pesada y partirla en Server Components queda como decision posterior. - E2E en produccion: las 5 suites que faltaban (subscriptions 5/5, notifications 3/3, fanout 1/1, search 2/2, broadcast 1/1) mas matriz 50/50 y 249/249 unit,
tsc0 y build OK. - Higiene de datos: 9 tracks, 2 shows, 2 usuarios reales, 0 filas QA. En una tanda previa de esta sesion se eliminaron 13 usuarios de prueba que llevaban semanas en produccion.
Lecciones
- Un
-replacede PowerShell que no casa devuelve$nully puede escribir archivos a 0 bytes: se perdieron 5 componentes y se recuperaron congit checkout. No escribir codigo fuente con regex en PowerShell. - Una regla CSS dentro de
@layer baseno llegaba al CSS servido: el anillo de foco global no aparecia hasta sacarla del layer. Comprobar en el navegador que la regla existe endocument.styleSheetsantes de darla por buena. - Los E2E que verifican el registro fallan de forma esperada al cambiar el modelo de roles: hay que actualizar el contrato del test, no relajar la asercion.
Pendiente de decision (no bloquea, nada de esto requiere Resend)
BLOB_READ_WRITE_TOKENausente: subir imagenes da 500 en produccion.SESSION_SECRETtampoco esta en.env.localni en.env.example.- Aplicar el seed P5.2 del catalogo influyente en produccion.
- Backfill de
artists.user_id: 6 de 7 artistas siguen sin dueno y no reciben el aviso de aprobacion. - Rate limit distribuido (Upstash/KV): hoy es un
Mapen memoria, evitable en serverless. - Refactor de
/dashboarda Server Components si se quiere atacar el TBT de ~1.1 s. - Export de datos personales / RGPD. La recuperacion de contrasena sigue fuera de alcance porque si depende de correo.
Sigue siendo prerelease: el proyecto no esta terminado.
P5/P6: UI, roles suscriptor a artista, seguridad y a11y
Cierra la tanda P5/P6 reportada con capturas: 13 problemas de UX/navegación/roles más 8 hallazgos de seguridad y 7 de accesibilidad.
UI y navegación
- Animación real en la expansión de shows, tarjetas más ricas, grid sin anidar, filtros con label asociado y select legible en modo oscuro
- Link Artistas visible para usuarios autenticados (antes un artista no podía ver otros artistas)
- Menú del avatar cierra con Escape y clic fuera, sin botón X
- Carrusel Embla solo en el catálogo del dashboard, navegable con click y teclado
- Disclaimer de pagos replicado en las tarjetas públicas
Descargas
- Exportador unificado server-side: dossier, rider, catálogo JSON y catálogo imprimible
- Se elimina la sección duplicada y se corrige el dato stale (ya no usa el estado viejo)
Roles y aprobaciones
- El registro público crea solo suscriptores; ya no se puede elegir artista
- La promoción a artista ocurre al aprobar el primer release o show, con invalidación de sesión
- submissions = portal del artista; approvals = consola admin con escritor único y motivo mínimo de 10 caracteres validado en backend
- El suscriptor puede crear contenido y siempre queda en pendiente
Seguridad
- El dashboard y artistas públicos ya no filtran borradores ni user_id
- Rate limit en las escrituras más abusables (shows, releases, subscriptions, likes, upload)
- Corregido un 500 en GET /api/releases?user_id
- Upload validado por extensión, magic bytes y MIME real
- Las escrituras de admin ahora notifican y hacen fan-out
- El token de sesión siempre expira (antes con "recordarme" no expiraba nunca)
Visualizador y a11y
- FFT 1024 con bandas logarítmicas entre 30 Hz y 18 kHz, ganancia, curva, decay y peak-hold; respeta prefers-reduced-motion
- LoginModal y modales de aprobación con roles ARIA, Escape, foco atrapado y errores anunciados
- Modo claro corregido en el reproductor de video, alt descriptivo y fallback en imágenes
Verificación
- 225/225 unit, tsc 0, build OK
- E2E en producción: approvals 4/4, subscriber 2/2, shows-transitions 2/2
- Matriz de producción 50/50 y funcional de exportación 9/9 con los cuatro archivos reales
- Auditoría de seguridad sin riesgos altos ni críticos de código
- Datos limpios: 13 usuarios de prueba de fases anteriores eliminados
Pendiente de decisión (no bloquea)
- FROM_EMAIL sin configurar y pressplay.eu.org pendiente de aprobación: no sale correo a terceros
- El seed de catálogo influyente (P5.2) está implementado pero no se aplicó en producción
- 6 de 7 artistas siguen sin user_id, por lo que no reciben notificación de aprobación
Sigue siendo prerelease: el proyecto todavía no está terminado.