codex/estilos-web-publica
codex/redisenio-dominio-musical
master
${ noResults }
8 Commits (715b28c3c06e74a102cc4f5e39f33ef39d796b28)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
715b28c3c0 |
El catálogo de la base ya es idéntico al de los archivos
Antes de cambiar veinte rutas de sitio, poder demostrar que sale lo mismo. `npm run db:comparar` pone lado a lado las dieciocho canciones, los cuatro discos y los cuatro estilos, campo a campo, y dice dónde difieren con la ruta exacta —`dorina.registro.reparto[0].porcentaje`—. Confiar en que las pruebas de interfaz noten una diferencia de un año o de un porcentaje sería confiar de más: pintan lo que les den. La primera pasada encontró ciento y pico diferencias, y no eran del comparador: - **Ningún medio estaba enlazado.** El volcado del catálogo y el de archivos se hicieron por separado, así que las veintitrés canciones tenían `audio_id`, `muestra_id` y `portada_id` a nulo. La web habría dejado de reproducir en cuanto leyera de la base. No se vio al volcar porque aquello comprobaba las letras carácter a carácter —lo delicado— y no que las relaciones estuvieran puestas. - **Las etiquetas volvían en orden alfabético.** No es cosmético: la primera etiqueta de un tema dice de qué va y las de después matizan. Ahora `cancion_etiqueta` tiene `orden`. - **La nota de cada versión no tenía columna** donde caer. - El país de edición se guardaba en ninguna parte, y los cuatro discos se quedaron sin él. `scripts/db/completar-catalogo.mjs` ata esos cabos y es idempotente. Los alias de `medio` son de verdad y no la misma tabla dos veces: sin ellos Postgres devuelve la misma fila para el audio y para la portada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
b22d7fbff2 |
Las compras dejan de apuntar a un texto y apuntan a la canción
Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
6797c03809 |
Ejemplos de versos en la ficha, y dónde escuchar cada obra
La ficha decía que el bolero va en octosílabo y endecasílabo alternados sin enseñar nunca uno. Un taller que describe la métrica y no la muestra está pidiendo que se la imaginen. `verso` es la estrofa y no el verso suelto —la rima y la alternancia de medidas no se ven en menos de dos versos— y `verso_linea` lleva cada uno con su escansión: `silabas` es la métrica y no la del diccionario, así que la sinalefa mete dos palabras en una casilla —«za⁀a»—, que es justo lo que en un texto corrido no se ve. `medida` va aparte porque un verso agudo suma una sílaba y uno esdrújulo resta una: «yo respondo con el son» tiene siete casillas y es un octosílabo. Los diez ejemplos son originales, escritos para enseñar, y entran marcados con `fuente = 'ejemplo'`. Ninguno es cita: para eso está `obra_referencia.cita`, y dos CHECK lo sostienen —un verso «cita» sin obra, o uno «propio» sin canción, no entran—. La escansión se comprueba antes de cargarla, contra la ley del acento final, y el sembrador aborta si algo no cuadra. Un ejemplo mal contado enseña a contar mal y eso no lo detecta ningún tipo ni ningún lint: por eso la comprobación está en su propio módulo, con nueve pruebas. `obra_referencia_enlace` sustituye a la columna `enlace`, que era texto suelto y solo daba sitio para uno; una obra está en Spotify **y** en YouTube. Comparte el vocabulario `PLATAFORMAS` con los enlaces de las canciones. De momento son búsquedas y no grabaciones concretas: una URL inventada manda a un 404 con pinta de dato bueno, y los enlaces exactos llegarán en el JSON de la ficha. De paso, dos cosas que se vieron al montarlo: - El orden de la ficha cambia. Primero lo que se usa para escribir —ejemplos, rasgos, temática— y después lo que sitúa el género. Es un taller: quien abre la página viene a escribir una letra. - `repeat(auto-fit, minmax(24rem, 1fr))` desbordaba ochenta píxeles en un móvil de 320, porque la rejilla pone la columna aunque no quepa. Ahora es `min(24rem, 100%)`, en los tres sitios donde estaba. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
8272f3c195 |
Un migrador propio: drizzle-kit dejaba de aplicar sin decirlo
Su troceador de SQL no entiende los cuerpos `$$` de plpgsql, y este esquema tiene dos funciones —la que comprueba que el reparto de autoría suma cien y la que impide ciclos en el linaje de géneros—. A partir de esa migración deja de aplicar nada y termina con éxito. Se descubrió porque `cancion.nota` seguía existiendo dos migraciones después de borrarla. Es decir: durante un rato la base y el esquema dijeron cosas distintas y nada avisó. `scripts/db/migrar.mjs` trocea por el mismo separador que escribe Drizzle, aplica en orden, anota en su misma tabla y con su mismo hash, y cuando algo falla dice en qué sentencia y deshace. De paso: - 0004 rehace `cancion_principal` alrededor del `DROP COLUMN`. Un `CREATE VIEW ... SELECT *` expande las columnas al crearse, así que la vista impedía borrar la columna. - 0005 le da procedencia a `persona`. Las figuras de un género las escribe un modelo igual que las fichas, con fechas que hay que verificar; sin esto no se distingue a quien firma una canción del catálogo de una semblanza importada sin revisar. - `marked`, para componer en el servidor el Markdown que ahora vive en una columna. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
be28a29693 |
El capítulo del taller cubre el ancho, con su índice al lado
La queja era justa y va por la tercera vez, así que esta vez queda medida y con prueba: la franja de datos, el índice del capítulo y los bloques de abajo terminan los tres en el mismo borde. Lo que no se puede hacer es ensanchar el texto. Para llenar novecientos píxeles con prosa legible haría falta letra de veinticuatro puntos; a menos de eso salen ciento veinte caracteres por línea y el ojo pierde el renglón. Ya se midió al hacer las páginas legales: estirado daba 129. Así que lo que cubre ese ancho es lo que va al lado, y no es la lista de compás y tempo que se quitó —esa subió a la franja—: es el índice de apartados del propio capítulo, el mismo recurso que llevan los textos legales. Y las columnas miden lo suyo con el sobrante en el corredor, porque con anchos fijos quedaban ciento veinte píxeles muertos al final de la fila, que es exactamente el hueco que se ve como un fallo. Además, la semilla de géneros: países, veintiún instrumentos, veintiuna personas, obras de referencia y el linaje del bolero-son, que deriva de dos. Entra marcada como importada y sin revisar, porque la escribió un modelo y se publica con el nombre del titular. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
e0a979bf5d |
La ficha de género pierde la barra lateral y gana su franja de datos
Los datos del género —compás, metro, tempo, rima— pasan de una lista de definiciones apretada en una columna estrecha a la derecha a una franja a todo lo ancho encima del texto. El compás y la métrica de un género se leen juntos o no se leen, y en la lateral quedaban en letra pequeña con el texto escorado. La estructura, la temática y los rasgos bajan debajo de la prosa como bloques a lo ancho: la temática en rejilla —son etiquetas disfrazadas de frase— y los rasgos en lista, que son frases de verdad. Es el sitio donde entrarán los instrumentos, los letristas, las obras de referencia y el linaje cuando tengan datos. Y el volcado de medios: 55 archivos a la tabla `medio` con su huella, su peso y su duración MEDIDA, no escrita. De ahí salen dos cosas que no se veían: el mismo audio está copiado bajo doce nombres distintos, y las duraciones del catálogo dicen 252 segundos donde el archivo dura 6. La clave de un medio lleva su almacén delante, porque `singles/dorina.mp3` existe en audio y en muestras y sin eso serían la misma fila. Es además la ruta con la que se servirán desde m.senzapaura.es/archives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
54df820056 |
Vuelca el catálogo a PostgreSQL, y lo comprueba
Los 48 archivos entran en la base: 18 canciones con sus 5 versiones como filas hermanas, 4 discos, 4 estilos, 8 géneros, 14 capítulos, 9 personas, 53 etiquetas, 2 entradas, 5 legales y una página. La web sigue leyendo de los archivos; esto solo llena el sitio donde caben. Reutiliza los analizadores del sitio en vez de leer el frontmatter otra vez. Es la decisión que sostiene todo lo demás: si un valor por defecto o una normalización cambian, cambian para los dos. Un segundo analizador «parecido» es lo que garantiza que un día la base y la web cuenten cosas distintas sin que nadie lo note. Todo en una transacción, y al final comprueba lo escrito contra lo leído —la letra de cada tema carácter a carácter— porque «no dio error» no es lo mismo que «está bien». Si no cuadra, deshace y dice qué falló. Y es idempotente: se puede lanzar las veces que haga falta sin duplicar nada, sin tocar cuentas, pedidos ni compras. Para poder reutilizar ese código desde Node hizo falta un gancho de resolución —`$lib`, imports sin extensión y un sustituto de `$app/paths`— y quitar una propiedad de parámetro de `ErrorDeContenido`: es sintaxis exclusiva de TypeScript y ningún cargador que solo borre tipos puede con ella. Y una corrección: el cuerpo del Markdown de una canción NO era un comentario, es la letra. Lo dije al revés en el documento del modelo y lo repetí varias veces. El campo `letra:` del frontmatter —que aparece en un solo archivo— son los créditos de quién la firma. Corregido en el documento, en el esquema, y quitada la columna `nota` que sobraba. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |
|
|
1a8c904ae8 |
Fase 2: el esquema entero en PostgreSQL
52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
4 weeks ago |