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/scripts/db/sembrar-versos.mjs

148 lines
4.9 KiB

/**
* 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.');

Powered by TurnKey Linux.