Zonas y tarifas
Configura ubicaciones, perfil, zona, métodos y tarifas para envío a domicilio y retiro en tienda.
Una tienda en Lima ofrece envío a domicilio desde su almacén y retiro gratis en su tienda física. Así se configura con Admin API.
Cómo funciona
Cinco piezas deciden qué opciones de envío ve el comprador:
| Pieza | Qué es | En esta guía |
|---|---|---|
| Ubicación | Un lugar desde donde despachas o donde se retira. | Almacén Lima y Tienda Miraflores. |
| Perfil | Qué ubicaciones atienden a un grupo de productos. | El perfil predeterminado de la tienda. |
| Zona | Los destinos que comparten precio. | Lima Metropolitana. |
| Método | La opción que elige el comprador. | Envío estándar y Retiro en tienda. |
| Tarifa | El precio de un método en una zona. | S/ 10, gratis desde S/ 150. |
Un método sin tarifa no aparece en el carrito.
Requisitos
- Un token de Admin con los permisos
settings:read,settings:updateyshipping_settings:updateenECOMIQ_TOKEN. Para asignar productos a un perfil, tambiéncatalog:update. Ver Inicio rápido. - Productos con peso, si vas a cobrar por kilo.
- Node.js 18 o superior y Python 3 con
requests, para los ejemplos. El cURL de Crea las tarifas usajq.
Crea las ubicaciones
Una ubicación despacha envíos, acepta retiros o ambas cosas. Almacén Lima
despacha a domicilio. Tienda Miraflores es el punto de retiro. Si ya existen,
búscalas con
Listar ubicaciones logísticas
y usa su id.
curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/locations \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Almacén Lima",
"code": "ALM-LIMA",
"type": "warehouse",
"address": "Av. Argentina 2450",
"city": "Lima",
"state": "Lima",
"country": "PE",
"postalCode": "15081",
"isDefault": true,
"supportsShipping": true
}'
curl -X POST https://api.ecomiq.pe/api/v1/fulfillment/locations \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Tienda Miraflores",
"code": "TDA-MIRAFLORES",
"type": "store",
"address": "Calle Berlín 245",
"city": "Lima",
"state": "Lima",
"country": "PE",
"postalCode": "15074",
"isPhysicalStore": true,
"supportsPickup": true,
"pickupInstructions": "Presenta tu DNI y el número de pedido en caja.",
"pickupLeadTimeInMinutes": 120
}'{ "id": "7204558912004325378" }Tienda Miraflores recibe 7204558912004325380. Los pasos siguientes reusan
API y las cabeceras. Un code repetido responde 409 con
Location.CodeExists.
| Campo | Qué hace |
|---|---|
type | warehouse, store o seller. |
supportsShipping | Despacha envíos a domicilio. Por defecto false. |
supportsPickup | Acepta retiros. Por defecto false. |
supportsLocalDelivery | Hace entregas locales con su propio reparto. |
useForFulfillment | Prepara pedidos. Por defecto true. |
isDefault | Ubicación predeterminada. Marcar otra la reemplaza. |
pickupInstructions | Lo que el comprador debe presentar al retirar. |
pickupLeadTimeInMinutes | Tiempo de preparación antes de que el pedido esté listo. |
Carga el stock de cada ubicación con Inventario.
Asigna las ubicaciones al perfil
Un perfil agrupa productos y define desde qué ubicaciones salen. Tu tienda ya
tiene un perfil predeterminado: lo usan los productos sin perfil asignado.
Búscalo por isDefault: true y fija sus locationIds.
# 1. Busca el perfil con "isDefault": true
curl https://api.ecomiq.pe/api/v1/shipping/profiles \
-H "Authorization: Bearer $ECOMIQ_TOKEN"
# 2. Fija sus ubicaciones
curl -X PUT https://api.ecomiq.pe/api/v1/shipping/profiles/7204558912004325430 \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Perfil general",
"description": "Sale de Almacén Lima o se retira en Tienda Miraflores.",
"locationIds": ["7204558912004325378", "7204558912004325380"],
"isDefault": true,
"isActive": true
}'{
"id": "7204558912004325430",
"name": "Perfil general",
"locationIds": ["7204558912004325378", "7204558912004325380"],
"isDefault": true,
"isActive": true
}PUTreemplaza el perfil completo: envía todos los campos.- Mantén
isDefault: trueeisActive: true. El perfil predeterminado no se desmarca ni se desactiva: la API responde400. Para cambiarlo, marca otro perfil conisDefault: true. - Sin
locationIds, el perfil usa todas las ubicaciones activas. - Si no tienes perfiles, crea uno con
POST /: el primero queda como predeterminado.v1/ shipping/ profiles
Para que un producto salga con otro perfil, crea ese perfil y asígnale el
producto. La respuesta es 204. DELETE en la misma ruta lo devuelve al
perfil predeterminado.
curl -X POST https://api.ecomiq.pe/api/v1/shipping/profiles/7204558912004325431/products \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "productIds": ["7204558912004325302"] }'Crea la zona
Una zona agrupa destinos con las mismas tarifas. Lima Metropolitana es la provincia de Lima, en el departamento de Lima. El Callao queda fuera: es otro departamento.
curl -X POST https://api.ecomiq.pe/api/v1/shipping/zones \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "LIMA",
"name": "Lima Metropolitana",
"description": "Provincia de Lima.",
"countryCodes": ["PE"],
"provinceCodes": ["Lima"],
"cityNames": ["Lima"]
}'{ "id": "7204558912004325435" }| Lista | Compara con | Ejemplo |
|---|---|---|
countryCodes | País de la dirección, en ISO 3166-1. | PE |
provinceCodes | Departamento, por nombre o ubigeo. | Lima o 15 |
cityNames | Provincia o ciudad, por nombre o ubigeo. | Lima o 1501 |
districtNames | Distrito, por nombre o ubigeo. | Miraflores o 150122 |
postalCodes | Código postal exacto. | 15074 |
- Llena al menos una lista.
- Dentro de una lista, basta que coincida un valor.
- Si llenas varias listas, el destino debe cumplir todas.
- No distingue mayúsculas.
Busca nombres y ubigeos con
Listar áreas geográficas:
GET /. level1 es el
departamento, level2 la provincia y level3 el distrito.
code identifica la zona en las tarifas y es único en la tienda. Sin code,
la API genera uno como ZON-001. Uno repetido responde 409 con
ShippingZone.CodeExists.
Con isActive: false, las tarifas de la zona dejan de aplicar.
Crea los métodos
Un método es lo que elige el comprador. Crea Envío estándar y Retiro en tienda en el perfil de Asigna las ubicaciones al perfil.
curl -X POST https://api.ecomiq.pe/api/v1/shipping/methods \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Envío estándar",
"baseLanguage": "es",
"type": "regular",
"basePrice": 10,
"currency": "PEN",
"estimatedDeliveryDays": 2,
"sortOrder": 1,
"shippingProfileId": "7204558912004325430"
}'
curl -X POST https://api.ecomiq.pe/api/v1/shipping/methods \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Retiro en tienda",
"baseLanguage": "es",
"type": "pickup",
"basePrice": 0,
"currency": "PEN",
"sortOrder": 2,
"shippingProfileId": "7204558912004325430"
}'{ "id": "7204558912004325440" }Retiro en tienda recibe 7204558912004325442. Un método nuevo queda activo.
type | Qué ofrece | Aparece si |
|---|---|---|
regular, express, sameDay | Envío a domicilio | Una ubicación del perfil tiene supportsShipping. |
pickup | Retiro | pickupEnabled está activo y hay una ubicación con supportsPickup o un punto de retiro. |
localDelivery | Entrega local | localDeliveryEnabled está activo y una ubicación con supportsLocalDelivery cubre el destino. |
| Campo | Qué hace |
|---|---|
shippingProfileId | Perfil del método. Un perfil con métodos propios solo ofrece esos. Uno sin métodos ofrece los que no tienen perfil. |
displayName | Nombre visible. Si falta, se usa name. |
estimatedDeliveryDays | Días hábiles de tránsito. |
minOrderAmount, maxOrderAmount | Subtotal en el que se ofrece el método. |
minWeight, maxWeight | Peso en kilos en el que se ofrece. |
visibleInCheckout | Con false, el método no sale en el carrito. Por defecto true. |
isActive | Con false, el método se pausa. Por defecto true. |
sortOrder | Orden en el carrito, de menor a mayor. |
pickupPointIds | Solo en pickup: limita los puntos de retiro. Vacío ofrece todos. |
En envíos y retiros, el comprador paga el precio de la tarifa, no basePrice.
Para ocultar o renombrar un método según monto, peso, día u hora, usa
Crear personalización de envío.
Puntos de retiro
Un método pickup ofrece cada ubicación del perfil con supportsPickup. Crea
un punto de retiro solo para un lugar que no es tuyo, como la agencia de un
courier, con Crear punto de
retiro.
Un punto con locationId reemplaza a esa ubicación en el carrito: el
comprador la ve una sola vez.
Traduce los textos visibles
Métodos, tarifas y personalizaciones de envío guardan baseLanguage y
translations. En un método, name es interno y displayName es lo que ve el
comprador.
{
"baseLanguage": "es",
"displayName": "Envío estándar",
"translations": {
"en": { "displayName": "Standard delivery" },
"pt": { "displayName": "Entrega padrão" }
}
}| Recurso | Cada traducción lleva |
|---|---|
| Método | displayName, obligatorio. |
| Tarifa | description, o null. |
| Personalización | displayName, obligatorio. |
translationslleva solo los otros idiomas, no el base.- Al actualizar, omitir
baseLanguageotranslations(o enviarlosnull) conserva el valor.translations: {}borra todas. - Se elige el idioma exacto, luego el principal y luego el texto base:
pt-BR → pt → base. Una tarifa condescription: nullen una traducción queda sin descripción en ese idioma. - El carrito usa el idioma de la tienda; los borradores de Admin, el del miembro.
Crea las tarifas
La tarifa pone el precio de un método. Sin una tarifa que aplique, el método no sale en el carrito. Envío estándar cuesta S/ 10 en Lima Metropolitana y es gratis desde S/ 150. Retiro en tienda es gratis.
[
{
"baseLanguage": "es",
"shippingMethodId": "7204558912004325440",
"type": "flat",
"zoneCode": "LIMA",
"price": 10,
"maxOrderPrice": 149.99,
"currency": "PEN",
"description": "Envío a Lima Metropolitana"
},
{
"baseLanguage": "es",
"shippingMethodId": "7204558912004325440",
"type": "freeShipping",
"zoneCode": "LIMA",
"minOrderPrice": 150,
"currency": "PEN",
"description": "Envío gratis desde S/ 150"
},
{
"baseLanguage": "es",
"shippingMethodId": "7204558912004325442",
"type": "freeShipping",
"currency": "PEN",
"description": "Retiro gratis"
}
]jq -c '.[]' tarifas.json | while read -r rate; do
curl -X POST https://api.ecomiq.pe/api/v1/shipping/rates \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d "$rate"
done{ "id": "7204558912004325441" }Las otras dos reciben 7204558912004325443 y 7204558912004325444.
Cada tarifa que aplica es una opción más en el carrito. Por eso la de S/ 10
termina en maxOrderPrice: 149.99: desde S/ 150 solo queda la gratuita.
type | Precio |
|---|---|
flat | price. |
priceRange | price. Exige minOrderPrice o maxOrderPrice. |
postalCode | price. Exige postalCodes. |
weightBased | basePrice más pricePerKg por kilo. |
tableCost | basePrice, más pricePerKg por kilo y pricePerUnit por unidad. |
freeShipping | Siempre 0. |
| Campo | Qué hace |
|---|---|
zoneCode | code de la zona. Vacío o general vale para cualquier destino. |
postalCodes | Limita la tarifa a esos códigos postales. |
minOrderPrice, maxOrderPrice | Rango del subtotal. Incluye ambos extremos. |
minWeight, maxWeight | Rango de peso en kilos. |
description | Nombre de la opción en el carrito. Si falta, se usa el displayName del método. |
surcharge | Monto que se suma al precio. |
discountPercentage | Descuento sobre el precio, de 0 a 100. |
Copia el code de la zona tal cual. Uno que no existe responde 404 con
ShippingZone.NotFound. Una tarifa con el mismo método, tipo, zona, códigos
postales y rangos que otra responde 409 con ShippingRate.Duplicate. Más
campos en
Crear tarifa de envío.
Ajusta los envíos de la tienda
PATCH / cambia solo los campos que envías. Activa el
retiro y la fecha estimada de entrega.
curl -X PATCH https://api.ecomiq.pe/api/v1/shipping/settings \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"defaultLocationId": "7204558912004325378",
"pickupEnabled": true,
"estimatedDeliveryEnabled": true,
"estimatedDeliveryFulfillmentDays": 1
}'{
"storeId": "7204558912004325300",
"defaultLocationId": "7204558912004325378",
"estimatedDeliveryEnabled": true,
"estimatedDeliveryFulfillmentDays": 1,
"pickupEnabled": true,
"localDeliveryEnabled": false,
"useLiveRates": false
}| Campo | Qué hace |
|---|---|
pickupEnabled | Ofrece los métodos pickup. |
localDeliveryEnabled | Ofrece los métodos localDelivery. |
estimatedDeliveryEnabled | Calcula la fecha estimada de entrega. |
estimatedDeliveryFulfillmentDays | Días hábiles de preparación. |
useLiveRates | Cotiza en vivo con el transportista del método (connectionId). Si falla, usa las tarifas. |
defaultLocationId | Ubicación predeterminada de envíos. Debe estar activa. |
La fecha estimada suma los días de preparación y los estimatedDeliveryDays
del método, en días hábiles. Salta sábados, domingos y feriados. Un pedido del
lunes 5 de octubre de 2026 suma 1 + 2 días hábiles. El jueves 8 es feriado, así
que la fecha estimada es el viernes 9.
Una tienda nueva trae los feriados nacionales del Perú de ese año y del siguiente. Un pedido programado para un feriado no ofrece retiro ni entrega local. Agrega tus cierres con Crear rango no laborable:
curl -X POST https://api.ecomiq.pe/api/v1/shipping/holidays \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Inventario anual",
"fromDate": "2026-12-30",
"toDate": "2026-12-31"
}'Revisa el carrito
El comprador ve las opciones en GET / de
Storefront API. Esa ruta no usa token: la tienda va en x-Store-Domain. El
flujo completo está en Tienda headless.
curl https://storefront.ecomiq.pe/v1/carts/<cart-token>/shipping-options \
-H "x-Store-Domain: mi-tienda"Este carrito tiene un Polo de algodón de S/ 79 y una dirección en Miraflores:
{
"groups": [
{
"shippingProfileId": "7204558912004325430",
"orderAmount": 79,
"rates": [
{
"shippingMethodId": "7204558912004325440",
"name": "Envío a Lima Metropolitana",
"displayName": "Envío estándar",
"type": "regular",
"target": "automatic",
"targetId": null,
"price": 10,
"currency": "PEN",
"rateId": "7204558912004325441",
"estimatedDeliveryFromDate": "2026-10-09"
},
{
"shippingMethodId": "7204558912004325442",
"name": "Retiro gratis",
"displayName": "Retiro en tienda",
"type": "pickup",
"target": "location",
"targetId": "7204558912004325380",
"targetName": "Tienda Miraflores",
"price": 0,
"currency": "PEN",
"rateId": "7204558912004325444"
}
]
}
]
}- El carrito agrupa los artículos por perfil. Cada grupo trae sus opciones.
- Cada elemento de
rateses una opción. El comprador elige una conPUT /: envíav1/ carts/ {cartToken}/ shipping-method shippingMethodId,rateId,target,targetIdy elshippingProfileIddel grupo. - Las zonas por país, departamento, provincia o distrito necesitan la
dirección de envío del carrito. En Perú, envía
department,provinceydistrict. Para zonas por ubigeo, envía tambiéndepartmentCode,provinceCodeydistrictCode. El carrito los guarda para las siguientes consultas. - Con solo
postalCode, aplican las tarifas sin zona y las zonas por código postal.
Si falta una opción, revisa:
| Falta | Revisa |
|---|---|
| Envío estándar | Una tarifa activa para ese subtotal y peso, la zona de la dirección y supportsShipping en Almacén Lima. |
| Retiro en tienda | pickupEnabled, una tarifa activa y Tienda Miraflores en el perfil con supportsPickup. |
| Ambas | visibleInCheckout del método y el perfil que usa el producto. |
Cambia tarifas en lote
POST / crea o cambia hasta 100 tarifas en una llamada.
Cada fila lleva action:
createnecesitashippingMethodId.updatenecesitashippingRateIdy solo cambia los campos que envías.
Los valores van como texto, igual que en un archivo: "12.00", no 12. Este
lote crea una tarifa para el Callao y sube a S/ 12 la de Lima.
{
"rows": [
{
"action": "create",
"shippingMethodId": "7204558912004325440",
"type": "flat",
"zoneCode": "CALLAO",
"price": "12.00",
"currency": "PEN",
"description": "Envío al Callao"
},
{
"action": "update",
"shippingRateId": "7204558912004325441",
"price": "12.00"
}
]
}curl -X POST https://api.ecomiq.pe/api/v1/shipping/rates/batch \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: tarifas-2026-10-05" \
--data @tarifas-lote.json{
"results": [
{
"index": 0,
"id": null,
"status": "failed",
"errors": [
{
"field": null,
"code": "ShippingRates.ImportInvalidRows",
"message": "Row 1: zone_code 'CALLAO' was not found."
}
]
},
{
"index": 1,
"id": "7204558912004325441",
"status": "updated",
"errors": []
}
]
}200 trae un resultado por fila, en el orden recibido. Las filas válidas se
aplican aunque otras fallen. Aquí falta la zona CALLAO: créala como en Crea la zona y reenvía solo esa fila.
- El lote rechaza un
zoneCodeque no existe.generaldeja la tarifa sin zona. - Una fila
createcon el mismo método, tipo, zona, códigos postales y rangos que otra tarifa falla. Repetir el lote no duplica tarifas. Idempotency-Keyes opcional. Conserva la clave al reintentar el mismo lote.
Importa tarifas desde un archivo
Para cientos de tarifas, sube un archivo a POST /.
Funciona como Importación de productos:
responde 202 con una operación, la sigues hasta que termina y lees el reporte
de filas rechazadas. Los formatos y límites son los mismos.
Este archivo sube a S/ 200 el monto del envío gratis:
shipping_rate_id,min_order_price,max_order_price
7204558912004325441,,199.99
7204558912004325443,200,curl -X POST https://api.ecomiq.pe/api/v1/shipping/rates/imports \
-H "Authorization: Bearer $ECOMIQ_TOKEN" \
-H "Content-Type: text/csv" \
-H "Idempotency-Key: umbral-gratis-2026-10-05" \
--data-binary @tarifas.csv{
"id": "7204558912004325460",
"status": "pending",
"resource": "shipping-rates",
"format": "csv",
"createdOn": "2026-10-05T15:30:00Z"
}Sigue la operación con GET /. Agrega ?validateOnly=true
a la URL para revisar el archivo sin cambiar tarifas.
Los encabezados pueden ir como en la plantilla (en español o en inglés) o con estas claves:
| Columna | Qué lleva |
|---|---|
shipping_method (Método de envío) | Nombre del método, como en la pestaña Listas, o su ID. Obligatoria al crear. |
shipping_rate_id (ID interno) | Sin ID se crea la tarifa. Con ID se actualiza esa tarifa. |
type (Tipo de tarifa) | Monto fijo, Gratis, Por peso, Peso + artículos, Por precio del pedido o Por códigos postales. También acepta Flat, FreeShipping, WeightBased, TableCost, PriceRange y PostalCode. Vacío: Monto fijo. |
zone (Zona) | Nombre o código de la zona. Vacío, general o Todas las zonas: todas. |
price, base_price, price_per_kg, price_per_unit, surcharge | Montos. Qué columnas usa cada tipo está en la pestaña Instrucciones. |
min_order_price, max_order_price, min_weight, max_weight | Rangos. En update, una celda vacía conserva el valor. |
postal_codes | Códigos separados por comas. En CSV, entre comillas. |
checkout_label (Texto en el checkout) | Nombre de la opción en el carrito. |
is_active, tax_included | Sí o No. También acepta true, false, yes y no. |
action | Opcional: create o update. Sin ella decide el ID interno. |
Pide la plantilla
POST / con { "format": "xlsx", "template": true }
genera la plantilla: la pestaña Tarifas con las columnas, Instrucciones con
qué poner en cada una, y Listas con tus métodos, tus zonas y los tipos. Es una
operación: al terminar, descarga su resultUrl.
Siguientes pasos
Última actualización: