|
|
# Modelo de datos
|
|
|
|
|
|
Diseño de la migración del contenido a PostgreSQL. **Fase 1 cerrada**: el
|
|
|
modelo está completo y las decisiones tomadas. No hay código escrito todavía.
|
|
|
|
|
|
Versión presentable, con diagramas entidad-relación:
|
|
|
<https://claude.ai/code/artifact/d87ceb4d-6038-402f-852e-13ff5d86f02f>
|
|
|
|
|
|
Este archivo es la especificación; el artefacto es la misma información puesta
|
|
|
para leerse. Cuando se implemente, la verdad pasa a ser
|
|
|
`src/lib/server/db/schema.ts` y esto queda como el porqué.
|
|
|
|
|
|
## 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
|
|
|
|
|
|
### 1 · Catálogo
|
|
|
|
|
|
- **`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`** _(nueva)_ — nombre, nombre_legal, ipi. Hoy autores e intérpretes
|
|
|
son cadenas sueltas repetidas en cada archivo.
|
|
|
- **`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`** _(nueva)_ — clase, clave, **`publico`** (los fragmentos sí, el
|
|
|
audio completo nunca; hoy eso lo garantiza la carpeta), 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, muestras— 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`.
|