/** * Carga los ejemplos de versos y los enlaces de las obras de referencia. * * npm run db:versos * * Los versos son originales, escritos para enseñar, y entran marcados como tal * —`fuente = 'ejemplo'`, `origen = 'importado'`, sin revisar—. Ninguno es cita * de obra ajena: para eso está `obra_referencia.cita`, y la tabla lo obliga * (un verso con `fuente = 'cita'` sin `obra_id` no entra). * * ## La comprobación de la escansión * * Antes de escribir nada se comprueba que cada verso cuadre con la ley del * acento final. La comprobación vive en `escansion.mjs`, para poder probarla * sin una base delante; si algo no cuadra, esto aborta en vez de avisar: un * ejemplo mal escandido enseña a contar mal. * * ## Los enlaces * * Son búsquedas en Spotify y en YouTube, no enlaces a una grabación concreta. * Es deliberado: una URL de canción hay que verificarla una a una, y una * inventada manda a un 404 con pinta de dato bueno. La búsqueda lleva a la obra * siempre, y cuando lleguen los enlaces exactos —en el JSON de la ficha, como * se decidió— sustituyen a estos con un `ON CONFLICT DO UPDATE`. */ import fs from 'node:fs'; import pg from 'pg'; import { revisarSemilla } from './escansion.mjs'; const SEMILLA = JSON.parse(fs.readFileSync('scripts/db/semilla-versos.json', 'utf8')); const MODELO = 'claude-opus-5'; /* --- La escansión, antes de tocar la base -------------------------------- */ const problemas = revisarSemilla(SEMILLA); if (problemas.length > 0) { console.error('La escansión no cuadra. No se ha tocado la base:'); for (const p of problemas) console.error(` ${p}`); process.exit(1); } /* --- A la base ------------------------------------------------------------ */ const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 60_000 }); await c.connect(); await c.query('BEGIN'); const contado = { verso: 0, linea: 0, enlace: 0 }; const avisos = []; try { const { rows: generos } = await c.query('SELECT id, slug FROM genero'); const idGenero = new Map(generos.map((g) => [g.slug, g.id])); for (const [slug, ejemplos] of Object.entries(SEMILLA.generos)) { const generoId = idGenero.get(slug); if (!generoId) { avisos.push(`no conozco el género «${slug}»`); continue; } // Se rehacen enteros: un ejemplo es su texto y su escansión juntos, y // actualizar por partes dejaría versos de una versión con la cuenta de // otra. Las líneas caen solas, por la clave foránea en cascada. await c.query('DELETE FROM verso WHERE genero_id = $1 AND fuente = $2', [generoId, 'ejemplo']); for (const [i, ejemplo] of ejemplos.entries()) { const { rows } = await c.query( `INSERT INTO verso (id, genero_id, titulo, ilustra, fuente, analisis, medida, rima, orden, estado, origen, modelo_origen, importado_en) VALUES ($1,$2,$3,$4,'ejemplo',$5,$6,$7,$8,'publicado','importado',$9,now()) RETURNING id`, [ crypto.randomUUID(), generoId, ejemplo.titulo, ejemplo.ilustra, ejemplo.analisis ?? null, ejemplo.medida ?? null, ejemplo.rima ?? null, i, MODELO ] ); contado.verso++; for (const [j, linea] of ejemplo.lineas.entries()) { await c.query( `INSERT INTO verso_linea (verso_id, numero, texto, silabas, tonicas, medida, rima) VALUES ($1,$2,$3,$4,$5,$6,$7)`, [ rows[0].id, j + 1, linea.texto, linea.silabas, linea.tonicas, linea.medida ?? null, linea.rima ?? null ] ); contado.linea++; } } } /* --- Dónde escucharlas ------------------------------------------------- */ const { rows: obras } = await c.query( `SELECT o.id, o.titulo, o.ano, (SELECT p.nombre FROM obra_referencia_persona op JOIN persona p ON p.id = op.persona_id WHERE op.obra_id = o.id ORDER BY op.orden LIMIT 1) AS autor FROM obra_referencia o` ); for (const obra of obras) { const busqueda = [obra.titulo, obra.autor].filter(Boolean).join(' '); const enlaces = { spotify: `https://open.spotify.com/search/${encodeURIComponent(busqueda)}`, youtube: `https://www.youtube.com/results?search_query=${encodeURIComponent(busqueda)}` }; for (const [plataforma, url] of Object.entries(enlaces)) { await c.query( `INSERT INTO obra_referencia_enlace (obra_id, plataforma, url) VALUES ($1,$2,$3) ON CONFLICT (obra_id, plataforma) DO UPDATE SET url = EXCLUDED.url`, [obra.id, plataforma, url] ); contado.enlace++; } } await c.query('COMMIT'); } catch (fallo) { await c.query('ROLLBACK').catch(() => {}); console.error('\nFalló, no se ha escrito nada:\n ' + fallo.message); await c.end(); process.exit(1); } await c.end(); for (const [que, n] of Object.entries(contado)) console.log(` ${que.padEnd(10)} ${n}`); for (const aviso of avisos) console.log(` aviso: ${aviso}`); console.log('\nSembrado. Los versos son ejemplos originales, sin revisar.');