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-generos.mjs

286 lines
9.4 KiB

/**
* Carga la semilla de géneros: países, instrumentos, personas, obras de
* referencia y el linaje.
*
* npm run db:generos
*
* Es el primer importador, y sigue el contrato que se decidió para las fichas:
* entra desde un JSON, resuelve las relaciones por código, y **marca la
* procedencia**. Todo lo que carga queda con `origen = 'importado'` y sin
* revisar, porque lo escribió un modelo y no una persona: es musicología que se
* publica con el nombre del titular, y esa diferencia tiene que estar en la
* base y no en la memoria de nadie.
*
* Los géneros ya existen —los creó el volcado del catálogo a partir de las
* fichas del taller—, así que aquí se completan, no se crean. Lo que sí se crea
* es lo que no tenía de dónde salir.
*/
import fs from 'node:fs';
import pg from 'pg';
const SEMILLA = JSON.parse(fs.readFileSync('scripts/db/semilla-generos.json', 'utf8'));
const MODELO = 'claude-opus-5';
const slugificar = (t) =>
t
.toLowerCase()
.normalize('NFD')
.replace(/[̀-ͯ]/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 60_000 });
await c.connect();
await c.query('BEGIN');
const contado = {};
const avisos = [];
try {
/* --- Países ------------------------------------------------------------ */
for (const p of SEMILLA.paises) {
await c.query(
`INSERT INTO pais (codigo, nombre, gentilicio, region) VALUES ($1,$2,$3,$4)
ON CONFLICT (codigo) DO UPDATE SET nombre = EXCLUDED.nombre,
gentilicio = EXCLUDED.gentilicio, region = EXCLUDED.region`,
[p.codigo, p.nombre, p.gentilicio ?? null, p.region ?? null]
);
}
contado.pais = SEMILLA.paises.length;
/* --- Instrumentos ------------------------------------------------------ */
const idInstrumento = new Map();
for (const i of SEMILLA.instrumentos) {
const { rows } = await c.query(
`INSERT INTO instrumento (id, slug, nombre, familia, subfamilia, pais_codigo,
descripcion, afinacion, estado, origen, modelo_origen, importado_en)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,'publicado','importado',$9,now())
ON CONFLICT (slug) DO UPDATE SET nombre = EXCLUDED.nombre,
familia = EXCLUDED.familia, subfamilia = EXCLUDED.subfamilia,
pais_codigo = EXCLUDED.pais_codigo, descripcion = EXCLUDED.descripcion,
afinacion = EXCLUDED.afinacion
RETURNING id`,
[
crypto.randomUUID(),
i.slug,
i.nombre,
i.familia,
i.subfamilia ?? null,
i.paisCodigo ?? null,
i.descripcion ?? null,
i.afinacion ?? null,
MODELO
]
);
idInstrumento.set(i.slug, rows[0].id);
}
contado.instrumento = idInstrumento.size;
/* --- Personas ----------------------------------------------------------
*
* Se completan las que ya existan por nombre —el volcado creó las de los
* créditos— y se crean las que falten. Que sea la misma tabla es justo el
* motivo de no haber hecho una de «letristas» aparte.
*/
const idPersona = new Map();
for (const p of SEMILLA.personas) {
const { rows } = await c.query(
`INSERT INTO persona (id, slug, nombre, nacimiento, defuncion, pais_codigo,
semblanza, estado, origen, modelo_origen, importado_en)
VALUES ($1,$2,$3,$4,$5,$6,$7,'publicado','importado',$8,now())
ON CONFLICT (slug) DO UPDATE SET nacimiento = EXCLUDED.nacimiento,
defuncion = EXCLUDED.defuncion, pais_codigo = EXCLUDED.pais_codigo,
semblanza = EXCLUDED.semblanza
RETURNING id`,
[
crypto.randomUUID(),
p.slug,
p.nombre,
p.nacimiento ?? null,
p.defuncion ?? null,
p.paisCodigo ?? null,
p.semblanza ?? null,
MODELO
]
);
idPersona.set(p.slug, rows[0].id);
}
contado.persona = idPersona.size;
/* --- Los géneros ------------------------------------------------------- */
const idGenero = new Map();
const { rows: existentes } = await c.query('SELECT id, slug FROM genero');
for (const g of existentes) idGenero.set(g.slug, g.id);
contado.generoNuevo = 0;
for (const [slug, datos] of Object.entries(SEMILLA.generos)) {
if (!idGenero.has(slug)) {
if (!datos.nombre) {
avisos.push(`el género «${slug}» no existe y la semilla no le da nombre; lo salto`);
continue;
}
const { rows } = await c.query(
`INSERT INTO genero (id, slug, nombre, resumen, origen_ano, estado, origen,
modelo_origen, importado_en)
VALUES ($1,$2,$3,$4,$5,'publicado','importado',$6,now()) RETURNING id`,
[
crypto.randomUUID(),
slug,
datos.nombre,
datos.resumen ?? '',
datos.origenAno ?? null,
MODELO
]
);
idGenero.set(slug, rows[0].id);
contado.generoNuevo++;
} else if (datos.origenAno) {
await c.query('UPDATE genero SET origen_ano = $1 WHERE id = $2', [
datos.origenAno,
idGenero.get(slug)
]);
}
}
/* --- Y todo lo que cuelga de ellos -------------------------------------- */
const cuenta = (clave) => (contado[clave] = (contado[clave] ?? 0) + 1);
for (const [slug, datos] of Object.entries(SEMILLA.generos)) {
const generoId = idGenero.get(slug);
if (!generoId) continue;
for (const [pais, relacion] of datos.paises ?? []) {
await c.query(
`INSERT INTO genero_pais (genero_id, pais_codigo, relacion) VALUES ($1,$2,$3)
ON CONFLICT (genero_id, pais_codigo) DO UPDATE SET relacion = EXCLUDED.relacion`,
[generoId, pais, relacion]
);
cuenta('generoPais');
}
for (const [nombre, pais] of datos.alias ?? []) {
await c.query(
`INSERT INTO genero_alias (id, genero_id, nombre, pais_codigo) VALUES ($1,$2,$3,$4)`,
[crypto.randomUUID(), generoId, nombre, pais]
);
cuenta('alias');
}
for (const [i, [inst, importancia, papel]] of (datos.instrumentos ?? []).entries()) {
const instrumentoId = idInstrumento.get(inst);
if (!instrumentoId) {
avisos.push(`${slug}: no conozco el instrumento «${inst}»`);
continue;
}
await c.query(
`INSERT INTO genero_instrumento (genero_id, instrumento_id, importancia, papel, orden)
VALUES ($1,$2,$3,$4,$5)
ON CONFLICT (genero_id, instrumento_id) DO UPDATE SET
importancia = EXCLUDED.importancia, papel = EXCLUDED.papel, orden = EXCLUDED.orden`,
[generoId, instrumentoId, importancia, papel ?? null, i]
);
cuenta('generoInstrumento');
}
for (const [i, [persona, papel]] of (datos.personas ?? []).entries()) {
const personaId = idPersona.get(persona);
if (!personaId) {
avisos.push(`${slug}: no conozco a «${persona}»`);
continue;
}
await c.query(
`INSERT INTO genero_persona (genero_id, persona_id, papel, orden) VALUES ($1,$2,$3,$4)
ON CONFLICT (genero_id, persona_id, papel) DO UPDATE SET orden = EXCLUDED.orden`,
[generoId, personaId, papel, i]
);
cuenta('generoPersona');
}
for (const [titulo, ano, autores, nota, cita] of datos.obras ?? []) {
const obraSlug = slugificar(`${titulo}-${ano ?? ''}`);
const { rows } = await c.query(
`INSERT INTO obra_referencia (id, slug, titulo, ano, genero_id, nota, cita,
estado, origen, modelo_origen, importado_en)
VALUES ($1,$2,$3,$4,$5,$6,$7,'publicado','importado',$8,now())
ON CONFLICT (slug) DO UPDATE SET nota = EXCLUDED.nota, cita = EXCLUDED.cita
RETURNING id`,
[
crypto.randomUUID(),
obraSlug,
titulo,
ano ?? null,
generoId,
nota ?? null,
cita ?? null,
MODELO
]
);
cuenta('obra');
for (const [i, autor] of (autores ?? []).entries()) {
const personaId = idPersona.get(autor);
if (!personaId) {
avisos.push(`«${titulo}»: no conozco a «${autor}»`);
continue;
}
await c.query(
`INSERT INTO obra_referencia_persona (obra_id, persona_id, papel, orden)
VALUES ($1,$2,'letra',$3) ON CONFLICT DO NOTHING`,
[rows[0].id, personaId, i]
);
}
}
for (const [clave, titulo, cuerpo] of datos.secciones ?? []) {
await c.query(
`INSERT INTO genero_seccion (id, genero_id, clave, titulo, cuerpo, orden)
VALUES ($1,$2,$3,$4,$5,0)
ON CONFLICT (genero_id, clave) DO UPDATE SET titulo = EXCLUDED.titulo,
cuerpo = EXCLUDED.cuerpo`,
[crypto.randomUUID(), generoId, clave, titulo, cuerpo]
);
cuenta('seccion');
}
}
/*
* El linaje va al final, en una pasada aparte: una arista puede apuntar a un
* género que se crea más abajo en el JSON, y el disparador de ciclos
* necesita que existan los dos extremos.
*/
for (const [slug, datos] of Object.entries(SEMILLA.generos)) {
const generoId = idGenero.get(slug);
if (!generoId) continue;
for (const [i, [origen, tipo, principal, nota]] of (datos.origenes ?? []).entries()) {
const origenId = idGenero.get(origen);
if (!origenId) {
avisos.push(`${slug}: no conozco el género de origen «${origen}»`);
continue;
}
await c.query(
`INSERT INTO genero_origen (genero_id, origen_id, tipo, principal, nota, orden)
VALUES ($1,$2,$3,$4,$5,$6)
ON CONFLICT (genero_id, origen_id, tipo) DO UPDATE SET
principal = EXCLUDED.principal, nota = EXCLUDED.nota`,
[generoId, origenId, tipo, principal ?? false, nota ?? null, i]
);
cuenta('linaje');
}
}
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(20)} ${n}`);
for (const aviso of avisos) console.log(` aviso: ${aviso}`);
console.log('\nSembrado. Todo entra como «importado» y sin revisar.');

Powered by TurnKey Linux.