You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/docs/modelo-de-datos.md

35 KiB

Modelo de datos

Diseño y estado de la migración del contenido a PostgreSQL. El modelo base y el dominio musical están implementados en src/lib/server/db/schema y en las migraciones 0000–0014.

Versión presentable, con diagramas entidad-relación: https://claude.ai/code/artifact/d87ceb4d-6038-402f-852e-13ff5d86f02f

Este archivo conserva el porqué; la verdad ejecutable es src/lib/server/db/schema.

Por qué

Lo que hay hoy en src/content/** son registros disfrazados de documento: una canción tiene veintiún campos estructurados en YAML —registro, duracion, interpretes, versiones, videos, etiquetas…— y el archivo entero existe para sostenerlos.

Corrección. Una versión anterior de este documento decía que la letra también iba en el frontmatter y que el cuerpo del Markdown eran cuatro líneas de comentario. Es al revés: el cuerpo del Markdown es la letra, y el campo letra: —que aparece en un solo archivo— son los créditos de quién la firma. Se vio al escribir el volcado. No cambia la conclusión, pero sí el reparto: la letra es prosa legítima y se queda como texto; lo que no debía estar en YAML son los otros veintiún campos.

Dos hallazgos concretos, no cuestión de gusto:

1. Alguien puede perder una canción que pagó. Cinco tablas apuntan a una canción por una cadena de texto —carrito_item, pedido_item, compra, favorito, valoracion— y ninguna tiene clave foránea, porque el destino no está en la base sino en un archivo. Renombrar un slug deja huérfana una compra sin que salte ningún error en ningún sitio.

2. estilo hace dos trabajos distintos. En el catálogo son las secciones del artista (baladas románticas, clásica, rock, pop melódico); en el taller, una «ficha de estilo» es un género hispanoamericano (bolero, tango, cumbia). Son dos entidades que comparten palabra y hoy comparten nombre en el código. La clasificación por país es del género, no del estilo, y mientras sean lo mismo no se puede ni escribir.

A eso se suma que nada se valida —estilo: bolreo no falla, la canción desaparece de su sección—, nada se consulta sin cargar el catálogo entero en memoria, y cambiar una coma exige reconstruir y desplegar.

Alcance: 60 tablas (13 ya existen), 48 archivos que migrar, 22 rutas que hoy leen $lib/content.

Decisiones transversales

Decisión Elegido Motivo
Identificadores text (nanoid 21), como las tablas actuales El slug es texto que se lee y algún día se corrige; una clave primaria no debería cambiar nunca
Slugs únicos + tabla redireccion Renombrar deja de romper enlaces publicados; la dirección vieja responde 301
Prosa columna text con Markdown, compuesto en el servidor Comprobado: ningún archivo de contenido usa sintaxis de Svelte, así que no se pierde nada al dejar de compilarlo con mdsvex
Listas tabla si se consulta, text[] si solo se enseña Las etiquetas cruzan el catálogo; los temas y rasgos de un género solo se pintan
Publicación estado (borrador · publicado · retirado) Escribir una ficha a medias no puede significar publicarla
Fechas timestamptz siempre; creado_en y actualizado_en Y en los legales, además, historial: hay que poder demostrar qué decía el día de una compra
Enumerados texto con CHECK, no ENUM de Postgres Es lo que genera Drizzle y permite añadir un valor sin una migración que bloquee la tabla
Borrados reales en contenido; retirado en lo vendido Una canción comprada no se borra nunca: la compra tiene que seguir apuntando a algo

Las entidades

  • estilo (cambia) — id, slug, nombre, resumen, orden, principal con índice parcial único (hoy es un acuerdo de palabra), imagen_id, estado.

  • album (cambia) — + numero_catalogo, formatos text[], pais_edicion_id. Los campos de edicion dejan de estar anidados.

  • cancion (cambia) — album_id nulo en los singles; el álbum «Singles» deja de existir como registro, era un invento para pintar y se resuelve al consultar. letra, nota, inspiracion y traducción salen del YAML.

  • cancion.version_de_id (decidido) — una versión es una fila de cancion, no una tabla aparte. Nulo en las principales. Se llegó aquí descartando partir en obra y grabacion: en ese reparto el estilo y las etiquetas colgarían de la obra, y una versión no podría tener los suyos —una balada con arreglo de bolero no cabría—. Con una fila por versión, cualquier campo puede cambiar.

    De regalo se arregla algo que estaba mal: el disco pasa a ser de la fila. Hoy album_id y numero cuelgan del tema, así que la «Maqueta 2018» es el corte 1 del álbum igual que la versión de estudio, y es falso.

    Cuatro cabos que hay que atar, o se vuelve en contra:

    1. Vista cancion_principal. Cuatro funciones devuelven listas de canciones y siete rutas las pintan, más los recuentos por estilo y por etiqueta. Si a una se le olvida version_de_id IS NULL, el catálogo dice veinticinco temas donde hay dieciocho y nadie lo nota. Lo normal es consultar la vista; pedir versiones es lo que hay que escribir a propósito.
    2. Un solo nivel, comprobado en la base. Una autorreferencia admite ciclos —A apunta a B y B a A— y una consulta recursiva sobre eso se cuelga. Se ataja con una clave foránea compuesta contra una columna generada es_version, sin triggers.
    3. ala_venta solo en las principales, y comprar la principal entrega todas sus versiones. Es la regla que ya estaba escrita, ahora literal.
    4. Los campos se copian al crear, no se heredan al leer. Un nulo heredado nunca se sabe si es «no lo sé» o «lo mismo de arriba». Y aquí copiar es además lo correcto: la maqueta de 2018 no tiene la misma letra, le falta el puente.
  • persona — identidad física/legal para autoría, IPI y datos personales.

  • artista — identidad pública con código estable, slug, tipo (persona/grupo/proyecto), descripción, biografía, imagen, país y estado.

  • artista_persona — vínculo opcional entre una identidad pública y las personas físicas que la encarnan o integran.

  • artista_identificador — identificadores externos por sistema.

  • cancion_interprete — crédito de una grabación concreta a un artista, con rol, instrumento, nombre acreditado y orden.

  • cancion_persona (nueva) — rol (letra · musica · interprete · arreglos · produccion), orden.

  • version — desaparece como tabla: cada versión es una fila de cancion con version_de_id. El isrc va en esa fila, no en la principal: identifica una grabación, y cada arreglo es una grabación distinta. El iswc es de la composición y se repite en la familia.

  • registro_obra (cambia) — iswc, entidad, numero, editorial. 1:1 con la canción.

  • autoria (cambia) — porcentaje numeric(5,2), no entero: los repartos reales llevan decimales. Suma 100 comprobado en la base.

  • etiqueta, cancion_etiqueta (cambia) — hoy se deducen de texto libre, así que «desamor» y «Desamor» son dos etiquetas y nadie lo ve.

  • traduccion (nueva) — cancion_id + idioma. Hoy solo cabe una.

  • video, cancion_enlace, album_enlace, credito_album (cambia) — dos tablas de enlaces y no una polimórfica: una clave foránea de verdad vale más que ahorrarse una tabla.

2 · Taller, géneros y glosario

Decidido: el catálogo de géneros aspira a ser completo y se llena poco a poco. Eso cambia el peso de esta área: la ficha de género deja de ser una tarjeta y pasa a ser la pieza más rica del sitio, con su historia, sus instrumentos, sus letristas y sus obras de referencia.

  • pais (nueva) — código ISO 3166-1, nombre, gentilicio, region.

  • genero (nueva) — bolero, tango, cumbia. En columnas solo lo suyo: origen_lugar, origen_ano (ordenable, hoy no lo es), tempo, rima, y las notas en prosa metrica_nota y compas_nota para el matiz que no cabe en una tabla —«a menudo alternados» es lo que hace útil la ficha—.

    Lo comparable sale a tablas auxiliares. Con ocho fichas se lee; con cien, que es a donde va esto, hay que poder preguntar.

  • genero_origen (nueva) — genero_id, origen_id, tipo (deriva · fusiona · influye), principal, nota, orden.

    Sustituye a padre_id, que no aguantaba. Un género no deriva de uno: el bolero-son es bolero y son; la salsa viene del son, el mambo y la guaracha; la bachata, del bolero, el son y el merengue. Con un solo padre, la primera fusión obliga a elegir cuál de los dos mentir —y bolero y son-cubano ya existen como fichas separadas—.

    El tercer tipo, influye, es más débil que derivar y hoy ya está escrito, aunque en el campo equivocado. Esto es literal de son-cubano.md:

    metrica: 'Octosílabo, herencia de la décima y la copla españolas'

    Un vínculo entre géneros, metido dentro del campo de la métrica porque no había dónde ponerlo.

    El grafo guarda la verdad y la arista principal —única por género, con índice parcial— da el árbol de navegación. Los subgéneros son las filas que apuntan a uno con tipo deriva.

    Guardas: genero_id <> origen_id, y un disparador contra los ciclos. Un grafo admite que A derive de B y B de A, y una consulta recursiva sobre eso no termina. En una tabla de este tamaño, comprobarlo al insertar con un WITH RECURSIVE no cuesta nada.

  • metro + genero_metro (nuevas) — codigo, nombre, silabas, y termino_id contra el glosario, porque «octosílabo» es también una entrada suya. La tabla puente lleva predominante.

    Semilla: de pentasílabo a alejandrino, más una fila libre con silabas nulo, para que «géneros sin métrica fija» —la bachata: «Libre, cercana al habla»— también sea una consulta y no una ausencia.

    El motivo, con los datos delante: el octosílabo aparece en el bolero, el son y el tango; el endecasílabo en el bolero y el tango; el alejandrino en el tango. Los tres escritos en prosa. «Enséñame los géneros en octosílabo» es justo lo que un taller de métrica necesita poder preguntar, y hoy no se puede.

  • compas + genero_compas (nuevas) — codigo ('2/4', '4/4', '3/4', '6/8', '2/2'), nombre, numerador y denominador —guardados aparte para poder pedir los binarios o los ternarios—, y principal en la puente. El bolero lleva dos filas: 2/4 en el clásico, 4/4 en el moderno.

  • genero_tema (nueva) — genero_id, etiqueta_id, orden.

    Los temas salen del array y comparten vocabulario con las etiquetas de las canciones, en vez de tener el suyo. Son etiquetas disfrazadas de frase —«El amor no correspondido», «La despedida»— y con cien géneros «qué géneros hablan del desarraigo» es la misma pregunta que no se podía hacer con la métrica.

    Compartir vocabulario no es ahorro, es lo que hace posible cruzar: «el desamor: nueve temas míos y cuatro géneros que viven de él». Con dos listas separadas, «desamor» y «desamor» serían dos cosas distintas y nadie lo vería.

    Los rasgos se quedan como array: aquellos sí son prosa de verdad —«Tratamiento de usted o de tú, nunca coloquial»— y meterlos en un vocabulario sería inventar etiquetas que nadie va a reutilizar.

  • genero_alias (nueva) — genero_id, nombre, pais_codigo, nota.

    Son cubano, son jarocho, son huasteco, son montuno. La cumbia colombiana y la argentina. Con cien fichas, buscar «son» devuelve cuatro cosas que no se parecen; cada una tiene que encontrarse por como la llaman donde se toca.

  • genero_seccion (nueva) — clave, titulo, cuerpo, orden. Toda la prosa de la ficha: introducción, historia, estructura de las canciones, motivación y temática. Y lo que surja mañana. Con una columna por bloque, en un mes hay quince columnas y la mitad vacías; siendo secciones ordenadas, un apartado nuevo no cuesta una migración. Es la respuesta a «y otros campos que puedan surgir».

  • genero_pais (nueva) — relacion: cuna · adoptado. La cumbia nace colombiana y se hace mexicana, peruana y argentina; el tango es de las dos orillas. Un solo país sería mentira.

  • instrumento (nueva) — slug, nombre, familia (cuerda · viento · percusión · teclado · electrófono · voz) y subfamilia libre, pais_codigo, descripcion, afinacion, imagen_id y muestra_id contra medio —el sonido de muestra es un archivo como cualquier otro—, estado.

  • genero_instrumento (nueva) — con importancia (esencial · habitual · ocasional) y papel («marca la clave», «lleva el bajo»). Sin eso, el tres cubano y una trompeta ocasional pesarían igual en la ficha, y no es verdad.

  • genero_persona (nueva) — papel: letrista · compositor · interprete · fundador. No hay tabla letrista: un letrista es una persona, y con dos tablas la misma acabaría existiendo dos veces con distinta grafía el día que colabore con alguien que además es figura de un género. persona gana los campos biográficos: nacimiento, defuncion, pais_codigo, retrato_id, semblanza.

  • obra_referencia + obra_referencia_persona (nueva) — titulo, ano, genero_id, nota, cita. «Letras de referencia de un letrista» y «canciones del género» son la misma entidad, mirada desde un lado o desde el otro.

    ⚠️ No se alojan letras completas de terceros. «Bésame mucho» tiene dueño y lo tendrá durante décadas. Se cita título, autor, año y un fragmento breve al amparo del derecho de cita —el mismo que invoca nuestro propio aviso legal— y se enlaza a dónde escucharla. Por eso el campo se llama cita y es corto, no letra.

  • obra_referencia_enlace (nueva) — obra_id, plataforma, url. Sustituye a la columna enlace, que era texto suelto y solo daba sitio para uno: una obra está en Spotify y en YouTube, y quien lee usa la que usa. Comparte el vocabulario PLATAFORMAS con cancion_enlace y album_enlace: son la misma clase de dato, y dos formas de escribirlo son dos formas de equivocarse.

Ejemplos de versos

  • verso + verso_linea (nuevas) — la ficha podía decir 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 —género, título del ejemplo, qué ilustra (metrica · rima · acento · imagen · registro · estructura), analisis, medida y rima del conjunto— y no el verso suelto, porque la rima y la alternancia de medidas solo se ven en dos o cuatro versos seguidos.

    verso_linea lleva cada verso con su escansión: silabas es un array de sílabas métricas, donde la sinalefa mete dos palabras en una casilla —«za⁀a»— porque al cantarlas se dice una; tonicas son las posiciones acentuadas, y medida va aparte y no se calcula del array porque un verso agudo suma una sílaba y uno esdrújulo resta una.

    fuente distingue ejemplo (escrito para el taller, sin dueño de fuera), propio (de una canción del catálogo) y cita (fragmento breve de obra ajena). No es cosmético: decide qué se puede publicar entero, y dos CHECK lo sostienen —un verso cita sin obra_id o uno propio sin cancion_id no entran—.

    La escansión se comprueba antes de cargarla, en scripts/db/escansion.mjs, contra la ley del acento final. Un ejemplo mal contado enseña a contar mal, y eso no lo detecta ningún tipo ni ningún lint.

  • cancion_genero (nueva) — un tema propio escrito en ese género, aunque en el catálogo viva bajo «baladas románticas». Es lo que permite que una ficha enseñe obra propia además de la ajena.

  • capitulo (cambia) — tipo (principio · ficha), genero_id solo en las fichas, cuerpo, estado.

Prompts para IA generativa

Biblioteca de prompts por género para Suno y equivalentes.

  • modelo_ia (nueva) — codigo, familia (suno · udio · riffusion · stable-audio · mureka), version, nombre, vigente, nota_sintaxis y esquema jsonb: un JSON Schema que describe la forma que toman los prompts de ese modelo. Con él se validan al guardar y el panel pinta el formulario solo, sin una pantalla por cada IA.

    El prompt depende del modelo, no de la IA. Un prompt de Suno v3 no es uno de v4: cambian la sintaxis, las etiquetas y lo que el modelo ignora. Si la clave foránea apunta a «Suno» a secas, el día que salga la versión siguiente queda una biblioteca de prompts sin saber para qué servían.

  • animo (nueva) — codigo, nombre, familia GEMS, valencia, energia, descripcion.

    La codificación es la GEMS (Geneva Emotional Music Scale), hecha para emoción musical y no para emoción en general: asombro, trascendencia, ternura, nostalgia, serenidad, fuerza, alegría vital, tensión y tristeza. Más los dos ejes del modelo circumplejo de Russell —valencia y energía—, que sirven para ordenar e interpolar y que además son redactables: «alta energía, valencia baja» es una instrucción; «tensión» a secas, no.

    Se llama animo y no version a propósito: version ya significa otra cosa en este modelo —cancion.version_de_id— y con las dos compartiendo nombre, en un mes ninguna consulta se lee sola.

  • prompt (nueva) — modelo_ia_id, genero_id, animo_id, idioma, notas, valoracion 1–5, ejemplo_id contra medio, preferido, visibilidad (privado · taller · publico), estado, y contenido jsonb con el prompt propiamente dicho.

    El contenido va abierto, y aquí sí toca. La forma del prompt la define un tercero y cambia sin avisarnos: Suno separa estilo de letra y admite exclusiones, otro modelo pedirá weirdness o style_influence. Modelar eso en columnas es perseguir el esquema de otro. Es el caso contrario al del contenido del sitio, donde el esquema es nuestro y por eso ahí sí se modela.

    Dos condiciones para que no se pudra, que es lo que le pasa siempre a un campo abierto:

    1. Se valida contra el esquema de su modelo. Sin eso, en seis meses hay prompts con exclude, excludes y excluded y ninguna forma de saber cuál era el bueno. Abierto no es lo mismo que sin validar.
    2. Lo que se consulta se queda fuera del JSON. Género, ánimo, modelo, valoración e idioma son columnas. Postgres indexa jsonb con GIN, así que no sería fatal, pero «los prompts que funcionaron para el bolero» tiene que seguir siendo una consulta trivial.

    jsonb y no json: el binario se indexa y normaliza las claves; el otro guarda el texto tal cual y no sirve para buscar.

    La versión del modelo no se repite dentro. Vive en modelo_ia.version; si además se escribe en el JSON, tarde o temprano dirán cosas distintas. Si una API la exige en el payload, se compone al enviar.

    Se guarda si funcionó —valoración y archivo con el resultado—, porque sin eso se acumulan doscientos prompts sin saber cuáles servían. Varios por combinación, con uno preferido: esto se itera, no se acierta a la primera. Y el idioma importa: en inglés suelen funcionar mejor, incluso para música en español.

  • termino (nueva) — la palabra: slug, termino único.

  • acepcion (nueva) — termino_id, numero, categoria, definicion de una o dos frases (lo que cabe en un globo sin taparle el texto a nadie), ampliacion, ejemplo.

    Un término tiene varias acepciones numeradas, y la categoría baja aquí: «clave» es instrumento de percusión en la primera, patrón rítmico en la segunda y armadura de tonalidad en la tercera. Con una sola definición por palabra, dos de las tres serían falsas.

  • termino_variante (nueva) — «sinalefas», «heptasílabos». Al nivel del término, no de la acepción: el plural no cambia de sentido. Sin esto el marcado solo acierta en singular, que es casi nunca.

  • capitulo_acepcion (nueva) — se calcula al guardar el capítulo, no al pintarlo, y apunta a una acepción, no a un término.

    Eso resuelve la duda que quedaba sobre el marcado automático: en un texto sobre el bolero, «clave» es el patrón rítmico y no la armadura. Lo automático sabe encontrar la palabra pero no elegir el sentido, así que propone y una persona confirma cuál. Es lo único que no se equivoca, y siendo tabla se puede además desmarcar una aparición suelta.

Regla editorial: solo la primera aparición de cada término por página. Marcar las veintidós veces que sale «estribillo» convierte el texto en un campo de minas subrayado.

3 · Editorial

  • entrada, entrada_cancion, entrada_etiqueta (cambia) — hoy la lista de canciones son slugs sin comprobar: si el tema no existe, la entrada enlaza a un 404 y nadie se entera.
  • documento_legal + version_legal (nueva) — historial con vigente_desde y resumen_cambios. No es burocracia: es prueba.
  • pagina, redireccion (cambia / nueva).

4 · Cuentas y comercio

Hecho en la fase 3. Lo que sigue describía el problema; la solución está aplicada: cancion_slug ya no existe en ninguna de estas tablas.

  • usuario, identidad, sesion, codigo_acceso, limite (sin cambios).

  • carrito_item, pedido_item, compra (cambia) — cancion_slug text pasa a cancion_id fk. En pedido_item se congelan título y precio: una factura tiene que seguir diciendo lo que decía.

  • datos_facturacion (nueva) — tipo, nombre, nif, dirección, pais_codigo, predeterminada.

    El país decide el IVA, y no es el nuestro. Una venta digital a un consumidor de otro país de la UE lleva el IVA de su país; fuera de la UE, normalmente ninguno. Por eso el tipo se guarda por línea de factura y no como una constante. Detrás de eso hay una obligación que no es una tabla sino un trámite —el régimen de ventanilla única— y Stripe Tax puede calcularlo si se activa.

  • factura (nueva) — serie y numero correlativos y sin huecos, base, iva_tipo, iva_cuota, total, y datos_congelados jsonb: si el cliente cambia su dirección, la factura vieja no puede cambiar con él.

  • descarga (nueva) — para poder demostrar la entrega en una reclamación.

5 · Interacción

  • favorito, valoracion (cambia) — clave foránea, y CHECK de 1 a 5 en la base y no solo al validar.

  • reproduccion (nueva) — decidido: se conservan por persona. Con segundos, porque una reproducción de tres segundos no es una escucha y contarla igual falsea la cifra. Agregación por día y borrado del detalle a los noventa días son parte del diseño, no un apaño para después.

    ⚠️ Obliga a cambiar la política de privacidad antes de publicar esto. Hoy dice que no hay seguimiento, y con esta decisión deja de ser cierto. Hace falta: la fila en la tabla de datos —qué se guarda, para qué, base legal y plazo—, y que la persona pueda ver y descargar su historial desde su cuenta. La base razonable es ejecución del contrato mientras alimente «lo tuyo»; si además se usa para recomendar, ya es otra cosa y hay que decirlo.

6 · Medios

  • medio — clase, clave, publico (la copia completa de escucha sí; másteres y descargas no), mime, bytes, duracion_s, ancho, alto, sha256, alt. Para que la administración pueda subir audio, un archivo tiene que ser una fila.

Decidido: los archivos viven en el servidor pero bajo dominio propio, m.senzapaura.es/archives. Gana más de lo que parece: un dominio sin cookies no manda la de sesión en cada imagen y cada audio, y el día que haya un CDN delante no hay que tocar ninguna URL —la base del dominio es configuración, y medio.clave sigue siendo la ruta—.

⚠️ Esto cambia una decisión anterior. En el proyecto está escrito que las descargas no usan URLs firmadas «porque el servidor ya tiene la sesión delante». En otro dominio la sesión ya no va delante: esa es justo la gracia. Así que:

  • Lo público —portadas y copias completas de escucha— se sirve directo y cacheado.
  • El audio completo pasa a necesitar URL firmada con caducidad corta: la firma la pone la aplicación después de comprobar la compra, y Nginx la valida con secure_link. Sin eso, el dominio de medios sería una puerta abierta al catálogo entero.

Y hace falta su propio registro A, su Proxy Host y su certificado.

7 · Importación de fichas de género

Decidido: las fichas se generan fuera, con un modelo más potente, y entran en JSON. Eso convierte el contrato de importación en parte del modelo.

  • Un JSON Schema de ficha de género, que se le da a esa IA como destino y que valida el importador. Sin él, cada tanda llega con las claves un poco distintas y la importación se vuelve artesanía.
  • Procedencia, en genero y en las auxiliares: origen (manual · importado), modelo_origen, importado_en, revisado_por, revisado_en. Es musicología publicada con tu nombre: la diferencia entre «importado» y «revisado» tiene que estar en la base, no en la memoria de nadie.
  • Idempotente, por slug: reimportar una ficha corregida actualiza, no duplica.
  • Las relaciones se resuelven por código —país, instrumento, metro, compás, persona— y lo que no exista se crea marcado como importado y sin revisar, en vez de fallar o de colarse en silencio. Así se ve qué se ha inventado el modelo.

8 · Bocetos, no se construyen ahora

lista + lista_cancion (la playlist que sigue sin definirse), encargo, inscripcion_taller. Se dejan sitio porque aparecer después obliga a migrar lo ya cargado.

Qué cambia en la web

  1. Sveltia CMS deja de servir. Es un editor sobre git: escribía los Markdown de src/content/, que ya no lee nadie. Retirado al empezar la fase 6, y con él static/admin/; un panel que guarda cambios que no salen en pantalla es peor que no tener panel. Mientras no estén las pantallas de editar, el contenido se carga por script y por SQL.
  2. El Markdown se compone en cada petición, no al construir. Se cachea.
  3. Cada página consulta la base. A cambio, cambiar una letra deja de exigir un despliegue.

Los tres ya son ciertos. Los .md de src/content siguen en el repo porque son de donde salió el volcado y con lo que se reconstruye la base, pero editarlos no cambia nada en pantalla. $lib/content solo lo usan los scripts.

Fases

  1. Este documento. Sin código. Hecha.

  2. Esquema y migración, con el sitio leyendo todavía de los archivos. Hecha. El volcado comparó las letras carácter a carácter antes de confirmar. Por el camino se descubrió que drizzle-kit migrate deja de aplicar en silencio cuando una migración lleva $$, y se escribió scripts/db/migrar.mjs en su lugar.

  3. Las claves foráneas de las compras. Hecha (migraciones 0007 y 0008). Siete tablas —carrito_item, pedido_item, compra, favorito, valoracion, reproduccion, descarga— apuntan ahora a cancion.id. En dos pasos, para no tener que parar el sitio: primero convive la columna vieja, después se borra. Las de dinero —compra, pedido_item, descarga— restringen el borrado; las demás caen con la canción. Las guardas nuevas se comprueban en npm run db:guardas.

  4. Las 22 rutas, de archivos a consultas. Hecha. Ningún archivo de src/routes importa ya de $lib/content.

    Lo que la bloqueaba no eran las rutas sino el cliente: el catálogo viajaba en el paquete de JavaScript y las tarjetas, el carrito, la cuenta y el reproductor lo consultaban por su cuenta. Baja por page.data desde el layout —veintiún kilobytes, medidos— y $lib/catalogo/cliente.svelte lo ata a las mismas consultas que usa el servidor. Ya no viaja en el paquete.

    La red fue npm run db:comparar, que pone el catálogo de la base y el de los archivos lado a lado campo a campo. En su primera pasada encontró que ningún medio estaba enlazado —las canciones tenían audio_id y portada_id a nulo, así que la web habría dejado de reproducir—, que las etiquetas volvían alfabéticas en vez de en su orden, y que faltaban columnas para la nota de cada versión, el país de edición y el texto de estilos y discos. npm run db:completar ata esos cabos y es idempotente.

  5. Lo nuevo: géneros por país, glosario con globos, introducción larga del taller. Hecha. Países con su relación, instrumentación con el peso de cada instrumento, letristas, obras de referencia, linaje, ejemplos de versos con su escansión y el glosario.

    El glosario marca los términos sobre el HTML ya compuesto —no sobre el Markdown, donde habría que esquivar la sintaxis—, una vez por término y por apartado, y nunca dentro de un enlace, un encabezado o un bloque de código. capitulo_acepcion sigue vacía a propósito: el marcado propone y una persona confirma, y hasta que exista la administración manda la primera acepción —que es la razón de que estén numeradas por orden de importancia—.

  6. La administración. En curso. Está la puerta y está la portada.

    Decidido: el panel es otra aplicación, en admin.senzapaura.es. Mismo repositorio y misma base, otro proceso y otro dominio; el servidor público se compila sin sus rutas, así que en senzapaura.es no es que /admin esté protegido, es que no existe. Se hizo primero como /admin dentro de la web y se movió: el síntoma de que estaba mal fue que el layout de la web pública acabó con un if sobre /admin para quitarse la cabecera.

    Comparten src/lib —esquema, consultas y acceso—, que es lo que evita mantener las migraciones por duplicado. Lo único que las separa son tres líneas de vite.config.ts.

    El precio de tener otro dominio es que el panel necesita su propia página de entrar: la cookie se emite para el host y no para el dominio padre, y eso es deliberado —así la sesión del panel no viaja en ninguna petición de la web—. Usa el mismo código de $lib/server/auth, y solo manda código a quien ya administra, contestando lo mismo exista o no la cuenta.

    El rol vive en usuario.rol, con un CHECK que impide inventarse uno, y se da con npm run db:admin. En una variable de entorno con correos habría sido más rápido, pero entonces revocar a alguien pide un despliegue y a «quién administra esto» solo sabe contestar quien lea la configuración del servidor.

    La guarda está en el +layout.server.ts del grupo que envuelve las pantallas y no en cada ruta: así una pantalla nueva queda protegida por estar dentro. Sin sesión manda a entrar con la vuelta puesta; con sesión y sin el rol, 403 y con el motivo, que ahí solo llega quien administraba y ha dejado de administrar.

    La portada contesta «qué falta» y no «cuánto hay»: cada sección trae lo que tiene, lo que está a medias y los nombres de lo que está a medias. Decir «1 género sin ficha» obliga a ir a buscar cuál, y esa búsqueda ya la ha hecho la consulta.

    Se editan los capítulos —los principios y las fichas, que son la misma tabla— y las fichas de género enteras: sus datos, los países que las ponen en el mapa y la prosa por secciones. Y los prompts de IA, con sus modelos: una fila por versión, no por marca, y varios prompts por combinación con uno marcado como el que funcionó.

    Los nueve ánimos de la GEMS se siembran con npm run db:animos. Los modelos no: los nombres y las versiones los pone quien los usa, y una lista escrita en un script envejecería en un mes.

    Faltan: el glosario —confirmar las acepciones marcadas, que es lo que tiene capitulo_acepcion esperando—, las entradas del blog, el catálogo, y de la ficha de género los alias, la temática, la instrumentación, el linaje, las personas, las obras de referencia y los versos.

Decisiones tomadas por defecto

Las dos últimas se decidieron sin respuesta explícita, eligiendo en cada caso la opción reversible. Cambiarlas es añadir, no rehacer.

  • Valoración y favoritos van en la canción principal, no en cada versión. Si cada versión se valorase aparte, la media de la tarjeta dejaría de significar nada —¿la del máster, la de la maqueta, la mezcla?— y en favoritos saldrían dos entradas del mismo tema. La unidad con la que se interactúa es la canción.
  • Los prompts nacen privados (visibilidad: privado). No porque publicarlos esté mal, sino porque es una decisión de posicionamiento sin tomar y esta es la reversible: publicarlos es cambiar una columna; despublicarlos cuando ya están indexados, no.

Todo lo decidido

  • Versiones: una versión es una fila de cancion con version_de_id.
  • Catálogo de géneros completo, que se llena poco a poco.
  • Las fichas se generan fuera en JSON y se importan con esquema y procedencia.
  • Género: genero_origen en lugar de padre_id; metro, compás y temática como vocabularios; alias por país; completitud deducida, no marcada.
  • El glosario lleva acepciones numeradas, y el marcado apunta a una acepción: lo automático propone, una persona confirma el sentido.
  • Los prompts van en jsonb validado contra el esquema de su modelo.
  • Facturas de verdad, con correlativo por contador, rectificativas e IVA por país del comprador.
  • Se conservan las escuchas por persona.
  • Los archivos, en m.senzapaura.es/archives, con URLs firmadas para el audio completo.

Lo que no es una tabla y hay que hacer igual

  1. Reescribir la política de privacidad antes de que las escuchas se publiquen: hoy afirma que no hay seguimiento y dejaría de ser cierto. Es un documento legal, no una nota.
  2. El régimen de ventanilla única del IVA para ventas digitales a otros países de la UE. Trámite, no código. Stripe Tax calcula los tipos si se activa.
  3. DNS, proxy y certificado para m.senzapaura.es y para admin.senzapaura.es, más el servicio del panel en el puerto 3001. Está escrito en despliegue/con-proxy-manager.md.

Powered by TurnKey Linux.