audit and sync

This commit is contained in:
Lucas Tettamanti
2026-01-18 19:00:49 -03:00
parent 3b39e706af
commit 23c3d44490
9 changed files with 404 additions and 19 deletions

View File

@@ -276,3 +276,159 @@ export async function refreshProductByWooId({ tenantId, wooId, parentId = null }
return normalized;
}
// ─────────────────────────────────────────────────────────────
// Sincronización completa con WooCommerce
// ─────────────────────────────────────────────────────────────
/**
* Obtiene todos los productos de WooCommerce usando paginación
*/
export async function fetchAllWooProducts({ tenantId }) {
const client = await getWooClient({ tenantId });
const allProducts = [];
let page = 1;
const perPage = 100;
console.log("[wooSnapshot] fetchAllWooProducts starting...");
while (true) {
const url = `${client.base}/products?per_page=${perPage}&page=${page}&status=any`;
const data = await fetchWoo({
url,
method: "GET",
timeout: client.timeout * 2, // Más tiempo para listados grandes
headers: client.authHeader
});
if (!Array.isArray(data) || data.length === 0) {
break;
}
allProducts.push(...data);
console.log(`[wooSnapshot] fetchAllWooProducts page ${page}: ${data.length} products (total: ${allProducts.length})`);
if (data.length < perPage) {
break; // Última página
}
page++;
}
console.log(`[wooSnapshot] fetchAllWooProducts completed: ${allProducts.length} products`);
return allProducts;
}
/**
* Sincroniza todos los productos desde WooCommerce (reemplaza snapshot local)
*/
export async function syncFromWoo({ tenantId }) {
console.log("[wooSnapshot] syncFromWoo starting...");
const t0 = Date.now();
// 1. Obtener todos los productos de Woo
const wooProducts = await fetchAllWooProducts({ tenantId });
if (wooProducts.length === 0) {
console.log("[wooSnapshot] syncFromWoo: no products found in Woo");
return { ok: true, synced: 0, ms: Date.now() - t0 };
}
// 2. Crear run para tracking
const runId = await insertSnapshotRun({ tenantId, source: "sync_from_woo", total: wooProducts.length });
// 3. Normalizar e insertar productos
const normalized = wooProducts.map(normalizeWooProduct);
await upsertSnapshotItems({ tenantId, items: normalized, runId });
// 4. Eliminar productos que ya no existen en Woo
await deleteMissingItems({ tenantId, runId });
const ms = Date.now() - t0;
console.log(`[wooSnapshot] syncFromWoo completed: ${wooProducts.length} products in ${ms}ms`);
return { ok: true, synced: wooProducts.length, ms };
}
/**
* Pushea cambios de un producto a WooCommerce
*/
export async function pushProductToWoo({ tenantId, wooProductId, categories, sellUnit }) {
const client = await getWooClient({ tenantId });
const url = `${client.base}/products/${encodeURIComponent(wooProductId)}`;
// Construir payload de actualización
const updatePayload = {};
// Actualizar categorías si se proporcionaron
if (categories && Array.isArray(categories) && categories.length > 0) {
// Primero obtener las categorías existentes en Woo para mapear nombres a IDs
const existingCats = await fetchWooCategoriesByNames({ tenantId, names: categories });
if (existingCats.length > 0) {
updatePayload.categories = existingCats.map(c => ({ id: c.id }));
}
}
// Actualizar meta de unidad de venta
if (sellUnit) {
updatePayload.meta_data = [
{ key: "_sell_unit_override", value: sellUnit }
];
}
// Solo hacer request si hay algo que actualizar
if (Object.keys(updatePayload).length === 0) {
return { ok: true, woo_product_id: wooProductId, updated: false };
}
try {
const data = await fetchWoo({
url,
method: "PUT",
body: updatePayload,
timeout: client.timeout,
headers: client.authHeader,
});
if (dbg.wooHttp) console.log("[wooSnapshot] pushProductToWoo", { wooProductId, updated: true });
return { ok: true, woo_product_id: wooProductId, updated: true, data };
} catch (err) {
console.error("[wooSnapshot] pushProductToWoo error:", err.message);
throw err;
}
}
/**
* Obtiene categorías de WooCommerce por nombres
*/
async function fetchWooCategoriesByNames({ tenantId, names }) {
if (!names || names.length === 0) return [];
const client = await getWooClient({ tenantId });
const allCategories = [];
let page = 1;
// Obtener todas las categorías de Woo (usualmente son pocas)
while (true) {
const url = `${client.base}/products/categories?per_page=100&page=${page}`;
const data = await fetchWoo({
url,
method: "GET",
timeout: client.timeout,
headers: client.authHeader,
});
if (!Array.isArray(data) || data.length === 0) break;
allCategories.push(...data);
if (data.length < 100) break;
page++;
}
// Filtrar las que coinciden con los nombres buscados
const normalizedNames = names.map(n => String(n).toLowerCase().trim());
return allCategories.filter(c =>
normalizedNames.includes(String(c.name).toLowerCase().trim()) ||
normalizedNames.includes(String(c.slug).toLowerCase().trim())
);
}