> For the complete documentation index, see [llms.txt](https://academy.gopersonal.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://academy.gopersonal.ai/developers/plataforma-ecommerce/vtex/funcion-de-parse.md).

# Función de parse

Esta guía es para quien configura la integración de catálogo de VTEX en GoPersonal y quiere escribir o modificar la **función de parse**: el fragmento de JavaScript que decide cómo se convierte cada producto de tu tienda VTEX en un item de GoPersonal. Explica de dónde salen los datos, con qué forma exacta llegan a la función (campo por campo), qué tiene que devolver y cómo probarla antes de guardarla. Al final hay un prompt listo para pedirle a un asistente de IA que escriba o cambie la función por vos.

***

## 1. Qué hace la función de parse

Cada vez que corre la sincronización de productos, GoPersonal:

1. Descarga el catálogo completo desde la API de VTEX con las credenciales de tu integración.
2. Normaliza cada producto a un formato intermedio (sección 3).
3. Ejecuta tu función de parse una sola vez, pasándole **todos** los productos normalizados.
4. Guarda cada objeto que devuelve tu función como un item de GoPersonal.
5. Marca como inactivo todo item que existía y no vino en esta sincronización.

Si no cargás una función, se usa la función por defecto (sección 2). La función se pega en el campo de función de parse de la configuración de la integración, y se aplica desde la próxima sincronización.

***

## 2. Formato de la función

Para VTEX la función es un **script**: un bloque de JavaScript que recibe una variable global `items` y tiene que dejar el resultado en la variable global `parsedResults`.

| Variable        | Tipo   | Uso                                                                                                     |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------- |
| `items`         | array  | Entrada: todos los productos del catálogo, ya normalizados por GoPersonal (sección 3)                   |
| `parsedResults` | array  | Salida: el script **tiene que asignarla** (`parsedResults = ...`). Cada elemento se guarda como un item |
| `console`       | objeto | Para logs de depuración durante la sincronización                                                       |

Reglas:

* JavaScript moderno (funciona optional chaining, spread, arrow functions). **No** hay `require`, `fetch`, `async/await` ni acceso a red o archivos.
* El script decide cuántos items genera: puede ser uno por SKU (lo que hace el default), uno por producto, o filtrar lo que no quiera enviar.
* Si el script tira una excepción, la sincronización completa falla y no se actualiza ningún item. Escribí código defensivo ante productos con datos faltantes.

Script por defecto:

```js
function transformProducts(products) {
    return products.flatMap(product =>
        product.skus.map(sku => ({
            ...product,
            ...sku,
            imgs: sku.imgs
        })).map(({id, name, description, category, brand, active, url, imgs}) => ({
            id,
            name,
            description,
            category,
            brand,
            active,
            url,
            imgs
        }))
    );
}

parsedResults = transformProducts(items);
```

Qué hace el default, en palabras: genera **un item por SKU**. Combina el producto con cada uno de sus SKUs (los campos del SKU pisan a los del producto: `id`, `name`, `active`, `url`, `price`, `regular_price`, `imgs`) y se queda solo con 8 campos. Cosas a tener en cuenta:

* `id` es el ID del SKU de VTEX, no el ID del producto.
* **No incluye `price`, `regular_price` ni `discount_tag`**, aunque están disponibles.
* No incluye talle, color, especificaciones, `category_ids`, EAN ni colecciones.
* `active` es la disponibilidad del SKU, no la del producto.

***

## 3. De dónde salen los datos

GoPersonal consulta la **Catalog Search API pública de VTEX** con la App Key y el App Token configurados en tu integración:

```
GET https://{tu-cuenta}.vtexcommercestable.com.br/api/catalog_system/pub/products/search?_from=0&_to=49
X-VTEX-API-AppKey:   {App Key}
X-VTEX-API-AppToken: {App Token}
```

* Recorre el catálogo de a 50 productos. VTEX limita este endpoint a 2500 productos; si tu catálogo es más grande, avisanos para activar el modo de catálogo grande, que trae todo el catálogo por ID. La función de parse funciona igual en los dos modos.
* Solo llegan productos que VTEX considera visibles para el buscador de la tienda. Un producto inactivo o sin SKUs activos en VTEX no viene, y por lo tanto queda inactivo en GoPersonal en la siguiente sincronización.
* Las URLs de producto y SKU se arman con la URL de la tienda configurada en la integración (o con la URL alternativa de productos, si la cargaste).

Para ver un producto crudo tal como lo entrega VTEX:

```bash
curl -s "https://{tu-cuenta}.vtexcommercestable.com.br/api/catalog_system/pub/products/search?_from=0&_to=0" \
  -H "X-VTEX-API-AppKey: $APP_KEY" -H "X-VTEX-API-AppToken: $APP_TOKEN" | jq '.[0]'
```

Ese JSON crudo **no** es lo que recibe tu función. GoPersonal lo normaliza primero al formato de la sección siguiente. La columna "Origen en VTEX" de cada tabla indica de qué campo del JSON crudo sale cada valor.

***

## 4. Qué recibe la función: `items` campo por campo

Cada elemento de `items` es un producto normalizado con esta forma. `baseUrl` es la URL de la tienda configurada en la integración.

### 4.1 Nivel producto

| Campo                        | Tipo           | Origen en VTEX                                                       | Cómo se calcula / notas                                                                                                                                                                                 |
| ---------------------------- | -------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                         | string         | `productId`                                                          | ID del producto. `""` si falta                                                                                                                                                                          |
| `name`                       | string         | `productName`                                                        |                                                                                                                                                                                                         |
| `description`                | string         | `description`                                                        | Suele traer HTML                                                                                                                                                                                        |
| `category`                   | string         | `categories[0]`                                                      | Path con barras, por ejemplo `"/Ropa/Remeras/"`. Es la **primera** categoría de la lista, que en VTEX es la más específica. Las demás no llegan                                                         |
| `category_ids`               | string         | `categoryId`                                                         | ID de la categoría más específica, como string. Un solo ID                                                                                                                                              |
| `brand`                      | string         | `brand`                                                              |                                                                                                                                                                                                         |
| `active`                     | `1` \| `0`     | `items[].sellers[0].commertialOffer.IsAvailable`                     | `1` si **algún** SKU tiene disponible su primer seller                                                                                                                                                  |
| `url`                        | string         | `linkText`                                                           | `baseUrl + "/" + linkText + "/p"`. `""` si no hay `linkText`                                                                                                                                            |
| `price`                      | number \| null | `items[].sellers[].commertialOffer.Price`                            | El **menor** precio de venta entre todos los sellers de todos los SKUs disponibles. `null` si ninguno está disponible                                                                                   |
| `regular_price`              | number \| null | `items[].sellers[].commertialOffer.ListPrice`                        | El **mayor** precio de lista entre sellers disponibles. `null` si ninguno                                                                                                                               |
| `discount_tag`               | string \| null | derivado                                                             | `"23.09% off"` cuando `price < regular_price`; si no, `null`                                                                                                                                            |
| `skus`                       | array          | `items[]`                                                            | Un objeto por SKU, ver 4.2                                                                                                                                                                              |
| `specs`                      | object         | `allSpecifications` + campos del producto                            | Especificaciones de producto: `{ "Material": ["Algodón"], "Género": ["Hombre"] }`. Cada valor es un array, tal como lo manda VTEX. No incluye las especificaciones de grupo (las que empiezan con `G.`) |
| `clusterHighlights`          | object         | `clusterHighlights`                                                  | Colecciones destacadas: `{ "141": "Nueva temporada" }` (ID → nombre). `{}` si no hay                                                                                                                    |
| `productClusters`            | object         | `productClusters`                                                    | Todas las colecciones del producto, mismo formato. Solo está presente si VTEX lo envía                                                                                                                  |
| `<nombre de especificación>` | string         | primer valor de cada especificación del grupo **"Especificaciones"** | Si tu tienda tiene un grupo de especificaciones llamado exactamente `Especificaciones`, cada una aparece también como campo plano con su primer valor. Ejemplo: `Material: "Algodón"`                   |

### 4.2 Nivel SKU: `skus[]`

| Campo           | Tipo                                        | Origen en VTEX (`items[i]`)                   | Notas                                                                                                                                                                                                   |
| --------------- | ------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | string                                      | `itemId`                                      | ID del SKU. `""` si falta                                                                                                                                                                               |
| `name`          | string                                      | `name`                                        | Nombre del SKU, por ejemplo `"Remera Básica Azul M"`                                                                                                                                                    |
| `ean`           | string \| undefined                         | `ean`                                         | Puede no venir                                                                                                                                                                                          |
| `active`        | `1` \| `0`                                  | `sellers[0].commertialOffer.IsAvailable`      | Disponibilidad según el primer seller                                                                                                                                                                   |
| `url`           | string                                      | `detailUrl`                                   | `baseUrl + detailUrl`. `""` si falta                                                                                                                                                                    |
| `price`         | number \| null                              | `sellers[0].commertialOffer.Price`            | Precio de venta del primer seller                                                                                                                                                                       |
| `regular_price` | number \| null                              | `sellers[0].commertialOffer.ListPrice`        | Precio de lista del primer seller                                                                                                                                                                       |
| `imgs`          | `Array<{url}>`                              | `images[].imageUrl`                           | Sin el querystring de versión. `[]` si el SKU no tiene imágenes                                                                                                                                         |
| `sku_specs`     | `Array<{FieldName, FieldValues: string[]}>` | `variations[]` y `skuSpecifications[]`        | Variaciones del SKU (talle, color, etc.). Ejemplo: `{FieldName: "Talla", FieldValues: ["M"]}`. VTEX suele mandar la misma variación por los dos caminos, así que **es normal que aparezcan duplicadas** |
| `product_specs` | `Array<{FieldName, FieldValues: string[]}>` | especificaciones del grupo "Especificaciones" | Las mismas especificaciones de producto del punto anterior, en formato lista y con todos sus valores. Se repiten en cada SKU                                                                            |
| `talla`         | string\[] \| undefined                      | `item["Talla Sku"]`                           | Solo tiene valor si tu tienda usa una variación llamada exactamente `Talla Sku`                                                                                                                         |

### 4.3 Campos de VTEX que no llegan

Estos campos del JSON crudo no forman parte de `items`: `productReference`, `productReferenceCode`, `categoriesIds`, las categorías después de la primera, `link`, `releaseDate`, `metaTagDescription`, `productTitle`, `brandId`, `referenceId`, `nameComplete`, `complementName`, `measurementUnit`, `unitMultiplier`, `isKit`, los sellers después del primero, `AvailableQuantity`, `Installments`, `Teasers`, `DiscountHighLight`, `PriceValidUntil`. Si necesitás alguno, contactanos.

### 4.4 Ejemplo de un elemento de `items`

```json
{
  "id": "1234",
  "name": "Remera Básica",
  "description": "<p>Remera de algodón.</p>",
  "category": "/Ropa/Remeras/",
  "category_ids": "27",
  "brand": "Acme",
  "active": 1,
  "url": "https://www.tienda.com/remera-basica/p",
  "price": 9990,
  "regular_price": 12990,
  "discount_tag": "23.09% off",
  "specs": { "Material": ["Algodón"], "Género": ["Hombre"] },
  "clusterHighlights": { "141": "Nueva temporada" },
  "productClusters": { "141": "Nueva temporada", "98": "Sale" },
  "Material": "Algodón",
  "Género": "Hombre",
  "skus": [
    {
      "id": "5678",
      "name": "Remera Básica Azul M",
      "ean": "7790001234567",
      "active": 1,
      "url": "https://www.tienda.com/remera-basica/p",
      "price": 9990,
      "regular_price": 12990,
      "imgs": [{ "url": "https://acme.vtexassets.com/arquivos/ids/155400/remera-azul.jpg" }],
      "sku_specs": [
        { "FieldName": "Talla", "FieldValues": ["M"] },
        { "FieldName": "Color", "FieldValues": ["Azul"] },
        { "FieldName": "Talla", "FieldValues": ["M"] },
        { "FieldName": "Color", "FieldValues": ["Azul"] }
      ],
      "product_specs": [
        { "FieldName": "Material", "FieldValues": ["Algodón"] },
        { "FieldName": "Género", "FieldValues": ["Hombre"] }
      ]
    }
  ]
}
```

***

## 5. Qué tiene que devolver

Cada elemento de `parsedResults` se guarda como un item. No hay un esquema obligatorio: podés agregar cualquier campo y queda disponible para filtros, reglas, recomendaciones y templates. Estos son los campos que GoPersonal ya sabe interpretar:

| Campo           | Tipo                   | Para qué se usa                                                                                                                                                                                          |
| --------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | string                 | **Identidad del item.** Obligatorio. Tiene que ser el mismo ID que envían los eventos de tu tienda (vista de producto, agregar al carrito, compra); si no coinciden, los eventos no se atribuyen al item |
| `active`        | `1` \| `0`             | Solo se muestran y recomiendan items con `active: 1`                                                                                                                                                     |
| `name`          | string                 | Nombre visible                                                                                                                                                                                           |
| `description`   | string                 | Texto o HTML                                                                                                                                                                                             |
| `url`           | string                 | Link del producto en emails y widgets                                                                                                                                                                    |
| `imgs`          | `Array<{url: string}>` | La imagen principal es `imgs[0].url`                                                                                                                                                                     |
| `price`         | number                 | Precio de venta actual                                                                                                                                                                                   |
| `regular_price` | number                 | Precio de lista, para mostrar tachado                                                                                                                                                                    |
| `discount_tag`  | string \| null         | Etiqueta de descuento lista para mostrar                                                                                                                                                                 |
| `category`      | string                 | Categoría o path; se usa en filtros y en el listado de categorías                                                                                                                                        |
| `category_ids`  | string                 | IDs separados por `,` o `\|`. GoPersonal genera automáticamente la lista `category_ids_list` para filtrar                                                                                                |
| `brand`         | string                 | Marca, para filtros                                                                                                                                                                                      |
| `sku`           | string                 | Código SKU, para cruzar con otras fuentes                                                                                                                                                                |
| `sku_list`      | string\[]              | IDs de las variantes del producto, para atribuir eventos que llegan con ID de variante                                                                                                                   |
| cualquier otro  | libre                  | Se guarda tal cual                                                                                                                                                                                       |

Reglas que conviene respetar:

* **`id` siempre como string y con una estrategia estable.** Si un día el `id` es el SKU y al siguiente es el producto, se generan items duplicados: los viejos quedan inactivos pero no se borran.
* **Todo lo que no devuelvas queda inactivo.** Para excluir productos o SKUs, simplemente no los incluyas en `parsedResults`.
* **Sin valores que cambien solos** (fechas, números al azar). GoPersonal compara cada item con su versión anterior para actualizar solo lo que cambió; un valor que varía en cada corrida obliga a reescribir todo el catálogo.
* Devolvé siempre objetos planos. Un `null` o un array dentro de `parsedResults` hace fallar la sincronización.

***

## 6. Ejemplos

**Un item por SKU con precio, talle y color** (el pedido más frecuente):

```js
function specValue(sku, name) {
  const s = (sku.sku_specs || []).find(x => x.FieldName === name);
  return s && s.FieldValues && s.FieldValues[0] ? s.FieldValues[0] : '';
}

parsedResults = items.flatMap(product =>
  product.skus.map(sku => ({
    id: sku.id,
    product_id: product.id,
    name: product.name,
    sku_name: sku.name,
    description: product.description,
    category: product.category,
    category_ids: product.category_ids,
    brand: product.brand,
    active: sku.active,
    url: sku.url || product.url,
    imgs: sku.imgs.length ? sku.imgs : (product.skus[0] ? product.skus[0].imgs : []),
    price: sku.price,
    regular_price: sku.regular_price,
    discount_tag: product.discount_tag,
    talle: specValue(sku, 'Talla'),
    color: specValue(sku, 'Color'),
    ean: sku.ean || '',
    collections: Object.values(product.productClusters || {})
  }))
);
```

**Un item por producto** (`id` = ID de producto, imágenes del primer SKU, lista de SKUs):

```js
parsedResults = items.map(product => ({
  id: product.id,
  name: product.name,
  description: product.description,
  category: product.category,
  category_ids: product.category_ids,
  brand: product.brand,
  active: product.active,
  url: product.url,
  price: product.price,
  regular_price: product.regular_price,
  discount_tag: product.discount_tag,
  imgs: (product.skus[0] && product.skus[0].imgs) || [],
  sku_list: product.skus.map(s => s.id),
  specs: product.specs
}));
```

**Filtrar**: excluir una categoría y quedarse solo con SKUs disponibles.

```js
parsedResults = items
  .filter(product => !product.category.startsWith('/Outlet/'))
  .flatMap(product => product.skus
    .filter(sku => sku.active === 1)
    .map(sku => ({
      id: sku.id,
      name: product.name,
      category: product.category,
      brand: product.brand,
      active: 1,
      url: sku.url || product.url,
      imgs: sku.imgs,
      price: sku.price,
      regular_price: sku.regular_price
    }))
  );
```

***

## 7. Cómo probar la función antes de guardarla

Con Node.js instalado podés ejecutar tu script contra el ejemplo de la sección 4.4 (o contra varios productos armados con esa misma forma), exactamente como lo hace GoPersonal:

1. Guardá el ejemplo en `items.json` como un array: `[ { ...producto... } ]`.
2. Guardá tu script en `parse.js`.
3. Ejecutá:

```bash
node -e '
const vm = require("vm"); const fs = require("fs");
const sandbox = { items: JSON.parse(fs.readFileSync("items.json", "utf8")), parsedResults: [], console };
new vm.Script(fs.readFileSync("parse.js", "utf8")).runInContext(vm.createContext(sandbox));
console.log(JSON.stringify(sandbox.parsedResults, null, 2));
'
```

Revisá que cada objeto tenga `id` como string, `active` en `1` o `0`, y los campos que esperás. Recién después pegá el script en la integración; la próxima sincronización lo usa.

***

## 8. Prompt listo para pedir cambios

Copiá este bloque, completá lo que está entre `<...>` y pegá debajo las secciones indicadas de esta guía. Sirve igual para escribir la función desde cero.

```
Tenés que escribir la función de parse de la integración de catálogo VTEX de GoPersonal.
Es un script de JavaScript que recibe la variable global `items` y tiene que asignar la
variable global `parsedResults` con los items a guardar.

Contexto (no lo cuestiones, así funciona la plataforma):
<pegar la sección 2 "Formato de la función">
<pegar la sección 4 "Qué recibe la función", incluyendo el ejemplo 4.4>
<pegar la sección 5 "Qué tiene que devolver">

Script actual:
<pegar el script que está hoy, o "ninguno, se usa el default">

Lo que necesito cambiar:
<describir en lenguaje natural. Ejemplo: "un item por SKU con talle y color como campos
separados, precio del SKU, y excluir la categoría Outlet">

Restricciones:
- JavaScript puro: sin require, sin fetch, sin async, sin acceso a red.
- Tiene que terminar asignando parsedResults. Puede usar console.log para depurar.
- Defensivo ante campos faltantes: nunca tiene que tirar una excepción por un producto raro.
- `id` siempre string y con la misma estrategia que el script actual (si cambia, avisame
  explícitamente porque duplica items).
- Sin valores que cambien entre corridas (fechas, random).
- Mantené los nombres y tipos de los campos estándar de la sección 5.

Devolveme:
1. El script completo, listo para pegar, sin explicaciones dentro del código.
2. Una lista corta de qué cambió respecto al script actual y qué campos nuevos aparecen.
3. El objeto de salida que genera para el producto de ejemplo de la sección 4.4.
```
