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/estudio/preparar.mjs

343 lines
10 KiB

/** 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();
}

Powered by TurnKey Linux.