Saltar al contenido principal

🔗 Integración de Ares con GMC

La integración con Google Merchant Center (GMC) permite publicar productos de Ares hacia un Google Spreadsheet, el cual es consumido diariamente por GMC siguiendo las reglas con su documentación oficial.

⚙️ Flujo de Integración

EtapaDescripción
EjecuciónSe dispara 1 vez al día mediante un cron job en AWS EventBridge.
ExtracciónSe consultan en MongoDB (Ares) los productos con filtros: completed = true, comercializable = 1, visibility = CatalogSearch.
TransformaciónLos productos se convierten al formato GMC usando un map template y reglas adicionales (afterTransform).
ValidacionesSe descartan productos sin imagen y donde sale price > price.
PublicaciónSe limpian y cargan los datos en un Google Spreadsheet (sheet: Automatizado).
Consumo GMCGMC sincroniza automáticamente 1 vez al día 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 originalObject?.availability > 0 ? "in stock" : "backorder";
} 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