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.
183 lines
7.0 KiB
183 lines
7.0 KiB
import { randomUUID } from 'node:crypto';
|
|
/** Unifica únicamente identidades personales enlazadas de forma explícita y unívoca. */
|
|
export async function migrarPersonas(c) {
|
|
await c.query(`CREATE TABLE IF NOT EXISTS estudio_alias (id text PRIMARY KEY,documento_id text NOT NULL REFERENCES estudio_documento(id) ON DELETE RESTRICT);
|
|
CREATE TABLE IF NOT EXISTS estudio_migracion (clave text PRIMARY KEY,datos jsonb NOT NULL,fecha timestamptz NOT NULL DEFAULT now());`);
|
|
await c.query("SELECT pg_advisory_xact_lock(hashtext('estudio:publicar'))");
|
|
const { rows: docs } = await c.query('SELECT * FROM estudio_documento FOR UPDATE');
|
|
const antiguas = docs.filter((d) => ['artistas', 'colaboradores'].includes(d.tipo));
|
|
if (!antiguas.length) return { convertidas: 0, unificadas: 0 };
|
|
const respaldo = { documentos: docs };
|
|
for (const tabla of ['estudio_vinculo', 'estudio_historial', 'estudio_trabajo'])
|
|
respaldo[tabla] = (await c.query(`SELECT * FROM ${tabla}`)).rows;
|
|
await c.query('INSERT INTO estudio_migracion(clave,datos) VALUES($1,$2)', [
|
|
'personas-' + randomUUID(),
|
|
respaldo
|
|
]);
|
|
const { rows: identidades } = await c.query(
|
|
"SELECT ap.* FROM artista_persona ap JOIN artista a ON a.id=ap.artista_id WHERE ap.relacion='identidad' AND a.tipo='persona'"
|
|
);
|
|
const { rows: artistas } = await c.query('SELECT id,slug,tipo,es_virtual FROM artista');
|
|
const { rows: personas } = await c.query(
|
|
'SELECT id,slug,nombre_legal,ipi,nacimiento,defuncion FROM persona'
|
|
);
|
|
const { rows: creditos } = await c.query(
|
|
"SELECT persona_id,CASE rol WHEN 'letra' THEN 'letrista' WHEN 'musica' THEN 'compositor' WHEN 'produccion' THEN 'productor' END AS tipo FROM cancion_persona UNION SELECT persona_id,CASE papel WHEN 'letrista' THEN 'letrista' WHEN 'compositor' THEN 'compositor' WHEN 'interprete' THEN 'artista' END FROM genero_persona"
|
|
);
|
|
const aliases = new Map();
|
|
for (const r of identidades) {
|
|
if (
|
|
identidades.filter((x) => x.persona_id === r.persona_id).length !== 1 ||
|
|
identidades.filter((x) => x.artista_id === r.artista_id).length !== 1
|
|
)
|
|
continue;
|
|
if (
|
|
docs.some((d) => d.id === r.artista_id && d.tipo === 'artistas') &&
|
|
docs.some((d) => d.id === r.persona_id && d.tipo === 'colaboradores')
|
|
)
|
|
aliases.set(r.artista_id, r.persona_id);
|
|
}
|
|
const grupos = new Map();
|
|
for (const d of antiguas) {
|
|
const id = aliases.get(d.id) ?? d.id;
|
|
grupos.set(id, [...(grupos.get(id) ?? []), d]);
|
|
}
|
|
const reservados = new Set(docs.filter((d) => d.tipo === 'personas').map((d) => d.borrador.slug));
|
|
const nuevos = [];
|
|
for (const [id, grupo] of grupos) {
|
|
const a = grupo.find((d) => d.tipo === 'artistas'),
|
|
p = grupo.find((d) => d.tipo === 'colaboradores');
|
|
const identidad = artistas.find((x) => x.id === a?.id),
|
|
fisica = personas.find((x) => x.id === p?.id);
|
|
const convertir = (edicion) => {
|
|
const x = a?.[edicion],
|
|
y = p?.[edicion];
|
|
if (!x && !y) return null;
|
|
const f = { ...(y?.campos ?? {}), ...(x?.campos ?? {}) };
|
|
const roles = new Set([
|
|
...(f.tiposPersona ?? []),
|
|
...(a
|
|
? [identidad?.es_virtual || identidad?.tipo === 'virtual' ? 'artista_virtual' : 'artista']
|
|
: []),
|
|
...creditos.filter((r) => r.persona_id === p?.id && r.tipo).map((r) => r.tipo)
|
|
]);
|
|
f.tiposPersona = [...roles];
|
|
f.naturaleza = identidad?.tipo === 'virtual' ? 'proyecto' : (identidad?.tipo ?? 'persona');
|
|
f.biografia = String(x?.campos.biografia || y?.campos.semblanza || '');
|
|
f.secciones =
|
|
x?.campos.secciones ??
|
|
(f.biografia ? [{ id: 'biografia-principal', tipo: 'texto', texto: f.biografia }] : []);
|
|
f.resumen = String(x?.campos.resumen ?? '');
|
|
f.nombreLegal = y?.campos.nombreLegal ?? fisica?.nombre_legal ?? '';
|
|
f.ipi = y?.campos.ipi ?? fisica?.ipi ?? '';
|
|
f.nacimiento = fisica?.nacimiento ?? null;
|
|
f.defuncion = fisica?.defuncion ?? null;
|
|
f._artistaId = identidad?.id ?? '';
|
|
f._personaId = fisica?.id ?? '';
|
|
f._personaSlug = fisica?.slug ?? '';
|
|
f._importado = true;
|
|
delete f.tipo;
|
|
delete f.semblanza;
|
|
return {
|
|
titulo: (x ?? y).titulo,
|
|
slug: (x ?? y).slug,
|
|
campos: f,
|
|
vinculos: [...(x?.vinculos ?? []), ...(y?.vinculos ?? [])]
|
|
};
|
|
};
|
|
const borrador = convertir('borrador'),
|
|
publicado = convertir('publicado');
|
|
if (reservados.has(borrador.slug)) {
|
|
borrador.slug += '-' + id.slice(0, 8);
|
|
}
|
|
reservados.add(borrador.slug);
|
|
const base = a ?? p;
|
|
nuevos.push({
|
|
...base,
|
|
id,
|
|
tipo: 'personas',
|
|
estado: grupo.some((d) => d.estado === 'publicado') ? 'publicado' : base.estado,
|
|
borrador,
|
|
publicado,
|
|
revision: Math.max(...grupo.map((d) => d.revision)) + 1
|
|
});
|
|
}
|
|
// Los enlaces apuntan a la ficha unificada; cada crédito conserva su papel.
|
|
const remap = (b) => {
|
|
if (!b) return b;
|
|
const vistos = new Set();
|
|
b.vinculos = (b.vinculos ?? [])
|
|
.map((v) => ({ ...v, destino: aliases.get(v.destino) ?? v.destino }))
|
|
.filter((v) => {
|
|
const k = v.papel + ':' + v.destino;
|
|
if (vistos.has(k)) return false;
|
|
vistos.add(k);
|
|
return true;
|
|
});
|
|
return b;
|
|
};
|
|
const todas = docs.filter((d) => !antiguas.some((x) => x.id === d.id)).concat(nuevos);
|
|
for (const d of todas) {
|
|
remap(d.borrador);
|
|
remap(d.publicado);
|
|
}
|
|
await c.query('DELETE FROM estudio_vinculo');
|
|
for (const d of nuevos) {
|
|
await c.query(
|
|
'UPDATE estudio_documento SET tipo=$2,borrador=$3,publicado=$4,revision=$5,estado=$6,actualizado=now(),autor=$7 WHERE id=$1',
|
|
[d.id, d.tipo, d.borrador, d.publicado, d.revision, d.estado, 'Migración a Personas']
|
|
);
|
|
await c.query(
|
|
'INSERT INTO estudio_historial(documento_id,revision,accion,autor,contenido) VALUES($1,$2,$3,$4,$5)',
|
|
[d.id, d.revision, 'unificar persona', 'Migración a Personas', d.borrador]
|
|
);
|
|
}
|
|
for (const d of todas.filter((d) => d.tipo !== 'personas'))
|
|
await c.query('UPDATE estudio_documento SET borrador=$2,publicado=$3 WHERE id=$1', [
|
|
d.id,
|
|
d.borrador,
|
|
d.publicado
|
|
]);
|
|
for (const [antes, despues] of aliases) {
|
|
await c.query('UPDATE estudio_historial SET documento_id=$2 WHERE documento_id=$1', [
|
|
antes,
|
|
despues
|
|
]);
|
|
await c.query('UPDATE estudio_trabajo SET documento_id=$2 WHERE documento_id=$1', [
|
|
antes,
|
|
despues
|
|
]);
|
|
await c.query(
|
|
'INSERT INTO estudio_alias(id,documento_id) VALUES($1,$2) ON CONFLICT(id) DO UPDATE SET documento_id=EXCLUDED.documento_id',
|
|
[antes, despues]
|
|
);
|
|
await c.query('DELETE FROM estudio_documento WHERE id=$1', [antes]);
|
|
}
|
|
const referencias = [
|
|
'principalId',
|
|
'cancionId',
|
|
'estiloId',
|
|
'generoId',
|
|
'modeloId',
|
|
'grabacionId'
|
|
];
|
|
for (const d of todas)
|
|
for (const edicion of ['borrador', 'publicado']) {
|
|
const b = d[edicion];
|
|
if (!b) continue;
|
|
const vinculos = [
|
|
...b.vinculos,
|
|
...referencias
|
|
.filter((k) => b.campos[k])
|
|
.map((k) => ({ destino: b.campos[k], papel: k, detalle: '', orden: 0 }))
|
|
];
|
|
for (const v of vinculos)
|
|
if (v.destino !== d.id)
|
|
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 ?? '']
|
|
);
|
|
}
|
|
return { convertidas: nuevos.length, unificadas: aliases.size };
|
|
}
|