separated in modules

This commit is contained in:
Lucas Tettamanti
2026-01-15 22:45:33 -03:00
parent eedd16afdb
commit ea62385e3d
41 changed files with 1116 additions and 2918 deletions

View File

@@ -0,0 +1,222 @@
import fs from "fs";
import path from "path";
import { parse } from "csv-parse/sync";
import { pool } from "../src/modules/2-identity/db/pool.js";
function parseArgs() {
const args = process.argv.slice(2);
const out = { file: null, tenantKey: null, replace: true };
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === "--file") out.file = args[++i];
else if (a === "--tenant-key") out.tenantKey = args[++i];
else if (a === "--no-replace") out.replace = false;
}
if (!out.file) {
throw new Error("Usage: node scripts/import-woo-snapshot.mjs --file <path> [--tenant-key <key>] [--no-replace]");
}
return out;
}
function parseNumber(val) {
if (val == null) return null;
const s = String(val).replace(/\./g, "").replace(",", ".").trim();
if (!s) return null;
const n = Number(s);
return Number.isFinite(n) ? n : null;
}
function parseBool(val) {
if (val == null) return null;
const s = String(val).trim().toLowerCase();
if (s === "1" || s === "si" || s === "sí" || s === "yes" || s === "true") return true;
if (s === "0" || s === "no" || s === "false") return false;
return null;
}
function splitList(val) {
if (!val) return [];
return String(val)
.split(",")
.map((v) => v.trim())
.filter(Boolean);
}
function extractAttributes(row) {
const attrs = {};
for (const key of Object.keys(row)) {
const m = /^Nombre del atributo (\d+)$/i.exec(key);
if (!m) continue;
const idx = m[1];
const name = String(row[key] || "").trim();
if (!name) continue;
const valuesKey = `Valor(es) del atributo ${idx}`;
const rawValues = row[valuesKey];
const values = String(rawValues || "")
.split("|")
.map((v) => v.trim())
.filter(Boolean);
attrs[name.toLowerCase()] = values.length ? values : [String(rawValues || "").trim()].filter(Boolean);
}
return attrs;
}
function normalizeRow(row) {
const wooId = Number(row["ID"] || row["Id"] || row["id"] || null);
const type = String(row["Tipo"] || "").trim().toLowerCase();
const parentId = Number(row["Superior"] || null) || null;
const name = String(row["Nombre"] || "").trim();
const slug = String(row["Slug"] || row["slug"] || "").trim() || null;
const published = parseBool(row["Publicado"]);
const status = published === true ? "publish" : published === false ? "draft" : null;
const visibility = String(row["Visibilidad en el catálogo"] || "").trim() || null;
const priceRegular = parseNumber(row["Precio normal"]);
const priceSale = parseNumber(row["Precio rebajado"]);
const priceCurrent = priceSale != null ? priceSale : priceRegular;
const hasStock = parseBool(row["¿Existencias?"]);
const stockQty = parseNumber(row["Inventario"]);
const backorders = String(row["¿Permitir reservas de productos agotados?"] || "").trim().toLowerCase() || null;
const weightKg = parseNumber(row["Peso (kg)"]);
const categories = splitList(row["Categorías"]);
const tags = splitList(row["Etiquetas"]);
const attributes = extractAttributes(row);
if (weightKg != null) attributes["peso_kg"] = [String(weightKg)];
let stockStatus = null;
if (hasStock === true) {
if (stockQty == null || stockQty > 0) stockStatus = "instock";
else stockStatus = "outofstock";
} else if (hasStock === false) {
stockStatus = "outofstock";
}
return {
woo_id: wooId,
type,
parent_id: parentId,
name,
slug,
status,
catalog_visibility: visibility,
price_regular: priceRegular,
price_sale: priceSale,
price_current: priceCurrent,
stock_status: stockStatus,
stock_qty: stockQty == null ? null : Math.round(stockQty),
backorders,
categories,
tags,
attributes_normalized: attributes,
};
}
async function getTenants(tenantKey) {
if (tenantKey) {
const { rows } = await pool.query(`select id, key from tenants where key=$1`, [tenantKey]);
return rows;
}
const { rows } = await pool.query(`select id, key from tenants`);
return rows;
}
async function insertRun({ tenantId, total, source }) {
const { rows } = await pool.query(
`insert into woo_snapshot_runs (tenant_id, source, total_items) values ($1, $2, $3) returning id`,
[tenantId, source, total]
);
return rows[0]?.id || null;
}
async function upsertSnapshotItem({ tenantId, runId, item, raw }) {
const q = `
insert into woo_products_snapshot
(tenant_id, woo_id, type, parent_id, name, slug, status, catalog_visibility,
price_regular, price_sale, price_current, stock_status, stock_qty, backorders,
categories, tags, attributes_normalized, date_modified, run_id, raw, updated_at)
values
($1,$2,$3,$4,$5,$6,$7,$8,
$9,$10,$11,$12,$13,$14,
$15::jsonb,$16::jsonb,$17::jsonb,$18,$19,$20::jsonb,now())
on conflict (tenant_id, woo_id)
do update set
type = excluded.type,
parent_id = excluded.parent_id,
name = excluded.name,
slug = excluded.slug,
status = excluded.status,
catalog_visibility = excluded.catalog_visibility,
price_regular = excluded.price_regular,
price_sale = excluded.price_sale,
price_current = excluded.price_current,
stock_status = excluded.stock_status,
stock_qty = excluded.stock_qty,
backorders = excluded.backorders,
categories = excluded.categories,
tags = excluded.tags,
attributes_normalized = excluded.attributes_normalized,
date_modified = excluded.date_modified,
run_id = excluded.run_id,
raw = excluded.raw,
updated_at = now()
`;
await pool.query(q, [
tenantId,
item.woo_id,
item.type,
item.parent_id,
item.name,
item.slug,
item.status,
item.catalog_visibility,
item.price_regular,
item.price_sale,
item.price_current,
item.stock_status,
item.stock_qty,
item.backorders,
JSON.stringify(item.categories ?? []),
JSON.stringify(item.tags ?? []),
JSON.stringify(item.attributes_normalized ?? {}),
null,
runId,
JSON.stringify(raw ?? {}),
]);
}
async function deleteMissing({ tenantId, runId }) {
await pool.query(
`delete from woo_products_snapshot where tenant_id=$1 and coalesce(run_id,0) <> $2`,
[tenantId, runId]
);
}
async function main() {
const { file, tenantKey, replace } = parseArgs();
const abs = path.resolve(file);
const content = fs.readFileSync(abs);
const records = parse(content, { columns: true, skip_empty_lines: true });
const normalized = records.map((r) => ({ item: normalizeRow(r), raw: r })).filter((r) => r.item.woo_id && r.item.name);
const tenants = await getTenants(tenantKey);
if (!tenants.length) {
throw new Error("No tenants found for import");
}
for (const t of tenants) {
const runId = await insertRun({ tenantId: t.id, total: normalized.length, source: "csv" });
for (const row of normalized) {
await upsertSnapshotItem({ tenantId: t.id, runId, item: row.item, raw: row.raw });
}
if (replace && runId) {
await deleteMissing({ tenantId: t.id, runId });
}
console.log(`[import] tenant=${t.key} items=${normalized.length} run_id=${runId}`);
}
await pool.end();
}
main().catch((err) => {
console.error(err);
process.exit(1);
});