> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fire.rest/llms.txt
> Use this file to discover all available pages before exploring further.

# menu.updated v2

> Se emite cuando un menú es creado o actualizado y debe propagarse a sistemas externos.

Un menú en Fire es la definición completa del catálogo para una combinación específica de tienda y canal — incluye categorías, productos, grupos de modificadores y horarios. El payload es autocontenido y está listo para enviarse a sistemas externos. Trátalo como un upsert: crea el menú si no existe o reemplázalo completamente si ya existe.

## Payload

```json theme={null}
{
  "event": {
    "id": "evt_def456",
    "type": "menu.updated",
    "executionId": "exec_abc123",
    "createdAt": "2025-01-15T14:31:00.000Z"
  },
  "data": {
    "account": "1",
    "country": "EC",
    "groupId": "a3f7c2d1-84be-4e10-9b3a-2c5d6e7f8091",
    "menu": {
      "list": {
        "listId": "805-iFood-delivery",
        "listName": "iFood - Store 805",
        "vendorId": "100.6.1350",
        "stores": [
          {
            "storeId": "b3d2a1f0-6e21-4c3a-9f5d-7a8b9c0d1e2f",
            "storeName": "Laboratorio Brasil",
            "timezone": "America/Sao_Paulo",
            "channels": [
              {
                "channelId": "0E049503-85CF-E511-80C6-000D3A3261F3",
                "channelReferenceName": "iFood",
                "schedules": [
                  { "day": "MONDAY",    "startTime": "07:00", "endTime": "23:00" },
                  { "day": "TUESDAY",   "startTime": "07:00", "endTime": "23:00" },
                  { "day": "WEDNESDAY", "startTime": "07:00", "endTime": "23:00" },
                  { "day": "THURSDAY",  "startTime": "07:00", "endTime": "23:00" },
                  { "day": "FRIDAY",    "startTime": "07:00", "endTime": "23:30" },
                  { "day": "SATURDAY",  "startTime": "08:00", "endTime": "23:30" },
                  { "day": "SUNDAY",    "startTime": "08:00", "endTime": "22:00" }
                ]
              }
            ]
          },
          {
            "storeId": "c4e3b2a1-7f32-4d4b-8a6e-8b9c0d1e2f3a",
            "storeName": "Vila Olimpia",
            "timezone": "America/Sao_Paulo",
            "channels": [
              {
                "channelId": "0E049503-85CF-E511-80C6-000D3A3261F3",
                "channelReferenceName": "iFood",
                "schedules": []
              }
            ]
          }
        ]
      },
      "categories": [
        {
          "productCategoryId": "cat_001",
          "name": "Hamburguesas",
          "displayInList": true,
          "featured": false,
          "position": 1,
          "images": [
            {
              "imageCategoryId": "img_cat_001",
              "fileUrl": "https://cdn.example.com/categories/burgers.jpg"
            }
          ],
          "assignedAt": "2025-01-10T09:15:00Z",
          "productListing": [
            { "productId": "prod_001", "position": 1 }
          ],
          "schedules": [
            { "day": "MONDAY",    "startTime": "11:00", "endTime": "23:00" },
            { "day": "TUESDAY",   "startTime": "11:00", "endTime": "23:00" },
            { "day": "WEDNESDAY", "startTime": "11:00", "endTime": "23:00" },
            { "day": "THURSDAY",  "startTime": "11:00", "endTime": "23:00" },
            { "day": "FRIDAY",    "startTime": "11:00", "endTime": "23:30" },
            { "day": "SATURDAY",  "startTime": "11:00", "endTime": "23:30" },
            { "day": "SUNDAY",    "startTime": "11:00", "endTime": "22:00" }
          ]
        },
        {
          "productCategoryId": "cat_002",
          "name": "Desayunos",
          "displayInList": true,
          "featured": false,
          "position": 2,
          "images": [],
          "assignedAt": "2025-02-03T16:40:00Z",
          "productListing": [
            { "productId": "prod_002", "position": 1 }
          ],
          "schedules": null
        }
      ],
      "products": [
        {
          "productId": "prod_001",
          "name": "Hamburguesa Clásica",
          "description": "Medallón de res, lechuga, tomate, pepinillos",
          "active": true,
          "type": "PRODUCTO",
          "priceInfo": {
            "pointPrice": 0,
            "price": 1000,
            "referencePrice": 1200,
            "suggestedPrice": 1200
          },
          "productModifiers": [
            {
              "modifierId": "mod_001",
              "position": 1,
              "overrides": [
                {
                  "productId": "prod_size_small",
                  "priceInfo": { "price": 800 }
                }
              ]
            }
          ],
          "schedules": [
            { "day": "MONDAY",    "startTime": "11:00", "endTime": "23:00" },
            { "day": "TUESDAY",   "startTime": "11:00", "endTime": "23:00" },
            { "day": "WEDNESDAY", "startTime": "11:00", "endTime": "23:00" },
            { "day": "THURSDAY",  "startTime": "11:00", "endTime": "23:00" },
            { "day": "FRIDAY",    "startTime": "11:00", "endTime": "23:30" },
            { "day": "SATURDAY",  "startTime": "11:00", "endTime": "23:30" },
            { "day": "SUNDAY",    "startTime": "11:00", "endTime": "22:00" }
          ],
          "images": [
            {
              "imageCategoryId": "img_prod_001",
              "fileUrl": "https://cdn.example.com/products/classic-burger.jpg"
            }
          ],
          "taxInfo": [
            { "vatRatePercentage": 12 }
          ],
          "additionalInfo": {
            "externalCode": "11019#23211#231",
            "ncm": "21.00.21.32",
            "assignedAt": "2025-01-10T09:15:00Z"
          }
        },
        {
          "productId": "prod_002",
          "name": "Panqueques",
          "description": "Panqueques esponjosos con jarabe de maple",
          "active": true,
          "type": "PRODUCTO",
          "priceInfo": {
            "pointPrice": 0,
            "price": 800,
            "referencePrice": 800,
            "suggestedPrice": 800
          },
          "productModifiers": [],
          "schedules": null,
          "images": [],
          "taxInfo": [
            { "vatRatePercentage": 12 }
          ],
          "additionalInfo": {
            "externalCode": "11019",
            "ncm": "19.05.90.90",
            "assignedAt": "2025-02-03T16:40:00Z"
          }
        },
        {
          "productId": "prod_size_small",
          "name": "Pequeño",
          "description": "Tamaño pequeño",
          "active": true,
          "type": "MODIFIER",
          "priceInfo": {
            "pointPrice": 0,
            "price": 0,
            "referencePrice": 0,
            "suggestedPrice": 0
          },
          "productModifiers": [],
          "schedules": null,
          "images": [],
          "additionalInfo": {
            "externalCode": "11020",
            "ncm": "21.00.21.32"
          }
        },
        {
          "productId": "prod_size_large",
          "name": "Grande",
          "description": "Tamaño grande",
          "active": true,
          "type": "MODIFIER",
          "priceInfo": {
            "pointPrice": 0,
            "price": 200,
            "referencePrice": 200,
            "suggestedPrice": 200
          },
          "productModifiers": [],
          "schedules": null,
          "images": [],
          "additionalInfo": {
            "externalCode": "11021",
            "ncm": "21.00.21.32"
          }
        }
      ],
      "modifierGroups": [
        {
          "modifierId": "mod_001",
          "modifier": "Elige tu tamaño",
          "minOptions": 1,
          "maxOptions": 1,
          "type": "RADIO",
          "modifierOptions": [
            { "optionId": "opt_001", "productId": "prod_size_small", "name": "Pequeño", "position": 1 },
            { "optionId": "opt_002", "productId": "prod_size_large", "name": "Grande", "position": 2 }
          ]
        }
      ]
    }
  }
}
```

## Campos

### `data`

| Campo     | Tipo   | Descripción                                                                                                                                                |
| --------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account` | string | Identificador de la cuenta                                                                                                                                 |
| `country` | string | Código de país ISO 3166-1 alpha-2 (p. ej. `EC`, `BR`, `CO`) — requerido por sistemas externos                                                              |
| `groupId` | string | UUID que correlaciona eventos del mismo batch de publicación o sincronización. Varios eventos `menu.updated` emitidos juntos comparten el mismo `groupId`. |
| `menu`    | object | Definición completa del menú                                                                                                                               |

### `data.menu`

| Campo            | Tipo      | Descripción                                  |
| ---------------- | --------- | -------------------------------------------- |
| `list`           | object    | Metadatos del menú y asociación canal/tienda |
| `categories`     | object\[] | Categorías del menú                          |
| `products`       | object\[] | Catálogo de productos                        |
| `modifierGroups` | object\[] | Grupos de modificadores y opciones           |

### `data.menu.list`

| Campo      | Tipo             | Descripción                                                                                                                                                                                                                                                                                |
| ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `listId`   | string           | Identificador compuesto: `{storeNumber}-{channelCode}-{fulfillmentType}`                                                                                                                                                                                                                   |
| `listName` | string           | Etiqueta autogenerada `{channelCode} - Store {storeNumber}` (p. ej. `"iFood - Store 805"`). **No** es el nombre del menú ni el nombre operativo de la tienda. El nombre del menú viaja en `channels[n].listName` de [`product.price_updated`](/es/webhook-reference/product-price-updated) |
| `vendorId` | string \| number | Código de marca                                                                                                                                                                                                                                                                            |
| `stores`   | object\[]        | Tiendas a las que aplica este menú                                                                                                                                                                                                                                                         |

### `data.menu.list.stores[n]`

| Campo       | Tipo           | Descripción                                                                                                                                                                                                                                                                                                                                                                 |
| ----------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `storeId`   | string         | UUID interno de la tienda (PK `stores.id`) — **no** es el `store_number`. Misma convención que `targets[n].storeId` de [`product.price_updated`](/es/webhook-reference/product-price-updated) y [`product.availability_changed`](/es/webhook-reference/product-availability-changed). El `store_number` solo aparece dentro de `list.listId` y como fallback de `storeName` |
| `storeName` | string         | Nombre operativo de la tienda. Fallback: `"Store #{store_number}"`                                                                                                                                                                                                                                                                                                          |
| `timezone`  | string \| null | Zona horaria IANA de la tienda (p. ej. `America/Sao_Paulo`). `null` si no está configurada                                                                                                                                                                                                                                                                                  |
| `channels`  | object\[]      | Canales de venta en los que se publica esta tienda                                                                                                                                                                                                                                                                                                                          |

### `data.menu.list.stores[n].channels[n]`

| Campo                  | Tipo      | Descripción                                                                                                                                                                                                                                                                      |
| ---------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channelId`            | string    | UUID interno del canal de ventas (PK `channels.id`) — **no** es el `channel_id` externo del agregador. Misma convención que `targets[n].channels[n].channelId` de los eventos de producto                                                                                        |
| `channelReferenceName` | string    | Nombre de referencia del **fulfillment** (p. ej. `delivery`, `pickup`) — no del canal de ventas. ⚠️ No forma par con `channelId`, que sí es del canal de ventas: en los eventos de producto (`targets[n].channels[n]`) `channelReferenceName` es el **canal** (`iFood`, `Rappi`) |
| `schedules`            | object\[] | Ventanas de tiempo en las que este canal está activo para esta tienda. Un array vacío (`[]`) indica que el canal opera 24 horas.                                                                                                                                                 |

### `data.menu.list.stores[n].channels[n].schedules[n]`

| Campo       | Tipo   | Descripción                                                                                    |
| ----------- | ------ | ---------------------------------------------------------------------------------------------- |
| `day`       | string | Día de la semana: `MONDAY`, `TUESDAY`, `WEDNESDAY`, `THURSDAY`, `FRIDAY`, `SATURDAY`, `SUNDAY` |
| `startTime` | string | Hora de apertura en formato `HH:mm`                                                            |
| `endTime`   | string | Hora de cierre en formato `HH:mm`                                                              |

### `data.menu.categories[n]`

| Campo               | Tipo              | Descripción                                                                                                                     |
| ------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `productCategoryId` | string            | Identificador de categoría                                                                                                      |
| `name`              | string            | Nombre para mostrar                                                                                                             |
| `displayInList`     | boolean           | Si la categoría es visible                                                                                                      |
| `featured`          | boolean           | Si la categoría está destacada                                                                                                  |
| `position`          | number            | Orden de visualización                                                                                                          |
| `images`            | object\[]         | Imágenes de la categoría — `{ imageCategoryId, fileUrl }`                                                                       |
| `assignedAt`        | string \| null    | Fecha en que la categoría entró al menú, en ISO 8601 UTC. Ver [Fecha de incorporación al menú](#fecha-de-incorporación-al-menú) |
| `productListing`    | object\[]         | Productos de esta categoría con sus posiciones — `{ productId, position }`                                                      |
| `schedules`         | object\[] \| null | Horario propio de la categoría. Siempre presente; `null` si no hay horario configurado                                          |

### `data.menu.products[n]`

| Campo                         | Tipo              | Descripción                                                                                                                                                                                                    |
| ----------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `productId`                   | string \| number  | Identificador del producto (`externalId` o UUID)                                                                                                                                                               |
| `name`                        | string            | Nombre del producto                                                                                                                                                                                            |
| `description`                 | string            | Descripción del producto                                                                                                                                                                                       |
| `active`                      | boolean           | Visibilidad en el menú (`visible`); no refleja stock ni disponibilidad operativa                                                                                                                               |
| `type`                        | string            | Tipo de ítem: `PRODUCTO`, `MODIFIER`, `COMPLEMENT`, `COMBO`                                                                                                                                                    |
| `priceInfo`                   | object            | Precios resueltos del producto                                                                                                                                                                                 |
| `priceInfo.pointPrice`        | number            | Precio en puntos                                                                                                                                                                                               |
| `priceInfo.price`             | number            | Precio resuelto (`resolved_price` / `final_price`; `0` para combos)                                                                                                                                            |
| `priceInfo.referencePrice`    | number            | Precio de referencia cuando aplica en catálogo                                                                                                                                                                 |
| `priceInfo.suggestedPrice`    | number            | Precio sugerido cuando aplica en catálogo                                                                                                                                                                      |
| `productModifiers`            | object\[]         | Referencias de grupos de modificadores — `{ modifierId, position, overrides? }`                                                                                                                                |
| `schedules`                   | object\[] \| null | Horario custom del producto. Siempre presente en todos los ítems de `products[]` (incl. stubs de opciones modifier); `null` si usa horario de tienda o no tiene horario custom                                 |
| `images`                      | object\[]         | Imágenes del producto                                                                                                                                                                                          |
| `taxInfo`                     | object\[]         | Información fiscal — `{ vatRatePercentage }`                                                                                                                                                                   |
| `upselling`                   | string            | Producto sugerido para upselling (opcional)                                                                                                                                                                    |
| `additionalInfo`              | object            | Metadata adicional opcional del producto                                                                                                                                                                       |
| `additionalInfo.externalCode` | string            | Código externo que identifica el producto en un sistema de terceros. Puede ser un código simple (p. ej. `11019`) o una clave compuesta con `#` como separador (p. ej. `11019#23211#231`).                      |
| `additionalInfo.ncm`          | string            | Código de clasificación fiscal NCM (Nomenclatura Común del Mercosur, p. ej. `21.00.21.32`).                                                                                                                    |
| `additionalInfo.assignedAt`   | string \| null    | Fecha en que el producto entró al menú, en ISO 8601 UTC. Ausente en los ítems que solo existen como opción de un grupo de modificadores. Ver [Fecha de incorporación al menú](#fecha-de-incorporación-al-menú) |

### `data.menu.products[n].productModifiers[n].overrides[n]`

| Campo             | Tipo   | Descripción                                                                                                                                                                                                                                    |
| ----------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `productId`       | string | Opción del modificador a la que aplica este override                                                                                                                                                                                           |
| `priceInfo`       | object | Override de precio para esta opción específica en este producto y menú                                                                                                                                                                         |
| `priceInfo.price` | number | **Único campo.** El override es un precio puntual y contextual: el resto de los precios de la opción (referencia, sugerido, puntos) no tienen valor propio dentro de un producto concreto — se leen de la opción como producto en `products[]` |

### `data.menu.modifierGroups[n]`

| Campo             | Tipo      | Descripción                                                                   |
| ----------------- | --------- | ----------------------------------------------------------------------------- |
| `modifierId`      | string    | Identificador del grupo de modificadores                                      |
| `modifier`        | string    | Nombre para mostrar del grupo                                                 |
| `minOptions`      | number    | Número mínimo de selecciones requeridas                                       |
| `maxOptions`      | number    | Número máximo de selecciones permitidas                                       |
| `type`            | string    | Tipo de selección: `RADIO` (single) o `CHECKBOX` (multiple)                   |
| `modifierOptions` | object\[] | Opciones individuales del grupo — cada `productId` debe existir en `products` |

### `data.menu.modifierGroups[n].modifierOptions[n]`

| Campo       | Tipo   | Descripción                     |
| ----------- | ------ | ------------------------------- |
| `optionId`  | string | Identificador de la opción      |
| `productId` | string | Producto usado como esta opción |
| `name`      | string | Nombre de la opción             |
| `position`  | number | Orden de visualización          |

## Fecha de incorporación al menú

`categories[n].assignedAt` y `products[n].additionalInfo.assignedAt` indican **cuándo la entidad entró al menú**. Es un dato de **membresía**, no de edición: no cambia al editar el precio, el nombre, la descripción, la imagen, los modificadores, el orden ni la visibilidad.

**Formato:** ISO 8601 con precisión de **segundos** y sufijo `Z` — `"2026-08-04T12:30:00Z"`. Siempre en **UTC**, nunca en la zona de la tienda: el valor es el mismo hecho para todas las tiendas del menú, que pueden estar en países distintos. Para mostrarlo en hora local usa el `timezone` que viaja por tienda en `list.stores[n]`. Sin milisegundos — a diferencia de `event.createdAt`, que sí los lleva.

| Situación                                                                        | Valor                                                                                                                       |
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Producto o categoría agregado al menú                                            | Fecha del alta                                                                                                              |
| Cualquier edición posterior (precio, nombre, imagen, orden, `active`)            | Sin cambios                                                                                                                 |
| Producto quitado del menú y vuelto a agregar                                     | Fecha **nueva** — el campo describe la membresía **vigente**, no la primera de la historia                                  |
| Producto presente en varias categorías del mismo menú                            | La **más antigua** de sus altas (en `products[n]` va un solo valor, aunque el producto aparezca en varios `productListing`) |
| Producto o categoría heredado de un menú padre                                   | La fecha del **padre**, no la del menú hijo                                                                                 |
| Ítem que solo existe como opción de un grupo de modificadores (`type: MODIFIER`) | Ausente — no es miembro del menú por sí mismo                                                                               |
| Categoría que se quedó sin productos y luego vuelve a tenerlos                   | Fecha nueva — la categoría se recrea                                                                                        |
| Menú publicado antes de que existiera el campo                                   | Ausente hasta el primer re-publish de esa tienda/canal                                                                      |

No existe `assignedAt` a nivel `list` ni `list.stores[n]`: el campo describe cuándo la entidad entró al **menú**, no cuándo una tienda empezó a recibirlo.

El mismo campo, con la misma semántica, viaja en [`product.updated`](/es/webhook-reference/product-updated). **No** viaja en [`product.price_updated`](/es/webhook-reference/product-price-updated) ni en [`product.availability_changed`](/es/webhook-reference/product-availability-changed): esos eventos transmiten solo su delta, y el `assignedAt` que ya tengas registrado sigue vigente.

## Notas

* El payload es un **menú completo** — no un diff. Reemplaza el menú completo en el sistema externo.
* Cada `menu.modifierGroups[n].modifierOptions[n].productId` debe referenciar un producto definido en `menu.products`.
* **`stores[n].channels[n].schedules`:** ventanas de tiempo en las que el canal está activo para esa tienda. Un array vacío (`[]`) indica que el canal opera 24 horas — sin restricciones.
* **`categories[].schedules`:** horario propio de la categoría si existe; `null` si no hay.
* **`products[].schedules`:** solo cuando el producto tiene schedule `mode: custom`; `null` si usa horario de tienda o no tiene horario custom.

## Eliminar un menú externamente

Fire no emite un evento de borrado separado para menús. Para eliminar un menú de un sistema externo, Fire envía un evento `menu.updated` con `menu.categories`, `menu.products` y `menu.modifierGroups` como arrays vacíos. Tu sistema debe interpretar un menú vacío como señal para desactivar o eliminar el menú externamente.

Este vaciado quita a categorías y productos del menú. Si el menú vuelve a publicarse, se reincorporan: llegan con `assignedAt` **nuevo**, no con su fecha original — misma regla que "producto quitado del menú y vuelto a agregar".

```json theme={null}
{
  "event": {
    "id": "evt_def459",
    "type": "menu.updated",
    "executionId": "exec_abc124",
    "createdAt": "2025-01-15T14:31:00.000Z"
  },
  "data": {
    "account": "1",
    "country": "EC",
    "groupId": "a3f7c2d1-84be-4e10-9b3a-2c5d6e7f8091",
    "menu": {
      "list": { "..." : "..." },
      "categories": [],
      "products": [],
      "modifierGroups": []
    }
  }
}
```

## Uso

Consulta la guía [Publicación de menú](/es/guides/menu-publication) para el flujo completo de procesamiento.
