Saltar al contenido
Clientes y ventas

Promociones y cupones

Crea un cupón y un descuento automático, y aplícalos en el carrito.

Crea el cupón BIENVENIDA10 con 10 % de descuento desde S/ 100 y un envío gratis desde S/ 150. Luego aplica el cupón en el carrito de tu tienda headless.

Requisitos

  • Un token de Admin en ECOMIQ_TOKEN, con los permisos promotion:create, promotion:update y promotion:read. Ver Inicio rápido.
  • Un carrito en tu tienda headless. Ver Tienda headless.

Cupón o descuento automático

  • Descuento automático (/v1/discounts): se aplica solo cuando el carrito cumple sus condiciones. Por ejemplo, envío gratis desde S/ 150.
  • Cupón (/v1/coupons): se aplica cuando el comprador escribe uno de sus códigos. Por ejemplo, BIENVENIDA10.

Los dos se configuran igual. type y target dicen qué descuentan:

Para dartypetargetamount
10 % en todo el pedidopercentageorder10
S/ 20 menos en ciertos productosfixedAmountitem20
50 % del envíopercentageshipping50
Envío gratisfreeShippingshipping0
3x2 en polosbuyXGetYitemNo va.

Con order, el descuento se reparte entre todos los artículos. Con item, cubre solo los productos de sus condiciones. GET /v1/promotions lista cupones y descuentos juntos.

Crea el cupón

Este cupón da 10 % en compras desde S/ 100, una vez por cliente y hasta 1000 usos en total.

curl -X POST https://api.ecomiq.pe/api/v1/coupons \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Bienvenida 10 %",
    "status": "active",
    "type": "percentage",
    "target": "order",
    "amount": 10,
    "validFrom": "2026-10-01T05:00:00Z",
    "validTo": "2027-01-01T04:59:59Z",
    "usageLimit": 1000,
    "customerUsageLimit": 1,
    "combineWithOtherDiscounts": true,
    "rules": [
      { "field": "cartSubtotal", "operator": "gte", "decimalValues": [100] }
    ],
    "codes": ["BIENVENIDA10"]
  }'
Respuesta 201
{
  "id": "7204558912004325430"
}
CampoQué hace
statusactive lo habilita. Sin él, queda en borrador y no se aplica.
validFrom, validToDesde y hasta cuándo vale, en UTC. 05:00Z es medianoche en Lima.
rulesLas condiciones del carrito.
usageLimitUsos en total, sumando todos sus códigos.
customerUsageLimitUsos por cliente.
combineWithOtherDiscountsSi se suma a otra promoción.
codesHasta 500 códigos de hasta 100 caracteres. Se guardan en mayúsculas.
codeUsageLimitUsos de cada código.

Un código no se repite en la tienda. Para programar un cupón, envía active con un validFrom futuro.

Crea el descuento automático

Este descuento deja el envío en cero cuando el subtotal llega a S/ 150.

curl -X POST https://api.ecomiq.pe/api/v1/discounts \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Envío gratis desde S/ 150",
    "status": "active",
    "type": "freeShipping",
    "target": "shipping",
    "amount": 0,
    "combineWithOtherDiscounts": true,
    "rules": [
      { "field": "cartSubtotal", "operator": "gte", "decimalValues": [150] }
    ]
  }'
Respuesta 201
{
  "id": "7204558912004325431"
}

Condiciones

La promoción se aplica solo si el carrito cumple todas las reglas de rules.

Para exigirRegla
Un monto mínimo{ "field": "cartSubtotal", "operator": "gte", "decimalValues": [150] }
Unidades mínimas{ "field": "cartTotalQuantity", "operator": "gte", "integerValues": [3] }
Ciertos productos{ "field": "productId", "operator": "in", "resourceIds": ["7204558912004325302"] }
Ciertos clientes{ "field": "customerId", "operator": "in", "resourceIds": ["7204558912004325301"] }
Un grupo de clientes{ "field": "customerGroupId", "operator": "in", "resourceIds": ["7204558912004325310"] }
Un método de pago{ "field": "paymentMethodCode", "operator": "in", "codes": ["yape"] }
  • variantId, categoryId, brandId y collectionId funcionan igual que productId, con eq, neq, in y notIn.
  • Montos y cantidades aceptan eq, neq, gt, gte, lt, in y notIn.
  • customerId, customerGroupId y paymentMethodCode aceptan solo in, una regla de cada uno por promoción. Una promoción se limita a clientes o a grupos, no a ambos.
  • in y notIn aceptan hasta 500 valores.
  • El subtotal se mide antes de descuentos.

Con target: "order", una regla de producto solo pide que el producto esté en el carrito. Con target: "item", el descuento cubre solo esos productos, y maxQuantity limita cuántas unidades.

Un cupón con regla de cliente o de grupo solo funciona en un carrito creado con la sesión de ese cliente. Ver Cuentas de clientes.

Compra X, lleva Y

Para un 3x2, usa type: "buyXGetY" y el objeto buyXGetY. Con este cuerpo, de cada tres Polos de algodón, el más barato sale gratis:

Cuerpo de POST /v1/discounts
{
  "name": "3x2 en polos",
  "status": "active",
  "type": "buyXGetY",
  "target": "item",
  "buyXGetY": {
    "buyQuantity": 2,
    "buyRules": [
      {
        "field": "productId",
        "operator": "in",
        "resourceIds": ["7204558912004325302"]
      }
    ],
    "getQuantity": 1,
    "getRules": [
      {
        "field": "productId",
        "operator": "in",
        "resourceIds": ["7204558912004325302"]
      }
    ],
    "discountType": "percentage",
    "discountAmount": 100
  }
}
CampoQué define
buyQuantityUnidades que debe comprar. Para pedir un monto, usa buyAmount.
buyRulesQué productos cuentan para la compra.
getQuantityUnidades que reciben el descuento.
getRulesQué productos reciben el descuento.
discountType, discountAmountEl descuento de cada unidad. 100 % la deja gratis.
maxApplicationsPerOrderVeces que se aplica por pedido. Sin él, no hay tope.

buyRules y getRules solo aceptan IDs. Una unidad de regalo no cuenta para la compra.

Decide si se suman

Si un carrito cumple varias promociones, primero se aplica el cupón y luego los descuentos automáticos, del más reciente al más antiguo. Dos promociones se suman solo si ambas tienen combineWithOtherDiscounts: true.

En este ejemplo las dos lo tienen: un comprador con BIENVENIDA10 y S/ 150 en el carrito recibe el 10 % y el envío gratis. Con false en el cupón, solo recibiría el 10 %. Un artículo nunca baja de cero.

Para editar, envía todo

PUT /v1/coupons/{couponId} y PUT /v1/discounts/{discountId} reemplazan la promoción. Si omites status, vuelve a borrador; si omites una fecha o un límite, se borra. Solo rules, codes, maxQuantity y buyXGetY se conservan si no los envías.

Crea más códigos

Un cupón puede tener muchos códigos, por ejemplo uno de un solo uso por comprador. Tú generas los códigos.

CuántosOperaciónRespuesta
UnoPOST /v1/coupons/{couponId}/codes201 con el código.
Hasta 100POST /v1/coupons/{couponId}/codes/batch200 con un resultado por código.
Más de 100POST /v1/coupons/{couponId}/codes/imports202 con una operación.

usageLimit es opcional; con 1, el código sirve una sola vez:

curl -X POST https://api.ecomiq.pe/api/v1/coupons/7204558912004325430/codes \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "code": "BIENVENIDA-ANDREA", "usageLimit": 1 }'
Respuesta 201
{
  "id": "7204558912004325432",
  "code": "BIENVENIDA-ANDREA",
  "usageLimit": 1,
  "usageCount": 0,
  "createdOn": "2026-10-05T15:30:00Z",
  "updatedOn": null
}

Hasta 100 códigos

curl -X POST https://api.ecomiq.pe/api/v1/coupons/7204558912004325430/codes/batch \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: bienvenida-lote-001" \
  -d '{
    "codes": ["BIENVENIDA-7K2P9Q", "BIENVENIDA-M4XH2D", "BIENVENIDA-Q8ZR6T"],
    "usageLimit": 1
  }'
Respuesta 200
{
  "results": [
    { "index": 0, "id": "7204558912004325433", "status": "created", "errors": [] },
    { "index": 1, "id": "7204558912004325434", "status": "created", "errors": [] },
    {
      "index": 2,
      "id": null,
      "status": "failed",
      "errors": [
        {
          "field": "code",
          "code": "PromotionCode.Exists",
          "message": "Promotion code 'BIENVENIDA-Q8ZR6T' already exists."
        }
      ]
    }
  ]
}

Cada código se crea por separado: si uno falla, los demás siguen. results[] va en el orden que enviaste. Si pierdes la respuesta, reintenta con la misma Idempotency-Key. Con PromotionCode.Exists, genera otro código; con PromotionCode.InvalidCode, usa entre 1 y 100 caracteres.

Más de 100 códigos

Sube un CSV con la columna code y, si quieres, usageLimit:

codigos.csv
code,usageLimit
BIENVENIDA-7K2P9Q,1
BIENVENIDA-M4XH2D,1
BIENVENIDA-Q8ZR6T,1
curl -X POST https://api.ecomiq.pe/api/v1/coupons/7204558912004325430/codes/imports \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: text/csv" \
  -H "Idempotency-Key: bienvenida-archivo-001" \
  --data-binary @codigos.csv
Respuesta 202
{
  "id": "7204558912004325440",
  "status": "pending",
  "resource": "promotion-codes",
  "format": "csv",
  "createdOn": "2026-10-05T15:30:00Z"
}

Sigue la operación como en Importación de productos, donde están los formatos, los límites y cómo leer las filas rechazadas.

Aplica el código en el carrito

Envía el código que escribió el comprador, en mayúsculas o minúsculas. La llamada lleva x-Store-Domain y no usa token. cart es el carrito de Tienda headless.

curl -X POST https://storefront.ecomiq.pe/v1/carts/<cart-token>/promotion-codes \
  -H "x-Store-Domain: mi-tienda" \
  -H "Content-Type: application/json" \
  -d '{ "code": "bienvenida10" }'

El carrito tiene dos Polos de algodón a S/ 79. El 10 % de S/ 158 es S/ 15.80:

Respuesta 200 (extracto)
{
  "currencyCode": "PEN",
  "promotionCodes": ["BIENVENIDA10"],
  "items": [
    {
      "id": "7204558912004325450",
      "title": "Polo de algodón",
      "variantSku": "POL-001-AZ-M",
      "quantity": 2,
      "unitPrice": 79
    }
  ],
  "totals": {
    "subtotal": 158,
    "discountTotal": -15.8,
    "shippingOriginalTotal": 0,
    "shippingTotal": 0,
    "total": 142.2
  }
}
CampoQué muestra
promotionCodesLos códigos aplicados.
totals.discountTotalLo que descuentan, en negativo.
totals.shippingTotalEl envío con descuento. shippingOriginalTotal es el de antes.
totals.totalLo que paga el comprador.

Cada cambio del carrito recalcula los descuentos. El envío gratis aparece en shippingTotal cuando el comprador elige un envío.

Sin el mínimo no hay error

Si el carrito no cumple las condiciones, el código igual se agrega a promotionCodes, pero no descuenta nada hasta que el carrito las cumpla.

Para mostrar «te faltan S/ 21», lee las rules del cupón desde tu backend con GET /v1/coupons/{couponId}: el carrito no las trae.

Para quitar un código, usa DELETE. La respuesta es el carrito recalculado:

curl -X DELETE https://storefront.ecomiq.pe/v1/carts/<cart-token>/promotion-codes/BIENVENIDA10 \
  -H "x-Store-Domain: mi-tienda"

El checkout vuelve a revisar

POST /v1/carts/{cartToken}/checkout recalcula los descuentos. Si un código venció o se agotó entretanto, responde 409 con Promotions.CodeNoLongerApplies y el código en params.code, y lo quita del carrito: muestra el nuevo total y vuelve a pedir el pago.

El uso del código se reserva al empezar el pago, así que dos compras no se llevan el último a la vez; si el pedido no se crea, el uso vuelve a quedar libre. En un carrito sin sesión, el límite por cliente se cuenta con el correo de la compra, pero ese correo no da acceso a las promociones de un cliente.

Errores

Al aplicar un código que no sirve, la API responde un error y no lo agrega:

Respuesta 400
{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Promotions.CodeLimitReached": ["Promotion code 'BIENVENIDA10' reached its usage limit."]
  },
  "code": "Promotions.CodeLimitReached"
}

Elige el mensaje para el comprador según code. Los intentos tienen un límite por minuto; con 429, espera lo que indique Retry-After.

codeEstadoQué pasó
Promotions.CodeNotFound400El código no existe.
Promotions.CodeLimitReached400El cupón se agotó: ya se usó todas las veces.
Promotions.CustomerUsageLimitReached400El cliente ya usó el cupón el máximo de veces.
Promotions.CodeInactive400El cupón está inactivo, aún no empieza o venció.
Promotions.CodeNotAssigned400El cupón es para otros clientes o grupos.
Promotions.CodeInvalid400El código es de una promoción automática.
Carts.NotActive400El carrito ya se compró, se canceló o venció.
Checkout.InProgress409El pago del carrito está en curso.
Sin cuerpo404El carrito no existe.

Al crear promociones en Admin API:

codeEstadoQué hacer
Coupon.CodeRequired400Envía al menos un código en codes.
Promotion.NotFound404Revisa el ID del cupón.
Promotion.Exists409Otra promoción usa ese name.
PromotionCode.Exists409Uno de los códigos ya existe.

Siguientes pasos

Última actualización:

En esta página