> 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/tiendanube/funcion-de-parse.md).

# Función de parse

Esta guía es para quien configura la integración de catálogo de Tiendanube (Nuvemshop en Brasil) 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 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.

para acceder a la función ingresa a sección Integraciones en el Menu y selecciona la integración de Tiendanube ya creada.

***

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

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

1. Descarga todos los productos publicados de tu tienda desde la API de Tiendanube.
2. Ejecuta tu función de parse **una vez por producto**, pasándole el producto tal como lo entrega la API, sin transformar.
3. Guarda el objeto que devuelve tu función como un item de GoPersonal.
4. 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 Tiendanube la función es una **función de JavaScript** llamada `parse` que recibe un producto y devuelve un objeto:

```js
function parse(product) {
  return { /* ... */ };
}
```

Reglas:

* JavaScript moderno (funciona optional chaining, spread, arrow functions). **No** hay `require`, `fetch`, `async/await`, acceso a red ni a archivos.
* **No hay `console`.** Un `console.log` dentro de la función produce un error.
* Devolvé **siempre un objeto**, uno por producto. No se puede devolver `null` para saltear un producto, ni un array para generar varios items por producto.
* El campo `active` **siempre queda en `1`**. Si devolvés `active: 0` (o no lo devolvés), GoPersonal lo reemplaza por `1`. Los items se desactivan únicamente cuando el producto deja de venir de la API (por ejemplo, porque se despublicó). Si necesitás excluir productos sin stock, usá un campo propio (ver ejemplo en la sección 6).
* Si la función tira una excepción, los productos del mismo lote (hasta 250) quedan fuera de esa sincronización y pasan a inactivos; el resto del catálogo sigue procesándose. Escribí código defensivo ante campos faltantes: `(product.images || [])`, `product.name?.es`.

Función por defecto:

```js
function parse(product) {
  const firstVariant = product.variants?.[0];
  return {
    id: product.id + '',
    sku: firstVariant?.sku || '',
    name: product.name?.es || '',
    url: product.canonical_url || '',
    description: product.description?.es || '',
    imgs: product.images.map(img => ({
      url: img.src
    })),
    firstCategory: product.categories?.[0]?.name?.es || '',
    category: (product.categories || []).map(cat => cat.name?.es || '').join('|') || '',
    color: firstVariant?.values?.[0]?.es || '',
    brand: product.brand || '',
    price: parseFloat(firstVariant?.promotional_price || firstVariant?.price || '0'),
    compare_at_price: parseFloat(firstVariant?.compare_at_price || '0'),
    regular_price: parseFloat(firstVariant?.price || '0'),
    sku_list: product.variants?.map(variant => variant.id + '') || []
  };
}
```

Qué hace el default, en palabras: un item por producto, con `id` = ID del producto como string. Cosas a tener en cuenta:

* Todos los precios salen de la **primera variante**. `price` es el precio promocional si existe, si no el precio normal.
* `color` es en realidad **el valor del primer atributo** de la primera variante. Si tu tienda define los atributos como Talle y Color, en ese orden, ahí va a quedar el talle.
* Los textos usan el idioma `es`. Si tu tienda está en portugués (`pt`) o inglés (`en`), el default devuelve `""` en nombre, descripción y categorías.
* No incluye stock, tags, `handle`, ni los atributos por nombre.

***

## 3. De dónde salen los datos

GoPersonal consulta la **API v1 de Tiendanube** con el token que autorizaste al instalar la app de GoPersonal en tu tienda:

```
GET https://api.tiendanube.com/v1/{store_id}/products?page=1&per_page=50&published=true
Authentication: bearer {access_token}
```

* Recorre el catálogo de a 50 productos.
* **Solo trae productos publicados.** Un producto despublicado no llega a la función y queda inactivo en GoPersonal en la siguiente sincronización.
* Trae todas las variantes de cada producto, visibles o no, con o sin stock.
* El producto llega a tu función **exactamente como lo devuelve la API**, sin ninguna transformación de GoPersonal.

Para ver un producto real, si tenés un access token propio de la API de Tiendanube:

```bash
curl -s "https://api.tiendanube.com/v1/$STORE_ID/products?per_page=1&published=true" \
  -H "Authentication: bearer $ACCESS_TOKEN" \
  -H "User-Agent: MiTienda (contacto@mitienda.com)" | jq '.[0]'
```

Si no tenés un token, usá el ejemplo de la sección 4.5, que sigue la referencia oficial del recurso Product: <https://tiendanube.github.io/api-documentation/resources/product>.

***

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

Convención de textos: todos los campos de texto traducibles son objetos por idioma, `{ "es": "...", "pt": "...", "en": "..." }`, con solo los idiomas que tu tienda tiene activos. La columna "Default" marca qué usa la función por defecto.

### 4.1 Nivel producto

| Campo               | Tipo                     | Descripción                                                                                                                                           | Default                     |
| ------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| `id`                | number                   | ID del producto. Convertilo a string para `id`                                                                                                        | `id`                        |
| `name`              | `{es?, pt?, en?}`        | Nombre por idioma                                                                                                                                     | `name` (`.es`)              |
| `description`       | `{es?, pt?, en?}`        | Descripción en HTML                                                                                                                                   | `description` (`.es`)       |
| `handle`            | `{es?, pt?, en?}`        | Slug de la URL por idioma                                                                                                                             |                             |
| `attributes`        | `Array<{es?, pt?, en?}>` | Nombres de los atributos de variante, en orden. Ejemplo: `[{es:"Talle"},{es:"Color"}]`. **Define qué significa cada posición de `variants[].values`** |                             |
| `published`         | boolean                  | Siempre `true` (solo llegan productos publicados)                                                                                                     |                             |
| `free_shipping`     | boolean                  | Envío gratis                                                                                                                                          |                             |
| `requires_shipping` | boolean                  | Producto físico                                                                                                                                       |                             |
| `canonical_url`     | string                   | URL completa del producto en la tienda                                                                                                                | `url`                       |
| `video_url`         | string \| null           |                                                                                                                                                       |                             |
| `seo_title`         | `{es?, ...}`             |                                                                                                                                                       |                             |
| `seo_description`   | `{es?, ...}`             |                                                                                                                                                       |                             |
| `brand`             | string \| null           | Marca (texto libre)                                                                                                                                   | `brand`                     |
| `tags`              | string                   | Tags separados por coma, por ejemplo `"verano, oferta"`                                                                                               |                             |
| `created_at`        | string                   | Fecha ISO 8601                                                                                                                                        |                             |
| `updated_at`        | string                   | Fecha ISO 8601                                                                                                                                        |                             |
| `variants`          | `Array<Variant>`         | Siempre al menos una. Un producto sin variantes tiene una única variante con `values: []`                                                             | ver 4.2                     |
| `images`            | `Array<Image>`           | Ordenadas por `position`. Puede ser `[]`                                                                                                              | `imgs`                      |
| `categories`        | `Array<Category>`        | Todas las categorías a las que pertenece el producto, no solo la más específica                                                                       | `category`, `firstCategory` |

### 4.2 Variantes: `variants[]`

| Campo                                | Tipo                                          | Descripción                                                                                                                                | Default                                        |
| ------------------------------------ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |
| `id`                                 | number                                        | ID de la variante. Es el ID que usan los eventos de carrito y compra de la tienda                                                          | `sku_list`                                     |
| `product_id`                         | number                                        |                                                                                                                                            |                                                |
| `image_id`                           | number \| null                                | ID en `images[]` de la foto de esta variante                                                                                               |                                                |
| `position`                           | number                                        | Orden                                                                                                                                      |                                                |
| `price`                              | string                                        | Precio de lista, por ejemplo `"1500.00"`. **Es texto**, hay que usar `parseFloat`                                                          | `regular_price`, `price` (si no hay promoción) |
| `promotional_price`                  | string \| null                                | Precio en promoción. `null` si no hay                                                                                                      | `price`                                        |
| `compare_at_price`                   | string \| null                                | Precio "antes", tachado                                                                                                                    | `compare_at_price`                             |
| `cost`                               | string \| null                                | Costo interno                                                                                                                              |                                                |
| `sku`                                | string \| null                                | Código SKU cargado en la tienda                                                                                                            | `sku` (solo el de la primera variante)         |
| `barcode`                            | string \| null                                |                                                                                                                                            |                                                |
| `mpn`                                | string \| null                                |                                                                                                                                            |                                                |
| `values`                             | `Array<{es?, pt?, en?}>`                      | Valores de atributo, en el mismo orden que `product.attributes`. Ejemplo: `[{es:"M"},{es:"Azul"}]`. `[]` si el producto no tiene variantes | `color` (`values[0].es`)                       |
| `stock_management`                   | boolean                                       | `false` = stock ilimitado                                                                                                                  |                                                |
| `stock`                              | number \| null                                | Stock total. `null` cuando no se gestiona stock                                                                                            |                                                |
| `inventory_levels`                   | `Array<{id, variant_id, location_id, stock}>` | Stock por sucursal o depósito                                                                                                              |                                                |
| `visible`                            | boolean                                       | Si la variante se muestra en la tienda                                                                                                     |                                                |
| `weight`, `width`, `height`, `depth` | string                                        | Dimensiones, como texto decimal                                                                                                            |                                                |
| `age_group`, `gender`                | string \| null                                | Datos para Google Shopping                                                                                                                 |                                                |
| `created_at`, `updated_at`           | string                                        | Fechas ISO 8601                                                                                                                            |                                                |

### 4.3 Imágenes: `images[]`

| Campo                      | Tipo                        | Descripción                        | Default      |
| -------------------------- | --------------------------- | ---------------------------------- | ------------ |
| `id`                       | number                      | Se cruza con `variants[].image_id` |              |
| `product_id`               | number                      |                                    |              |
| `src`                      | string                      | URL absoluta de la imagen          | `imgs[].url` |
| `position`                 | number                      | Orden                              |              |
| `alt`                      | `Array<{es?, ...}>` \| `[]` | Texto alternativo                  |              |
| `created_at`, `updated_at` | string                      |                                    |              |

### 4.4 Categorías: `categories[]`

| Campo                          | Tipo              | Descripción                                 | Default                     |
| ------------------------------ | ----------------- | ------------------------------------------- | --------------------------- |
| `id`                           | number            |                                             |                             |
| `name`                         | `{es?, pt?, en?}` |                                             | `category`, `firstCategory` |
| `description`                  | `{es?, ...}`      |                                             |                             |
| `handle`                       | `{es?, ...}`      | Slug                                        |                             |
| `parent`                       | number \| null    | ID de la categoría padre. `null` si es raíz |                             |
| `subcategories`                | number\[]         | IDs de las categorías hijas                 |                             |
| `seo_title`, `seo_description` | `{es?, ...}`      |                                             |                             |
| `google_shopping_category`     | string \| null    |                                             |                             |
| `created_at`, `updated_at`     | string            |                                             |                             |

### 4.5 Ejemplo de `product`

```json
{
  "id": 123456789,
  "name": { "es": "Remera Básica" },
  "description": { "es": "<p>Remera de algodón.</p>" },
  "handle": { "es": "remera-basica" },
  "attributes": [{ "es": "Talle" }, { "es": "Color" }],
  "published": true,
  "free_shipping": false,
  "requires_shipping": true,
  "canonical_url": "https://www.tienda.com/productos/remera-basica/",
  "video_url": null,
  "seo_title": { "es": "" },
  "seo_description": { "es": "" },
  "brand": "Acme",
  "tags": "verano, basicos",
  "created_at": "2025-03-01T12:00:00+0000",
  "updated_at": "2026-08-30T09:15:00+0000",
  "variants": [
    {
      "id": 987654321,
      "image_id": 55501,
      "product_id": 123456789,
      "position": 1,
      "price": "12990.00",
      "compare_at_price": "12990.00",
      "promotional_price": "9990.00",
      "stock_management": true,
      "stock": 4,
      "weight": "0.300",
      "width": "0.00",
      "height": "0.00",
      "depth": "0.00",
      "sku": "REM-AZ-M",
      "values": [{ "es": "M" }, { "es": "Azul" }],
      "barcode": null,
      "mpn": null,
      "age_group": null,
      "gender": null,
      "cost": null,
      "visible": true,
      "inventory_levels": [{ "id": 1, "variant_id": 987654321, "location_id": "01ABC", "stock": 4 }],
      "created_at": "2025-03-01T12:00:00+0000",
      "updated_at": "2026-08-30T09:15:00+0000"
    }
  ],
  "images": [
    { "id": 55501, "product_id": 123456789, "src": "https://d2r9epyceweg5n.cloudfront.net/stores/001/234/products/remera-azul.jpg", "position": 1, "alt": [], "created_at": "2025-03-01T12:00:00+0000", "updated_at": "2025-03-01T12:00:00+0000" }
  ],
  "categories": [
    { "id": 10, "name": { "es": "Ropa" }, "description": { "es": "" }, "handle": { "es": "ropa" }, "parent": null, "subcategories": [11], "seo_title": { "es": "" }, "seo_description": { "es": "" }, "google_shopping_category": null, "created_at": "2025-01-01T00:00:00+0000", "updated_at": "2025-01-01T00:00:00+0000" },
    { "id": 11, "name": { "es": "Remeras" }, "description": { "es": "" }, "handle": { "es": "remeras" }, "parent": 10, "subcategories": [], "seo_title": { "es": "" }, "seo_description": { "es": "" }, "google_shopping_category": null, "created_at": "2025-01-01T00:00:00+0000", "updated_at": "2025-01-01T00:00:00+0000" }
  ]
}
```

Con la función por defecto, ese producto se guarda como:

```json
{
  "id": "123456789",
  "sku": "REM-AZ-M",
  "name": "Remera Básica",
  "url": "https://www.tienda.com/productos/remera-basica/",
  "description": "<p>Remera de algodón.</p>",
  "imgs": [{ "url": "https://d2r9epyceweg5n.cloudfront.net/stores/001/234/products/remera-azul.jpg" }],
  "firstCategory": "Ropa",
  "category": "Ropa|Remeras",
  "color": "M",
  "brand": "Acme",
  "price": 9990,
  "compare_at_price": 12990,
  "regular_price": 12990,
  "sku_list": ["987654321"],
  "active": 1
}
```

Fijate que `color` quedó `"M"` porque el primer atributo de la tienda es Talle.

***

## 5. Qué tiene que devolver

El objeto que devuelve `parse` 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`                    | Se fuerza siempre a `1` en Tiendanube (ver sección 2)                                                                                                                                                    |
| `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, por ejemplo `"23% off"`                                                                                                                                        |
| `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 cambiás la estrategia, se generan items duplicados: los viejos quedan inactivos pero no se borran.
* **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.
* Los precios de la API son texto: convertilos con `parseFloat` y cubrí el caso `null`.

***

## 6. Ejemplo de función personalizada

Multi-idioma, atributos por nombre (sin depender del orden), precio más bajo entre variantes, stock total, categoría más específica y tags como lista:

```js
function parse(product) {
  const t = (obj) => (obj && (obj.es || obj.pt || obj.en)) || '';
  const variants = product.variants || [];
  const attrs = (product.attributes || []).map(t);
  const attrOf = (variant, name) => {
    const i = attrs.findIndex(a => a.toLowerCase() === name.toLowerCase());
    return i >= 0 && variant.values && variant.values[i] ? t(variant.values[i]) : '';
  };
  const num = (v) => { const n = parseFloat(v); return isNaN(n) ? null : n; };

  const withPrice = variants.filter(v => num(v.price) !== null);
  const sellPrice = (v) => num(v.promotional_price) ?? num(v.price);
  const cheapest = withPrice.reduce((best, v) => (!best || sellPrice(v) < sellPrice(best)) ? v : best, null);
  const first = variants[0] || {};

  const categories = product.categories || [];
  const leaf = categories.find(c => !c.subcategories || c.subcategories.length === 0) || categories[categories.length - 1];
  const totalStock = variants.reduce((s, v) => s + (v.stock === null ? 9999 : (v.stock || 0)), 0);

  return {
    id: product.id + '',
    sku: first.sku || '',
    name: t(product.name),
    description: t(product.description),
    url: product.canonical_url || '',
    imgs: (product.images || []).map(img => ({ url: img.src })),
    brand: product.brand || '',
    category: categories.map(c => t(c.name)).join('|'),
    category_ids: categories.map(c => c.id).join(','),
    leaf_category: leaf ? t(leaf.name) : '',
    tags: (product.tags || '').split(',').map(s => s.trim()).filter(Boolean),
    price: cheapest ? sellPrice(cheapest) : 0,
    regular_price: cheapest ? num(cheapest.price) : 0,
    compare_at_price: cheapest ? (num(cheapest.compare_at_price) || 0) : 0,
    talle: attrOf(first, 'Talle'),
    color: attrOf(first, 'Color'),
    sizes: variants.map(v => attrOf(v, 'Talle')).filter(Boolean),
    colors: [...new Set(variants.map(v => attrOf(v, 'Color')).filter(Boolean))],
    stock: totalStock,
    in_stock: totalStock > 0 ? 1 : 0,
    sku_list: variants.map(v => v.id + ''),
    free_shipping: !!product.free_shipping
  };
}
```

Como `active` no se puede poner en `0` desde la función, un campo propio como `in_stock` es la forma de excluir productos sin stock en filtros y recomendaciones.

***

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

Con Node.js instalado podés ejecutar tu función contra el ejemplo de la sección 4.5, o contra productos reales de tu tienda si tenés un access token, con la misma semántica que usa GoPersonal (sin `console` dentro de la función):

1. Guardá tu función en `parse.js`.
2. Guardá uno o más productos en `products.json` como un array: `[ { ...producto... } ]`.
3. Ejecutá:

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

Para probar con productos reales, reemplazá el archivo por una descarga de la API:

```bash
curl -s "https://api.tiendanube.com/v1/$STORE_ID/products?per_page=10&published=true" \
  -H "Authentication: bearer $ACCESS_TOKEN" \
  -H "User-Agent: MiTienda (contacto@mitienda.com)" > products.json
```

Revisá que cada objeto tenga `id` como string y los campos que esperás. Recién después pegá la función en la integración; la próxima sincronización la 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 Tiendanube de
GoPersonal. Es una función de JavaScript `function parse(product) { ... }` que recibe un
producto de la API de Tiendanube y devuelve el objeto a guardar como item.

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.5>
<pegar la sección 5 "Qué tiene que devolver">

Función actual:
<pegar la función que está hoy, o "ninguna, se usa el default">

Lo que necesito cambiar:
<describir en lenguaje natural. Ejemplo: "usar el idioma pt, poner talle y color en campos
separados buscándolos por nombre de atributo, y agregar el stock total">

Restricciones:
- JavaScript puro: sin require, sin fetch, sin async, sin acceso a red.
- Es `function parse(product) { ... }`. Devolvé siempre un objeto, nunca null ni un array.
- No uses console. No intentes poner active en 0 porque se reemplaza por 1.
- Defensivo ante campos faltantes: nunca tiene que tirar una excepción por un producto raro.
- Los precios de la API son texto; convertilos con parseFloat y cubrí el caso null.
- `id` siempre string y con la misma estrategia que la función 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. La función completa, lista para pegar, sin explicaciones dentro del código.
2. Una lista corta de qué cambió respecto a la función actual y qué campos nuevos aparecen.
3. El objeto de salida que genera para el producto de ejemplo de la sección 4.5.
```
