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.
343 lines
10 KiB
343 lines
10 KiB
|
4 weeks ago
|
/** Crea el almacén editorial e importa una instantánea, sin modificar el catálogo público. */
|
||
|
|
import pg from 'pg';
|
||
|
|
import { migrarPersonas } from './personas.mjs';
|
||
|
|
import { migrarInspiracion } from './inspiracion.mjs';
|
||
|
|
import { TABLAS_FUENTE, huella } from './fuente.mjs';
|
||
|
|
import { readFileSync } from 'node:fs';
|
||
|
|
import { fileURLToPath } from 'node:url';
|
||
|
|
const c = new pg.Client({ connectionString: process.env.DATABASE_URL });
|
||
|
|
await c.connect();
|
||
|
|
const estructura = readFileSync(
|
||
|
|
fileURLToPath(new URL('./estructura.sql', import.meta.url)),
|
||
|
|
'utf8'
|
||
|
|
);
|
||
|
|
try {
|
||
|
|
await c.query('BEGIN');
|
||
|
|
await c.query(estructura);
|
||
|
|
const {
|
||
|
|
rows: [control]
|
||
|
|
} = await c.query('SELECT * FROM estudio_control WHERE id=1 FOR UPDATE');
|
||
|
|
if (control.importado_en) {
|
||
|
|
console.log('El almacén ya está importado. No se sobrescriben borradores.');
|
||
|
|
await migrarPersonas(c);
|
||
|
|
await migrarInspiracion(c);
|
||
|
|
await c.query('COMMIT');
|
||
|
|
} else {
|
||
|
|
const tablas = TABLAS_FUENTE;
|
||
|
|
/** @type {Record<string,any[]>} */
|
||
|
|
const datos = {};
|
||
|
|
for (const tabla of tablas) datos[tabla] = (await c.query(`SELECT * FROM ${tabla}`)).rows;
|
||
|
|
/** @type {{id:string,tipo:string,estado:string,borrador:any}[]} */ const docs = [];
|
||
|
|
const agregar = (tipo, f, campos = {}, vinculos = []) =>
|
||
|
|
docs.push({
|
||
|
|
id: f.id,
|
||
|
|
tipo,
|
||
|
|
estado: f.estado ?? 'publicado',
|
||
|
|
borrador: {
|
||
|
|
titulo: f.titulo ?? f.nombre ?? f.termino ?? f.codigo ?? f.slug,
|
||
|
|
slug: f.slug ?? f.codigo ?? f.id,
|
||
|
|
campos: { ...campos, _importado: true },
|
||
|
|
vinculos
|
||
|
|
}
|
||
|
|
});
|
||
|
|
const medios = new Map(datos.medio.map((m) => [m.id, m]));
|
||
|
|
for (const m of datos.medio.filter((m) => m.clase === 'imagen'))
|
||
|
|
await c.query('INSERT INTO estudio_archivo(id,clase,nombre,datos) VALUES($1,$2,$3,$4)', [
|
||
|
|
m.id,
|
||
|
|
'imagen',
|
||
|
|
m.clave,
|
||
|
|
{
|
||
|
|
legacyId: m.id,
|
||
|
|
clave: m.clave,
|
||
|
|
mime: m.mime,
|
||
|
|
bytes: m.bytes,
|
||
|
|
ancho: m.ancho,
|
||
|
|
alto: m.alto,
|
||
|
|
sha256: m.sha256,
|
||
|
|
alt: m.alt
|
||
|
|
}
|
||
|
|
]);
|
||
|
|
for (const s of datos.estilo)
|
||
|
|
agregar('estilos', s, {
|
||
|
|
resumen: s.resumen,
|
||
|
|
cuerpo: s.cuerpo,
|
||
|
|
orden: s.orden,
|
||
|
|
principal: s.principal,
|
||
|
|
imagenId: s.imagen_id ?? ''
|
||
|
|
});
|
||
|
|
for (const a of datos.artista)
|
||
|
|
agregar('artistas', a, {
|
||
|
|
tipo: a.tipo,
|
||
|
|
resumen: a.descripcion_corta,
|
||
|
|
biografia: a.biografia,
|
||
|
|
lugarOrigen: a.lugar_origen ?? '',
|
||
|
|
sitioWeb: a.sitio_web ?? '',
|
||
|
|
imagenId: a.imagen_id ?? ''
|
||
|
|
});
|
||
|
|
for (const p of datos.persona)
|
||
|
|
agregar('colaboradores', p, {
|
||
|
|
nombreLegal: p.nombre_legal ?? '',
|
||
|
|
ipi: p.ipi ?? '',
|
||
|
|
semblanza: p.semblanza ?? '',
|
||
|
|
imagenId: p.retrato_id ?? ''
|
||
|
|
});
|
||
|
|
for (const e of datos.etiqueta) agregar('etiquetas', e);
|
||
|
|
for (const f of datos.cancion) {
|
||
|
|
const audios = datos.cancion_audio.filter((a) => a.cancion_id === f.id);
|
||
|
|
const info = { legacyId: f.id, duracionS: f.duracion_s };
|
||
|
|
for (const a of audios) {
|
||
|
|
const m = medios.get(a.medio_id);
|
||
|
|
if (!m) throw Error('Audio con medio inexistente');
|
||
|
|
const nombre = a.uso === 'stream' ? 'escucha' : a.uso;
|
||
|
|
info[nombre] = {
|
||
|
|
clave: m.clave,
|
||
|
|
mime: m.mime,
|
||
|
|
bytes: m.bytes,
|
||
|
|
sha256: m.sha256,
|
||
|
|
formato: a.formato,
|
||
|
|
bitrateKbps: a.bitrate_kbps,
|
||
|
|
frecuenciaHz: a.frecuencia_hz,
|
||
|
|
bitsMuestra: a.bits_muestra,
|
||
|
|
canales: a.canales
|
||
|
|
};
|
||
|
|
}
|
||
|
|
if (audios.length)
|
||
|
|
await c.query('INSERT INTO estudio_archivo(id,clase,nombre,datos) VALUES($1,$2,$3,$4)', [
|
||
|
|
`a:${f.id}`,
|
||
|
|
'audio',
|
||
|
|
f.titulo,
|
||
|
|
info
|
||
|
|
]);
|
||
|
|
if (!f.version_de_id)
|
||
|
|
agregar('canciones', f, {
|
||
|
|
principalId: `g:${f.id}`,
|
||
|
|
alaVenta: f.ala_venta,
|
||
|
|
destacada: f.destacada,
|
||
|
|
resumen: '',
|
||
|
|
inspiracionSecciones:
|
||
|
|
Array.isArray(f.inspiracion_secciones) && f.inspiracion_secciones.length
|
||
|
|
? f.inspiracion_secciones
|
||
|
|
: f.inspiracion
|
||
|
|
? [{ id: 'texto-principal', tipo: 'texto', texto: f.inspiracion }]
|
||
|
|
: [],
|
||
|
|
_legacyId: f.id,
|
||
|
|
_versionCodigo: f.version_codigo
|
||
|
|
});
|
||
|
|
const creditos = datos.cancion_interprete
|
||
|
|
.filter((x) => x.cancion_id === f.id)
|
||
|
|
.map((x) => ({
|
||
|
|
destino: x.artista_id,
|
||
|
|
papel: x.rol,
|
||
|
|
detalle: x.instrumento ?? '',
|
||
|
|
acreditadoComo: x.acreditado_como ?? '',
|
||
|
|
orden: x.orden
|
||
|
|
}));
|
||
|
|
creditos.push(
|
||
|
|
...datos.cancion_persona
|
||
|
|
.filter((x) => x.cancion_id === f.id && ['letra', 'musica', 'produccion'].includes(x.rol))
|
||
|
|
.map((x) => ({ destino: x.persona_id, papel: x.rol, detalle: '', orden: x.orden })),
|
||
|
|
...datos.cancion_etiqueta
|
||
|
|
.filter((x) => x.cancion_id === f.id)
|
||
|
|
.map((x) => ({ destino: x.etiqueta_id, papel: 'etiqueta', detalle: '', orden: x.orden }))
|
||
|
|
);
|
||
|
|
agregar(
|
||
|
|
'grabaciones',
|
||
|
|
{ ...f, id: `g:${f.id}`, slug: f.version_de_id ? f.slug : `${f.slug}-grabacion` },
|
||
|
|
{
|
||
|
|
cancionId: f.version_de_id ?? f.id,
|
||
|
|
versionNombre: f.version_nombre ?? 'Estudio',
|
||
|
|
estiloId: f.estilo_id,
|
||
|
|
idioma: f.idioma,
|
||
|
|
ano: f.ano,
|
||
|
|
letra: f.letra ?? '',
|
||
|
|
tieneLetra: f.tiene_letra,
|
||
|
|
formacionVocal: f.formacion_vocal ?? '',
|
||
|
|
acceso: f.acceso,
|
||
|
|
imagenId: f.portada_id ?? '',
|
||
|
|
audioId: audios.length ? `a:${f.id}` : '',
|
||
|
|
_legacyId: f.id
|
||
|
|
},
|
||
|
|
creditos
|
||
|
|
);
|
||
|
|
}
|
||
|
|
for (const a of datos.album)
|
||
|
|
agregar(
|
||
|
|
'lanzamientos',
|
||
|
|
a,
|
||
|
|
{
|
||
|
|
tipo: a.tipo,
|
||
|
|
estiloId: a.estilo_id,
|
||
|
|
fecha: a.fecha,
|
||
|
|
resumen: a.resumen,
|
||
|
|
cuerpo: a.cuerpo,
|
||
|
|
sello: a.sello ?? '',
|
||
|
|
numeroCatalogo: a.numero_catalogo ?? '',
|
||
|
|
imagenId: a.portada_id ?? ''
|
||
|
|
},
|
||
|
|
datos.cancion
|
||
|
|
.filter((f) => f.album_id === a.id)
|
||
|
|
.sort((a, b) => (a.numero ?? 0) - (b.numero ?? 0))
|
||
|
|
.map((f, i) => ({ destino: `g:${f.id}`, papel: 'pista', detalle: '', orden: i }))
|
||
|
|
);
|
||
|
|
for (const e of datos.entrada)
|
||
|
|
agregar(
|
||
|
|
'blog',
|
||
|
|
e,
|
||
|
|
{ fecha: e.fecha, resumen: e.resumen, cuerpo: e.cuerpo, imagenId: e.imagen_id ?? '' },
|
||
|
|
[
|
||
|
|
...datos.entrada_cancion
|
||
|
|
.filter((x) => x.entrada_id === e.id)
|
||
|
|
.map((x) => ({
|
||
|
|
destino:
|
||
|
|
datos.cancion.find((f) => f.id === x.cancion_id)?.version_de_id ?? x.cancion_id,
|
||
|
|
papel: 'cancion',
|
||
|
|
detalle: '',
|
||
|
|
orden: 0
|
||
|
|
})),
|
||
|
|
...datos.entrada_etiqueta
|
||
|
|
.filter((x) => x.entrada_id === e.id)
|
||
|
|
.map((x) => ({ destino: x.etiqueta_id, papel: 'etiqueta', detalle: '', orden: 0 }))
|
||
|
|
]
|
||
|
|
);
|
||
|
|
for (const p of datos.pagina)
|
||
|
|
agregar('paginas', p, { resumen: p.descripcion, cuerpo: p.cuerpo });
|
||
|
|
for (const l of datos.documento_legal) {
|
||
|
|
const v = datos.version_legal.find((v) => v.id === l.version_vigente_id);
|
||
|
|
agregar('legales', l, {
|
||
|
|
resumen: l.resumen,
|
||
|
|
cuerpo: v?.cuerpo ?? '',
|
||
|
|
fecha: v ? new Date(v.vigente_desde).toISOString().slice(0, 10) : '',
|
||
|
|
orden: l.orden
|
||
|
|
});
|
||
|
|
}
|
||
|
|
for (const g of datos.genero)
|
||
|
|
agregar(
|
||
|
|
'generos',
|
||
|
|
g,
|
||
|
|
{
|
||
|
|
resumen: g.resumen,
|
||
|
|
origenLugar: g.origen_lugar ?? '',
|
||
|
|
origenAno: g.origen_ano,
|
||
|
|
tempo: g.tempo ?? '',
|
||
|
|
rima: g.rima ?? '',
|
||
|
|
metricaNota: g.metrica_nota ?? '',
|
||
|
|
compasNota: g.compas_nota ?? '',
|
||
|
|
cuerpo:
|
||
|
|
datos.genero_seccion.find((s) => s.genero_id === g.id && s.clave === 'introduccion')
|
||
|
|
?.cuerpo ?? ''
|
||
|
|
},
|
||
|
|
[
|
||
|
|
...datos.genero_origen
|
||
|
|
.filter((r) => r.genero_id === g.id)
|
||
|
|
.map((r) => ({
|
||
|
|
destino: r.origen_id,
|
||
|
|
papel: r.tipo,
|
||
|
|
detalle: r.nota ?? '',
|
||
|
|
orden: r.orden
|
||
|
|
})),
|
||
|
|
...datos.genero_tema
|
||
|
|
.filter((r) => r.genero_id === g.id)
|
||
|
|
.map((r) => ({
|
||
|
|
destino: r.etiqueta_id,
|
||
|
|
papel: 'etiqueta',
|
||
|
|
detalle: '',
|
||
|
|
orden: r.orden
|
||
|
|
}))
|
||
|
|
]
|
||
|
|
);
|
||
|
|
for (const cap of datos.capitulo)
|
||
|
|
agregar('capitulos', cap, {
|
||
|
|
tipo: cap.tipo,
|
||
|
|
generoId: cap.genero_id ?? '',
|
||
|
|
orden: cap.orden,
|
||
|
|
resumen: cap.resumen,
|
||
|
|
cuerpo: cap.cuerpo
|
||
|
|
});
|
||
|
|
for (const t of datos.termino) {
|
||
|
|
const a = datos.acepcion.find((a) => a.termino_id === t.id && a.numero === 1);
|
||
|
|
agregar('glosario', t, {
|
||
|
|
categoria: a?.categoria ?? 'musica',
|
||
|
|
definicion: a?.definicion ?? '',
|
||
|
|
cuerpo: a?.ampliacion ?? ''
|
||
|
|
});
|
||
|
|
}
|
||
|
|
for (const m of datos.modelo_ia)
|
||
|
|
agregar('modelos', m, {
|
||
|
|
familia: m.familia,
|
||
|
|
version: m.version,
|
||
|
|
vigente: m.vigente,
|
||
|
|
esquema: m.esquema ? JSON.stringify(m.esquema, null, 2) : ''
|
||
|
|
});
|
||
|
|
for (const p of datos.prompt)
|
||
|
|
agregar(
|
||
|
|
'prompts',
|
||
|
|
{ ...p, titulo: p.notas?.slice(0, 90) || `Prompt ${p.id.slice(0, 8)}` },
|
||
|
|
{
|
||
|
|
modeloId: p.modelo_ia_id,
|
||
|
|
generoId: p.genero_id ?? '',
|
||
|
|
grabacionId: p.cancion_id ? `g:${p.cancion_id}` : '',
|
||
|
|
idioma: p.idioma,
|
||
|
|
contenido: JSON.stringify(p.contenido, null, 2),
|
||
|
|
notas: p.notas ?? '',
|
||
|
|
visibilidad: p.visibilidad
|
||
|
|
}
|
||
|
|
);
|
||
|
|
for (const r of datos.redireccion)
|
||
|
|
agregar(
|
||
|
|
'redirecciones',
|
||
|
|
{ id: `r:${r.desde}`, titulo: r.desde, slug: `redireccion-${docs.length}` },
|
||
|
|
{ desde: r.desde, hacia: r.hacia, motivo: r.motivo ?? '' }
|
||
|
|
);
|
||
|
|
for (const d of docs)
|
||
|
|
await c.query(
|
||
|
|
'INSERT INTO estudio_documento(id,tipo,estado,borrador,publicado,autor) VALUES($1,$2,$3,$4,$5,$6)',
|
||
|
|
[
|
||
|
|
d.id,
|
||
|
|
d.tipo,
|
||
|
|
d.estado,
|
||
|
|
d.borrador,
|
||
|
|
d.estado !== 'borrador' ? d.borrador : null,
|
||
|
|
'Importación inicial'
|
||
|
|
]
|
||
|
|
);
|
||
|
|
const ids = new Set(docs.map((d) => d.id));
|
||
|
|
const camposReferencia = [
|
||
|
|
'principalId',
|
||
|
|
'cancionId',
|
||
|
|
'estiloId',
|
||
|
|
'generoId',
|
||
|
|
'modeloId',
|
||
|
|
'grabacionId'
|
||
|
|
];
|
||
|
|
for (const d of docs) {
|
||
|
|
const links = [
|
||
|
|
...d.borrador.vinculos,
|
||
|
|
...camposReferencia
|
||
|
|
.filter((k) => d.borrador.campos[k])
|
||
|
|
.map((k) => ({ destino: d.borrador.campos[k], papel: k, detalle: '', orden: 0 }))
|
||
|
|
];
|
||
|
|
for (const v of links) {
|
||
|
|
if (!ids.has(v.destino)) throw Error(`Referencia no importada: ${d.tipo}/${d.id}`);
|
||
|
|
for (const edicion of d.estado !== 'borrador' ? ['borrador', 'publicado'] : ['borrador'])
|
||
|
|
await c.query(
|
||
|
|
'INSERT INTO estudio_vinculo(origen,destino,edicion,papel,orden,detalle) VALUES($1,$2,$3,$4,$5,$6) ON CONFLICT DO NOTHING',
|
||
|
|
[d.id, v.destino, edicion, v.papel, v.orden, v.detalle]
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
await c.query('UPDATE estudio_control SET importado_en=now(),huella_fuente=$1 WHERE id=1', [
|
||
|
|
huella(datos)
|
||
|
|
]);
|
||
|
|
await migrarPersonas(c);
|
||
|
|
await migrarInspiracion(c);
|
||
|
|
await c.query('COMMIT');
|
||
|
|
console.log(`Importadas ${docs.length} fichas; catálogo público conservado. Modo preparación.`);
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
await c.query('ROLLBACK');
|
||
|
|
console.error(e.message);
|
||
|
|
process.exitCode = 1;
|
||
|
|
} finally {
|
||
|
|
await c.end();
|
||
|
|
}
|