🔗 Integración de Ares con Typesense
La integración de Ares con Typesense permite que los productos concentrados en Ares estén disponibles en un buscador de alto rendimiento.
Para implementarla, seguimos la documentación oficial de Typesense, lo que nos permitió apoyarnos directamente en los endpoints y buenas prácticas provistas por el proyecto.
💡 Idea General
El objetivo principal de esta integración es que todos los productos que llegan a Ares (desde PIM, Pricing o Williams) puedan ser indexados en Typesense para su búsqueda y consulta.
Ares se encarga de:
- Detectar productos pendientes de publicación en el canal TYPESENSE.
- Transformarlos a la estructura esperada por Typesense.
- Publicarlos usando los endpoints oficiales de Typesense Client.
- Mantener un log del resultado de cada operación.
Resumen de Integración
| Etapa | Descripción |
|---|---|
| Selección de productos | Se consultan en MongoDB hasta 3000 productos pendientes de publicación (status PENDING o sin canal registrado). |
| Clasificación | Cada producto se clasifica como CREATE, UPDATE, DELETE (no implementado) o IGNORE. |
| Transformación | Los productos se transforman mediante map templates al formato esperado por Typesense. |
| Publicación | Se envían en batch a Typesense usando documents().import() con acción create o update. |
| Persistencia | Se actualizan los productos en MongoDB con el nuevo estado y se registran logs en ProductLog para trazabilidad. |
🔄 Flujo de Integración
-
Ares detecta productos pendientes
- Consulta en MongoDB aquellos productos que tengan
channels.TYPESENSE.status = PENDINGo que no tengan el canal registrado. - Máximo 3000 productos por ejecución.
- Consulta en MongoDB aquellos productos que tengan
-
Clasificación por tipo de publicación
- CREATE → Producto nuevo en Typesense.
- UPDATE → Producto ya existente que requiere actualización.
- DELETE → No implementado actualmente.
- IGNORE → Productos que no deben publicarse (ejemplo:
producto de tipo configurable).
-
Transformación
- Cada producto se transforma mediante plantillas (
map templates) que definen cómo mapear los atributos de Ares a la estructura de Typesense.
- Cada producto se transforma mediante plantillas (
-
Publicación en Typesense
- Se usa el SDK oficial de Typesense (
typesense.Client) para conectarse con el cluster. - Los productos se envían en batch mediante el endpoint
documents().import()con las acciones:create→ Inserción de nuevos productos.update→ Actualización de productos existentes.
- Se usa el SDK oficial de Typesense (
-
Persistencia de resultados
- Se actualizan los productos en MongoDB con el nuevo estado (
CREATE_SENT,UPDATE_SENT,IGNORED, etc.). - Se insertan logs en la colección
ProductLogpara auditoría.
- Se actualizan los productos en MongoDB con el nuevo estado (
📡 Endpoints de Typesense Usados
La integración se apoya en los endpoints oficiales del SDK de Typesense:
- Importación de documentos
client.collections(collectioName).documents().import(mappedProducts, {
action: 'create',
return_id: true
}); - Documentación oficial click aquí para mas detalle
↔️ Mapeo de datos
Se utiliza la siguiente plantilla personalizada que transforma la informacion de un producto almacenado en Ares para que sea correctamente interpretado por Typesense.
Plantilla
export const map = {
template: {
id: {
path: "sku",
transform: (value, transformedObject, originalObject) =>
value ?? originalObject?._id,
},
sku: {
path: "sku",
transform: (value, transformedObject, originalObject) =>
value ?? originalObject?._id,
},
type: {
path: "product_type",
},
name: {
path: "name",
},
description: {
path: "description",
},
created_at: {
path: "createdAt",
},
color_filter: {
path: "color",
transform: (value, transformedObject, originalObject) => {
return getColorFilter(
originalObject,
"color_color",
originalObject._config.colors
);
},
},
color_family: {
path: "color",
transform: (value, transformedObject, originalObject) => {
return getColorFilter(
originalObject,
"color_family",
originalObject._config.colors
);
},
},
categories: {
path: "categories",
transform: (val) => {
const categories = Array.isArray(val) ? val : [];
const levels = { level0: [], level1: [], level2: [], level3: [] };
categories.forEach((category) => {
const categoryParts = category.split("/");
switch (categoryParts.length) {
case 1:
levels.level0.push(categoryParts[0]);
break;
case 2:
levels.level1.push(categoryParts.join(" /// "));
break;
case 3:
levels.level2.push(categoryParts.join(" /// "));
break;
case 4:
levels.level3.push(categoryParts.join(" /// "));
break;
default:
break;
}
});
return levels;
},
},
l2: {
path: "categories",
transform: (value, transformedObject) => {
let result = "";
const level2 = transformedObject?.categories?.level2;
if (Array.isArray(level2) && level2.length) {
const last = level2[level2.length - 1];
if (last.startsWith("Muebles")) {
const parts = last.split("///");
result = parts.pop();
}
}
return result;
},
},
categories_without_path: {
path: "categories",
transform: (val) => {
const list = new Set();
if (val) {
val.forEach((e) => {
const parts = e.split("/");
parts.forEach((part) => {
list.add(part.trim());
});
});
}
return Array.from(list);
},
},
image: {
path: "images",
transform: (value, transformedObject, originalObject) => {
let results = [];
if (Array.isArray(value)) {
value.push(originalObject?.image ?? "");
results = value.filter((link) => link.toLowerCase().includes("_still1"));
if (!results.length) {
results = value.filter((link) => link.toLowerCase().includes("_still2"));
}
}
let image = results.length ? results[0] : "";
image = image.replace("http:", "https:");
return image;
},
},
image_url: {
path: "images",
transform: (value, transformedObject) => transformedObject.image,
},
look_image: {
path: "images",
transform: (value, transformedObject, originalObject) => {
let results = [];
if (Array.isArray(value)) {
value.push(originalObject?.image ?? "");
results = value.filter((link) => link.toLowerCase().includes("_ho."));
if (!results.length) {
results = value.filter((link) => link.toLowerCase().includes("_still2"));
}
}
let image = results.length ? results[0] : "";
image = image.replace("http:", "https:");
return image;
},
},
images: {
path: "images",
transform: (val, transformedObject, originalObject) => {
let images = val ?? [];
images.unshift(originalObject?.image);
images = images
.filter(url => url !== undefined && url.trim() !== '')
.map(url => url.replace('http:', 'https:'));
return images.filter((element, index) => {
return images.indexOf(element) === index;
});
}
},
url: {
path: "link",
transform: (value, transformedObject, originalObject) => {
return originalObject.product_link ?? originalObject.link ?? "";
},
},
url_path: {
path: "link",
transform: (value, transformedObject, originalObject) => {
let link = originalObject.product_link ?? originalObject.link ?? "";
return link.replace(/^https?:\/\/[^\/]+/, "");
},
},
product_url: {
path: "product_link",
},
product_url_path: {
path: "product_link",
transform: (val) => val?.replace(/^https?:\/\/[^\/]+/, "") || "",
},
price: {
path: "sale_price",
transform: (val) => ({
MXN: {
default: val,
default_formated: parseFloat(val).toLocaleString("en-US", {
style: "currency",
currency: "USD",
}),
},
}),
},
msrp: {
path: "price",
},
discount_amount: {
path: "price",
transform: (value, transformedObject, originalObject) => {
const msrp = originalObject.price > originalObject.sale_price ? originalObject.price : originalObject.sale_price;
const price = originalObject.sale_price;
const diff = parseFloat(msrp) - parseFloat(price);
return parseFloat(diff.toFixed(2));
},
},
discount_percentage: {
path: "price",
transform: (value, transformedObject, originalObject) => {
var percentage = 0;
if (
originalObject.price > 0 &&
originalObject.price > originalObject.sale_price
) {
var diff = originalObject.price - originalObject.sale_price;
percentage = (diff / originalObject.price) * 100;
}
return parseFloat(percentage.toFixed(2));
},
},
has_discount: {
path: "price",
transform: (value, transformedObject, originalObject) =>
transformedObject.discount_percentage > 5 ? true : false,
},
in_stock: {
path: "availability",
transform: (val) => (val > 0 ? 1 : 0),
},
stock_qty: {
path: "availability",
},
stock: {
path: "stock",
transform: (stock) => {
stock = stock || {};
const result = {};
const store_codes = Object.keys(stock).filter(code => code.toUpperCase() !== 'CEDIS' && !code.toUpperCase().endsWith('_EX'));
for (const storeCode of store_codes) {
const exhibition = stock[storeCode + '_EX'] ?? 0;
const takehome = stock[storeCode] ?? 0;
if (exhibition || takehome) {
result[storeCode] = {
ex: exhibition,
th: takehome,
// Retroactivo
exhibition: null,
takehome: null
};
} else {
result[storeCode] = null;
}
// Retroactivo
result[storeCode.toUpperCase()+'_EX'] = null;
result[storeCode.toLowerCase()] = null;
result[storeCode.toLowerCase()+'_ex'] = null;
}
// Retroactivo
for (let key of ['CEDIS', 'cedis', 'cedis_ex', 'viasanangel', 'shpaseoqro', 'shocn', 'shmpc', 'shmol', 'shmerida', 'shforo4', 'shant', 'shand', 'satelite', 'santafe', 'parquedelta', 'montvetro', 'montvalle', 'interlomas_granada', 'galeriasguadalajara', 'artz']) {
result[key] = null;
}
return result;
},
},
stores: {
path: "stock",
transform: (stock) => {
stock = stock || {};
const result = {};
const store_codes = Object.keys(stock).filter(code => code.toUpperCase() !== 'CEDIS' && !code.toUpperCase().endsWith('_EX'));
for (const storeCode of store_codes) {
const exhibition = stock[storeCode + '_EX'] ?? 0;
const takehome = stock[storeCode] ?? 0;
if (exhibition || takehome) {
result[storeCode] = {
ex: exhibition,
th: takehome,
};
} else {
result[storeCode] = null;
}
}
return result;
},
},
visibility: {
path: "visibility",
transform: (val) => {
let v = "";
switch (val) {
case 1:
v = "Not Visible Individually";
break;
case 2:
v = "Catalog";
break;
case 3:
v = "Buscar";
break;
case 4:
v = "Catalog, Search";
break;
case 5:
v = "Atraves del simple";
break;
}
return v;
},
},
comportamiento_bajo_umbral: {
path: "cbu",
},
comercializable: {
path: "comercializable",
transform: (val) => val && val === 1,
},
alto: {
path: "product_height",
transform: (val) => val !== undefined ? val : 0
},
ancho: {
path: "product_width",
transform: (val) => val !== undefined ? val : 0
},
largo: {
path: "product_length",
transform: (val) => val !== undefined ? val : 0
},
estilo: {
path: "linea",
transform: (val) => val !== undefined ? val : ""
},
extra_days_code: {
path: "extra_days_code",
transform: (val) => val ? val : ""
},
is_enabled: {
path: "status",
transform: (val) => val === true,
},
is_available: {
path: "salable",
transform: (val) => (val && val === true ? true : false),
},
is_extend_zone: {
path: "extend_zone",
transform: (val, transformedObject, originalObject) => {
if (originalObject.product_type === 'gaiagrouped') {
let simples = Object.values(originalObject.elements || []);
return simples.some((e) => e?.extend_zone === true);
} else {
return val === true;
}
},
},
is_express_delivery: {
path: "salable",
transform: (val, transformedObject, originalObject) => {
return isExpressDelivery(originalObject)
},
},
delivery_days: {
path: "availability",
transform: (stock, transformedObject, originalObject) => {
let days = null;
if (originalObject.product_type === 'simple') {
days = stock ? originalObject.tiempo_entrega_max : originalObject.tiempo_entrega_max_sin_stock;
} else if (originalObject.product_type === 'gaiagrouped') {
let simples = Object.values(originalObject.elements || []);
let tiempo_entrega_max = Math.max.apply(Math, simples.map((e) => e.tiempo_entrega_max));
let tiempo_entrega_max_sin_stock = Math.max.apply(Math, simples.map((e) => e.tiempo_entrega_max_sin_stock));
days = stock ? tiempo_entrega_max : tiempo_entrega_max_sin_stock;
}
return days;
},
},
label_discount: {
path: "label_discount",
transform: (val) => val ? val : "",
},
has_label_discount: {
path: "label_discount",
transform: (val) => !!(val ?? "").trim(),
},
is_oferta_del_dia: {
path: "label_discount",
transform: (val) => val === "Oferta del Día",
},
is_oferta_flash: {
path: "label_discount",
transform: (val) => val === "Oferta Flash",
},
is_40_off: {
path: "label_discount",
transform: (val) => "40% OFF" === String(val).trim().toLocaleUpperCase(),
},
variant_code: {
path: "sku",
transform: (val, transformedObject, originalObject) => originalObject._id.split('_')[0]
},
"variants.color": {
path: "color_variants",
transform: (val, transformedObject, originalObject) => {
let variants = [];
if (val && Array.isArray(val)) {
variants = Object.values(val).map((variant) => {
variant.color = Array.isArray(variant.color)
? variant.color[0]
: variant.color;
return {
product_code: variant.sku,
name: variant.name,
price: variant.sale_price,
msrp: variant.price,
image: variant.image,
color: getColorFilter(
variant,
"color",
originalObject._config.colors
),
color_family: getColorFilter(
variant,
"color_family",
originalObject._config.colors
),
url_path: variant.link?.replace(/^https?:\/\/[^\/]+/, "") || "",
url_product_path: variant.product_link_path,
is_available: variant.salable,
label_discount: variant?.label_discount ?? "",
has_label_discount: !!(variant?.label_discount ?? "").trim(),
is_express_delivery: isExpressDelivery(variant)
};
});
}
return variants.filter(
(variant) => variant.product_code != originalObject._id && variant.is_available == true
);
},
},
num_ratings: {
path: "reviews_io",
transform: (val) => val?.count ?? 0,
},
average_rating: {
path: "reviews_io",
transform: (val) => val?.average ?? 0,
},
average_rating_rounded: {
path: "reviews_io",
transform: (val) => val?.average ? parseInt(val.average) : 0
},
weighted_score: {
path: "master_scores",
transform: (val) => val?.score_1 ? parseFloat(val.score_1) : 0
},
enhanced_attributes: {
path: "enhanced_attributes",
transform: (val) => val ?? [],
},
facetable_attributes: {
path: "facetable_attributes",
transform: (val) => val ?? {},
},
recommended_items: {
path: "recommended_items",
transform: (val) => val ?? [],
},
tags_descuento: {
path: "tags_descuento",
transform: (val) => val ?? [],
},
extra_tags: {
path: "extra_tags",
transform: (val) => val ?? [],
},
additional_services: {
path: "additional_services",
transform: (val, transformedObject, originalObject) => {
const services = val ?? [];
return originalObject.product_type === 'gaiagrouped' ? [] : services;
},
},
landing_pages: {
path: "landing_pages",
transform: (val) => Array.isArray(val) ? val : []
},
has_cashback: {
path: "has_cashback",
transform: (val, transformedObject, originalObject) => {
return val === true
&& originalObject.product_type !== 'gaiagrouped';
}
},
cat_nav: {
path: "name",
transform: (val, transformedObject, originalObject) => {
const index = [
originalObject?.category_1 ?? '',
originalObject?.category_2 ?? '',
originalObject?.category_3 ?? ''
].join('+');
const newCat = getCatNavMap()[index] ?? '';
if (newCat) {
return newCat;
} else {
console.log(`No se encontró la categoría ${index} en el mapa de categorías`);
return 'NOT_FOUND';
}
}
},
publish: {
path: "channels",
transform: (val) => ({
source:
process.env?.IS_LOCAL == "true"
? `local ${process.env?.USER}`
: process.env?.AWS_LAMBDA_FUNCTION_NAME,
date: `${new Date().toLocaleString()} (${process.env.TZ})`,
changes: val.TYPESENSE.pending_changes,
}),
},
},
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 getCatNavMap = (categories) => {
return {
"MUEBLES+MESAS+ESCRITORIOS": "MESAS",
"MUEBLES+MESAS+MESAS DE CENTRO": "MESAS",
"MUEBLES+MESAS+MESAS DE COMEDOR": "MESAS",
"MUEBLES+MESAS+MESAS LATERALES": "MESAS",
"MUEBLES EXTERIOR+MESAS EXTERIOR+MESAS DE CENTRO EXTERIOR": "MESAS",
"MUEBLES EXTERIOR+MESAS EXTERIOR+MESAS DE COMEDOR EXTERIOR": "MESAS",
"MUEBLES EXTERIOR+SETS EXTERIOR+SETS DE COMEDORES EXTERIOR": "MESAS",
"MUEBLES+SETS+SETS DE COMEDOR": "MESAS",
"MUEBLES+SETS+SETS DE COMEDORES": "MESAS",
"MUEBLES+SETS+SETS DE MESAS": "MESAS",
"MUEBLES+SILLAS+BANCAS": "SILLAS",
"MUEBLES+SILLAS+BANCOS ALTOS": "SILLAS",
"MUEBLES+SILLAS+SILLAS DE COMEDOR": "SILLAS",
"MUEBLES+SILLAS+SILLAS DE OFICINA": "SILLAS",
"MUEBLES EXTERIOR+SETS EXTERIOR+SETS DE SILLAS EXTERIOR": "SILLAS",
"MUEBLES EXTERIOR+SILLAS EXTERIOR+BANCAS EXTERIOR": "SILLAS",
"MUEBLES EXTERIOR+SILLAS EXTERIOR+SILLAS DE COMEDOR EXTERIOR": "SILLAS",
"MUEBLES+SETS+SETS DE SILLAS": "SILLAS",
"MUEBLES+SETS+SETS DE BANCOS": "SILLAS",
"MUEBLES+SETS+SETS DE OFICINA": "SILLAS",
"MUEBLES+MESAS+ESCRITORIOS": "HOME OFFICE",
"MUEBLES+SILLAS+SILLAS DE OFICINA": "HOME OFFICE",
"MUEBLES+SILLAS+SILLAS DE COMEDOR": "HOME OFFICE",
"ILUMINACION+LAMPARAS+LAMPARAS DE MESA": "HOME OFFICE",
"MUEBLES+SOFAS+CHAISES": "SOFAS",
"MUEBLES+SOFAS+ESQUINEROS": "SOFAS",
"MUEBLES+SOFAS+PUFFS": "SOFAS",
"MUEBLES+SOFAS+SET DE SOFAS": "SOFAS",
"MUEBLES+SOFAS+SILLONES": "SOFAS",
"MUEBLES+SOFAS+SOFAS 2 PLAZAS": "SOFAS",
"MUEBLES+SOFAS+SOFAS 3 PLAZAS": "SOFAS",
"MUEBLES+SOFAS+SOFAS 4 PLAZAS": "SOFAS",
"MUEBLES+SOFAS+SOFAS CAMAS": "SOFAS",
"MUEBLES+SOFAS+TABURETES": "SOFAS",
"MUEBLES EXTERIOR+SETS EXTERIOR+SETS DE SOFAS EXTERIOR": "SOFAS",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+ARMARIOS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+BUROS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+CARRO DE SERVICIO": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+COMODAS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+CONSOLAS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+CREDENZAS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+LIBREROS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+MUEBLES DE BAÑO": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+MUEBLES DE BAR": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+MUEBLES PARA TV": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+TOCADORES": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+VITRINAS": "ALMACENAJE",
"MUEBLES+MUEBLES DE ALMACENAMIENTO+ZAPATERAS": "ALMACENAJE",
"MUEBLES+CAMAS+BASES DE CAMA": "CAMAS",
"MUEBLES+CAMAS+CABECERAS": "CAMAS",
"MUEBLES+CAMAS+CAMAS": "CAMAS",
"MUEBLES+CAMAS+COLCHONES": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+ALMOHADAS": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+COLCHAS": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+FRAZADAS": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+FUNDAS DE DUVETS": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+PROTECTOR DE COLCHON": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+RELLENO DE DUVET": "CAMAS",
"ACCESORIOS+ROPA DE CAMA+SABANAS": "CAMAS",
"MUEBLES+SETS+SETS DE RECAMARA": "CAMAS",
"ILUMINACION+ACCESORIOS DE ILUMINACION+FOCOS DECORATIVOS": "LÁMPARAS",
"ILUMINACION+ACCESORIOS DE ILUMINACION+PANTALLAS DE LAMPARAS": "LÁMPARAS",
"ILUMINACION+LAMPARAS+LAMPARAS DE MESA": "LÁMPARAS",
"ILUMINACION+LAMPARAS+LAMPARAS DE PIE": "LÁMPARAS",
"ILUMINACION+LAMPARAS+LAMPARAS DE TECHO": "LÁMPARAS",
"MUEBLES EXTERIOR+ACCESORIOS EXTERIOR+ILUMINACION DE EXTERIOR": "LÁMPARAS",
"ACCESORIOS+COJINES+COJIN RELLENO": "COJINES",
"ACCESORIOS+COJINES+FUNDAS DE COJINES": "COJINES",
"ACCESORIOS+COJINES+RELLENOS DE COJINES": "COJINES",
"ACCESORIOS+COJINES+SET DE COJINES": "COJINES",
"MUEBLES EXTERIOR+ACCESORIOS EXTERIOR+COJINES EXTERIOR": "COJINES",
"MUEBLES KIDS+ACCESORIOS KIDS+COJINES KIDS": "COJINES",
"MUEBLES EXTERIOR+ACCESORIOS EXTERIOR+COJINES EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+ACCESORIOS EXTERIOR+ILUMINACION DE EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+MESAS EXTERIOR+MESAS DE CENTRO EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+MESAS EXTERIOR+MESAS DE COMEDOR EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+SETS EXTERIOR+SETS DE COMEDORES EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+SETS EXTERIOR+SETS DE SILLAS EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+SETS EXTERIOR+SETS DE SOFAS EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+SILLAS EXTERIOR+BANCAS EXTERIOR": "JARDÍN Y EXTERIOR",
"MUEBLES EXTERIOR+SILLAS EXTERIOR+SILLAS DE COMEDOR EXTERIOR": "JARDÍN Y EXTERIOR",
"ACCESORIOS+TAPETES+TAPETES DE DECORACION": "TAPETES",
"ACCESORIOS+BAÑO+ACCESORIOS PARA BAÑO": "ACCESORIOS",
"ACCESORIOS+COJINES+COJIN RELLENO": "ACCESORIOS",
"ACCESORIOS+COJINES+FUNDAS DE COJINES": "ACCESORIOS",
"ACCESORIOS+COJINES+RELLENOS DE COJINES": "ACCESORIOS",
"ACCESORIOS+COJINES+SET DE COJINES": "ACCESORIOS",
"ACCESORIOS+COMEDOR+ACCESORIOS DE BAR": "ACCESORIOS",
"ACCESORIOS+COMEDOR+CUBERTERIA": "ACCESORIOS",
"ACCESORIOS+COMEDOR+PLATOS": "ACCESORIOS",
"ACCESORIOS+COMEDOR+PORTAVASOS": "ACCESORIOS",
"ACCESORIOS+CRISTALERIA+SET DE COPAS": "ACCESORIOS",
"ACCESORIOS+DECORACION+CUADROS": "ACCESORIOS",
"ACCESORIOS+DECORACION+DECORACION DE MESA": "ACCESORIOS",
"ACCESORIOS+DECORACION+DECORACION DE PARED": "ACCESORIOS",
"ACCESORIOS+DECORACION+FLOREROS": "ACCESORIOS",
"ACCESORIOS+DECORACION+MACETAS": "ACCESORIOS",
"ACCESORIOS+DECORACION+MACETEROS": "ACCESORIOS",
"ACCESORIOS+DECORACION+MARCOS": "ACCESORIOS",
"ACCESORIOS+DECORACION+PORTAVELAS": "ACCESORIOS",
"ACCESORIOS+DECORACION+RELOJES": "ACCESORIOS",
"ACCESORIOS+ESPEJOS+ESPEJOS DE PARED": "ACCESORIOS",
"ACCESORIOS+ESPEJOS+ESPEJOS DE PISO": "ACCESORIOS",
"ACCESORIOS+ESPEJOS+SET DE ESPEJOS": "ACCESORIOS",
"ACCESORIOS+FRAGANCIAS+DIFUSORES": "ACCESORIOS",
"ACCESORIOS+FRAGANCIAS+SPRAYS AROMATIZANTES": "ACCESORIOS",
"ACCESORIOS+FRAGANCIAS+VELAS": "ACCESORIOS",
"ACCESORIOS+LAMPARAS+LAMPARAS DE MESA": "ACCESORIOS",
"ACCESORIOS+LAMPARAS+LAMPARAS DE TECHO": "ACCESORIOS",
"ACCESORIOS+MANTELERIA+CAMINOS DE MESA": "ACCESORIOS",
"ACCESORIOS+MANTELERIA+MANTELES": "ACCESORIOS",
"ACCESORIOS+MANTELERIA+SERVILLETAS": "ACCESORIOS",
"ACCESORIOS+ORGANIZACION+ORGANIZADORES": "ACCESORIOS",
"ACCESORIOS+ORGANIZACION+PERCHEROS": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+ALMOHADAS": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+COLCHAS": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+FRAZADAS": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+FUNDAS DE DUVETS": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+PROTECTOR DE COLCHON": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+RELLENO DE DUVET": "ACCESORIOS",
"ACCESORIOS+ROPA DE CAMA+SABANAS": "ACCESORIOS",
"MUEBLES KIDS+ACCESORIOS KIDS+COJINES KIDS": "KIDS",
"MUEBLES KIDS+ACCESORIOS KIDS+DECORACION DE PARED KIDS": "KIDS",
"MUEBLES KIDS+ACCESORIOS KIDS+FRAZADAS KIDS": "KIDS",
"MUEBLES KIDS+CAMAS KIDS+BICAMAS": "KIDS",
"MUEBLES KIDS+CAMAS KIDS+CAMAS ENTERAS KIDS": "KIDS",
"MUEBLES KIDS+CAMAS KIDS+CUNAS KIDS": "KIDS",
"MUEBLES KIDS+MESAS KIDS+CAMBIADORES KIDS": "KIDS",
"MUEBLES KIDS+MESAS KIDS+MESITAS KIDS": "KIDS",
"MUEBLES KIDS+MUEBLES DE ALMACENAMIENTO KIDS+BUROS KIDS": "KIDS",
"MUEBLES KIDS+MUEBLES DE ALMACENAMIENTO KIDS+COMODAS KIDS": "KIDS",
"MUEBLES KIDS+SETS DE KIDS+SETS DE MESITAS KIDS": "KIDS",
"MUEBLES KIDS+SILLAS KIDS+SILLITAS KIDS": "KIDS",
"MUEBLES KIDS+SOFAS KIDS+SILLONES KIDS": "KIDS"
};
}
Esta plantilla es procesada usando la libreria json-map-transform