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.
286 lines
9.4 KiB
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.');
|