🔗 Integración de Ares con Meta
La integración con Meta - Product Catalog permite publicar productos de Ares hacia un Google Spreadsheet, el cual es consumido diariamente por Meta siguiendo las reglas con su documentación oficial.
⚙️ Flujo de Integración
| Etapa | Descripción |
|---|---|
| Ejecución | Se dispara Cada hora mediante un cron job en AWS EventBridge. |
| Extracción | Se consultan en MongoDB (Ares) los productos con filtros: completed = true, comercializable = 1, visibility = CatalogSearch. |
| Transformación | Los productos se convierten al formato GMC usando un map template y reglas adicionales (afterTransform). |
| Validaciones | Se descartan productos sin imagen, donde sale price > price y salable != true. |
| Publicación | Se limpian y cargan los datos en un Google Spreadsheet (sheet: Automatizado). |
| Consumo GMC | Meta sincroniza automáticamente Cada hora leyendo el spreadsheet. |
↔️ Mapeo de datos
Plantilla
import { transform } from "lodash";
export const map = {
template: {
id: {
path: "sku",
transform: (value, transformedObject, originalObject) =>
value ?? originalObject?._id,
},
title: {
path: "name",
},
description: {
path: "description",
},
availability: {
path: "salable",
transform: (salable, transformedObject, originalObject) => {
if (salable) {
return "in stock";
} else {
return "out of stock";
}
},
},
link: {
path: "product_link",
transform: (val) => val,
},
'availability date': {
path: "sku",
transform: (val, transformedObject, originalObject) => {
if ("backorder" === transformedObject?.availability) {
const days = originalObject.tiempo_entrega_max_sin_stock;
return addBusinessDays(new Date(), 20);
}
},
},
'image link': {
path: "image",
transform: (value, transformedObject) => {
return replaceImageHost(value);
},
},
price: {
path: "price",
transform: (val, transformedObject, originalObject) => {
if (val) {
return `${val} MXN`
} else if (originalObject?.sale_price) {
return `${originalObject.sale_price} MXN`
}
},
},
'sale price': {
path: "sale_price",
transform: (val, transformedObject, originalObject) => {
if (val) {
return `${val} MXN`
} else if (originalObject?.price) {
return `${originalObject.price} MXN`
}
},
},
'identifier exists': {
path: "name",
transform: (value, transformedObject, originalObject) => 'no',
},
gtin: {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
mpn: {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
brand: {
path: "name",
transform: (value, transformedObject, originalObject) => 'GAIA',
},
'product highlight': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'product detail': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'additional image link': {
path: "images",
transform: (images, transformedObject, originalObject) => {
const options = [];
if (Array.isArray(images)) {
images.forEach(img => {
if (img.includes("_miniambiente") || img.includes("_infografico")) {
options.push(img);
}
});
}
if (originalObject?.image) {
options.push(originalObject.image);
}
return replaceImageHost(options[0]);
},
},
condition: {
path: "name",
transform: (value, transformedObject, originalObject) => 'new',
},
adult: {
path: "name",
transform: (value, transformedObject, originalObject) => 'no',
},
color: {
path: "color_principal",
},
material: {
path: "material",
transform: (value) => {
if (value && Array.isArray(value)) {
return value[0];
}
if (value) {
return value;
}
return undefined;
},
},
size: {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
gender: {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
pattern: {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'age group': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
multipack: {
path: "name",
transform: (value, transformedObject, originalObject) => {
return undefined;
},
},
'is bundle': {
path: "product_type",
transform: (value, transformedObject, originalObject) => {
return 'gaiagrouped' == value ? 'yes' : 'no';
},
},
'unit pricing measure': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'unit pricing base measure': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'energy efficiency class': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'min energy efficiency class': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'min energy efficiency class': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'item group id': {
path: "sku",
transform: (val, transformedObject, originalObject) => originalObject._id.split('_')[0]
},
'sell on google quantity': {
path: "name",
transform: (value, transformedObject, originalObject) => undefined,
},
'custom label 0': {
path: "sku",
transform: (value, transformedObject, originalObject) => {
const cat2 = originalObject.category_2;
return `${cat2}`;
},
},
'custom label 1': {
path: "name",
transform: (value, transformedObject, originalObject) => {
const cat2 = originalObject.category_3;
return `${cat2}`;
},
},
'custom label 2': {
path: "sku",
transform: (value, transformedObject, originalObject) => {
return Array.isArray(originalObject?.landing_pages) ? originalObject.landing_pages.join(',') : '';
},
},
},
afterTransform: null,
};
const getColorFilter = (originalObject, colorAttr, colorsMap) => {
const colorInfo = colorsMap.find(
(e) => e.key == originalObject[colorAttr]
) ?? { color: { hex: "#FFFFFF" }, translate: "" };
return {
name: colorInfo.translate,
color: colorInfo.color.hex,
refinement: `${colorInfo.translate};${colorInfo.color.hex}`,
};
};
const isExpressDelivery = (product) => {
let isExpressDelivery = false;
const isAvailable = product.salable;
const maxDeliveryTime = product.tiempo_entrega_max || 0;
const maxDeliveryTimeWithoutStock =
product.tiempo_entrega_max_sin_stock || 0;
const quantity = product.availability;
if (maxDeliveryTime && isAvailable) {
isExpressDelivery = quantity > 0 || (quantity == 0 && isAvailable);
const deliveryDays =
isExpressDelivery && quantity > 0
? maxDeliveryTime
: maxDeliveryTimeWithoutStock;
isExpressDelivery = deliveryDays <= 8; // ahora esta hardcodeado pero se seteaba en M2
}
return isExpressDelivery;
};
const replaceImageHost = (url) => {
if (url) {
const filename = url.split('/').pop();
return `https://cdnpim.gaia.design/${filename}`;
} else {
return url;
}
};
const addBusinessDays = (startDate, businessDaysToAdd) => {
const result = new Date(startDate);
let addedDays = 0;
while (addedDays < businessDaysToAdd) {
result.setDate(result.getDate() + 1);
const day = result.getDay();
if (day !== 0 && day !== 6) { // 0 = domingo, 6 = sábado
addedDays++;
}
}
const offset = -6 * 60; // -0600 en minutos
const pad = (n) => String(n).padStart(2, '0');
const formatted = `${result.getUTCFullYear()}-${pad(result.getUTCMonth() + 1)}-${pad(result.getUTCDate())}T${pad(result.getUTCHours())}:${pad(result.getUTCMinutes())}${offset >= 0 ? '+' : '-'}${pad(Math.abs(offset / 60))}00`;
return formatted;
}
Esta plantilla es procesada usando la libreria json-map-transform