El cliente lo expresó con una frase sencilla: "Quiero que los portes sean gratis desde 200€ para la Península, desde 300€ para Portugal y desde 500€ para Baleares e Italia. Ahora mismo Portugal se beneficia de los portes gratis de España y eso nos sale caro." La tienda —llamémosla BodegaRivero.es— lleva años en PrestaShop 1.7 y tiene instalado el módulo wkavailfreeshipping de Webkul, que en teoría permite exactamente eso: definir condiciones de envío gratuito por zona, transportista y país.

Lo que vino después fue una de esas intervenciones en las que cada solución descubre un problema nuevo. Al final corregimos seis bugs distintos, tocamos tres ficheros PHP del core y los módulos, y dejamos documentado en base de datos el estado exacto de cada condición. Este artículo recoge todo el proceso.

El problema raíz: PS_SHIPPING_FREE_PRICE no entiende de zonas

Antes de tocar nada, lo primero fue entender por qué Portugal recibía portes gratis. La tienda tenía configurado el parámetro global PS_SHIPPING_FREE_PRICE = 200. Este parámetro, que se gestiona desde el módulo en el apartado "Descuento de envío global", es una trampa clásica de PrestaShop.

El override de la clase Cart que instala wkavailfreeshipping intercepta el cálculo del coste de envío. Pero cuando PS_SHIPPING_FREE_PRICE es mayor que cero, lo evalúa antes de consultar las condiciones por zona. El resultado: en cuanto un carrito supera 200€, el envío es gratis para todos los países del mundo, independientemente de cualquier otra configuración del módulo. La condición de "solo España Peninsular" que habíamos visto en el panel simplemente no se aplica.

La solución es poner PS_SHIPPING_FREE_PRICE = 0 y delegar toda la lógica en las condiciones por zona del módulo. Sin ese paso, todo lo demás es inútil.

El módulo tiene dos modos de funcionamiento que conviene entender antes de configurar nada:

Modo Comportamiento Barra de progreso
Global (PS_SHIPPING_FREE_PRICE > 0) Portes gratis para todos al alcanzar el importe Se muestra en todas las páginas, mismo umbral para todos
Por condiciones (PS_SHIPPING_FREE_PRICE = 0) Cada zona tiene su propio umbral Solo en carrito/checkout con dirección asignada, umbral correcto por zona

La pérdida de la barra en fichas de producto es el coste de hacerlo bien. En modo global, un cliente portugués veía "te faltan X€ para portes gratis" calculado sobre 200€ cuando su umbral real era 300€. Información incorrecta es peor que no mostrar nada.

La estructura de zonas y condiciones

Una vez con PS_SHIPPING_FREE_PRICE = 0, el módulo trabaja con condiciones que se crean en su propio controlador de administración. El detalle importante: las condiciones no aparecen en la pantalla habitual de configuración del módulo. Tienen su propio controlador accesible por URL directa:

URL del controlador de condiciones
https://tutienda.com/adminXXX/index.php?controller=AdminWkAfsCondition

Para BodegaRivero.es configuramos cuatro condiciones, una por zona geográfica:

Zona Transportista País(es) Umbral portes gratis
España Peninsular Transporte Seguro Península España (exc. Baleares/Canarias) 200€
España Islas Baleares Transporte Islas Baleares España (estado Baleares) 500€
Portugal Transporte Extranjero Portugal 300€
Italia Transporte Extranjero Italia 500€

Bug 1 — Baleares aplica el umbral de la Península

Con las condiciones creadas y el parámetro global a cero, hicimos las primeras pruebas. España Peninsular y Portugal funcionaban. Baleares, no: un carrito de 250€ con dirección en Palma de Mallorca obtenía portes gratis, cuando el umbral para Baleares debería ser 500€.

El diagnóstico tardó un rato porque a primera vista la configuración era correcta. El problema estaba en el código del propio módulo. En dos ficheros independientes, wkavailfreeshipping usaba esta función para determinar la zona del pedido:

Código original — incorrecto
$wkIdZone = Country::getIdZone($wkIdCountry);

Country::getIdZone() devuelve la zona asociada al país. Para España (id_country = 6), devuelve siempre la zona 9, que es la Peninsular. Baleares tiene su propia zona (11) pero solo a nivel de estado/provincia, no de país. Como ambas son el mismo país, la función nunca devuelve zona 11.

La solución es usar la dirección de entrega concreta del pedido, que sí contiene el estado/provincia y por tanto la zona correcta:

Corrección en Cart.php (override)
// ANTES:
$wkIdZone = Country::getIdZone($wkIdCountry);

// DESPUÉS:
$wkIdZone = Address::getZoneById((int) $this->id_address_delivery);
Corrección en AfsCondition.php
// DESPUÉS — con fallback cuando no hay dirección asignada:
$wkIdZone = $cart->id_address_delivery
    ? Address::getZoneById((int) $cart->id_address_delivery)
    : Country::getIdZone($wkIdCountry);

Este bug afecta a cualquier tienda PrestaShop que tenga zonas basadas en estados o provincias (Baleares, Canarias, o cualquier región con zona propia) y use wkavailfreeshipping. La instalación por defecto del módulo siempre lo tiene.

Bug 2 — Las condiciones de Portugal y Baleares no aplican el descuento

Corregido el problema de zona, las pruebas con Baleares y Portugal seguían fallando: el módulo identificaba correctamente la zona pero no aplicaba los portes gratis aunque se superara el umbral.

La causa era más discreta. Al crear una condición desde el admin del módulo, el campo categories se guarda vacío ([] en JSON). El módulo aplica el descuento solo si el producto encaja en una categoría configurada o si allowedForAll es true. En la práctica, con categories = [], el descuento nunca se aplica aunque el importe supere el umbral.

La condición de España Peninsular funcionaba porque existía antes de nuestra intervención y tenía las categorías correctas. Las nuevas condiciones no las heredaban automáticamente. La solución fue copiar las mismas categorías por SQL:

SQL — copiar categorías a las condiciones sin ellas
UPDATE ps_wk_afs_condition
SET categories = '["3755","3756","3759","3760","3757","3779","3767","3804"]'
WHERE id_condition IN (3, 4);  -- Portugal y Baleares

Aviso para nuevas condiciones: cada vez que se crea una condición nueva en el módulo, hay que asegurarse de que tenga las mismas categorías que las condiciones que funcionan. El formulario del admin no las rellena automáticamente.

Bug 3 — La condición de peso siempre es verdadera

Al crear condiciones desde el admin, el módulo rellena shipping_weight_min = 0 y shipping_weight_max = 99999 por defecto. El módulo aplica portes gratis si se cumple la condición de precio o la de peso. Con ese rango de peso, la condición de peso siempre es verdadera, lo que significa que todos los pedidos obtendrían portes gratis independientemente del importe.

La corrección es poner ambos campos al mismo valor máximo para que la condición de peso nunca se cumpla y sea la de precio la única que importa:

SQL — desactivar condición de peso
UPDATE ps_wk_afs_condition
SET shipping_weight_min = 99999.00, shipping_weight_max = 99999.00;

Bug 4 — El transportista de Baleares no aparece en el módulo

Durante la configuración en producción, al intentar asignar el transportista de Baleares a la condición correspondiente, el campo del módulo no lo mostraba como opción. El transportista existía en la base de datos pero no era seleccionable.

El módulo usa Carrier::getCarriers() para poblar el selector, y esa función filtra por active = 1. El transportista de Baleares estaba marcado como inactivo (active = 0) en producción, probablemente porque nadie lo había activado explícitamente. En el entorno de pruebas sí estaba activo, de ahí que no hubiéramos detectado el problema antes.

SQL — activar el transportista
UPDATE ps_carrier SET active = 1 WHERE id_carrier = 172;

Bug 5 — Cliente italiano procesado con transportista de España

Este fue el bug más inesperado y llegó después de la puesta en producción. Un pedido de un cliente italiano fue procesado con el transportista de España Peninsular, cobrándole los portes incorrectos.

La causa: la dirección de entrega del cliente tenía id_state = 348 (Huesca, provincia española). Address::getZoneById() prioriza la zona del estado sobre la del país:

Lógica de Address::getZoneById en PrestaShop
// PrestaShop prioriza la zona del estado si existe
self::$_idZones[$id_address] = (int) (
    (int) $result['id_zone_state'] ? $result['id_zone_state'] : $result['id_zone']
);

Como Huesca pertenece a la zona Peninsular (zona 9), el sistema seleccionó el transportista de España en lugar del de Italia. El cliente probablemente había cambiado el país a Italia después de haber seleccionado Huesca como provincia, y el formulario no limpió el campo id_state.

La corrección para el pedido afectado fue directa, y añadimos una consulta de diagnóstico para detectar el mismo problema en otras direcciones:

SQL — detectar direcciones con provincia española y país extranjero
SELECT a.id_address, a.id_country, a.id_state,
       s.name AS estado, cl.name AS pais
FROM ps_address a
JOIN ps_country_lang cl ON cl.id_country = a.id_country AND cl.id_lang = 1
LEFT JOIN ps_state s ON s.id_state = a.id_state
WHERE a.id_country != 6
  AND a.id_state != 0
  AND a.deleted = 0;

Bug 6 — Usuarios sin dirección ven el umbral de España

El último problema era de cara al cliente, no de lógica de cálculo. Un usuario anónimo o recién llegado a la tienda veía en el carrito la barra de progreso con "te faltan X€ para portes gratis" calculada a 200€, el umbral de España Peninsular, aunque su país real fuera otro.

La función getWeightAndPriceForFreeShipping() en AfsCondition.php, cuando id_address_delivery = 0, usa el país por defecto del contexto (España) para buscar la condición aplicable. Encuentra la condición de la Península (200€) y la muestra como si fuera universal.

La solución fue añadir un bloque en displayMessage() del fichero principal del módulo que detecta la ausencia de dirección y muestra un mensaje informativo estático con todos los umbrales, en lugar del umbral de España:

PHP — bloque para usuarios sin dirección (wkavailfreeshipping.php)
// Sin dirección: mensaje genérico informativo
if (!$this->context->cart->id_address_delivery) {
    $genericMsg = 'Portes gratis: 200€ España Peninsular | 300€ Portugal | 500€ Baleares e Italia. '
        . 'Inicia sesión e introduce tu dirección para ver el descuento aplicado a tu pedido.';
    $this->context->smarty->assign([
        'shipping_msg' => 1,
        'message'      => $genericMsg,
        'color'        => Configuration::get('WK_AFS_SHIPPING_MSG_COLOR'),
        'bgColor'      => Configuration::get('WK_AFS_SHIPPING_MSG_BG_COLOR'),
        'imageName'    => false,
    ]);
    return $this->fetch(
        'module:wkavailfreeshipping/views/templates/hook/avail-free-shipping-alert.tpl'
    );
}

Una nota importante sobre la plantilla: el tag Smarty predeterminado escapa el HTML. Si el mensaje incluye caracteres como o etiquetas, hay que usar {$message nofilter} en el fichero .tpl para que Smarty no los escape.

El resultado final en producción

Tras aplicar todas las correcciones, el estado en base de datos quedó así:

Condición Umbral Zona País Estado
España Peninsular 200€ 9 — Península España Activa
Portugal 300€ 13 — Portugal Portugal Activa
Baleares 500€ 11 — Baleares España Activa
Italia 500€ 15 — Italia Italia Activa

Y PS_SHIPPING_FREE_PRICE = 0 en la configuración global de PrestaShop.

Verificamos el funcionamiento con pedidos de prueba en cada zona: carrito de 280€ con dirección en Lisboa → portes 7.80€ (no llega a 300€); mismo carrito a 340€ → portes gratis. Carrito de 460€ con dirección en Palma de Mallorca → portes con coste (no llega a 500€); a 520€ → portes gratis. Los resultados fueron correctos en todos los casos.

Resumen: lista de verificación para esta configuración

Si vas a implementar portes gratis por zona en PrestaShop 1.7 con wkavailfreeshipping, esta es la secuencia completa para no llevarte sorpresas:

  1. Poner PS_SHIPPING_FREE_PRICE = 0 desde el módulo (o por SQL). Sin este paso, nada más funciona.
  2. Crear las condiciones desde el controlador AdminWkAfsCondition, no desde la pantalla principal del módulo.
  3. Asignar las mismas categorías que la condición que funciona a todas las condiciones nuevas. El formulario no las rellena solo.
  4. Corregir la condición de peso: poner shipping_weight_min = shipping_weight_max = 99999 para que nunca se cumpla.
  5. Verificar que los transportistas están activos (active = 1) antes de intentar asignarlos en el módulo.
  6. Reemplazar Country::getIdZone por Address::getZoneById en los dos ficheros del módulo si la tienda tiene zonas basadas en provincias (Baleares, Canarias, etc.).
  7. Añadir el mensaje genérico para usuarios sin dirección si no quieres que vean el umbral de España por defecto.
  8. Limpiar la caché de PrestaShop después de cada cambio (Parámetros Avanzados → Rendimiento → Limpiar caché).

Antes de aplicar en producción: prueba siempre los cambios en un entorno de réplica. El override de Cart.php y los cambios en el módulo afectan al cálculo de envío en tiempo real para todos los clientes.

Preguntas frecuentes

¿Por qué PS_SHIPPING_FREE_PRICE aplica portes gratis a todos los países aunque el módulo esté configurado solo para España?

Porque es un parámetro global de PrestaShop evaluado antes de cualquier lógica de módulo. El override de la clase Cart lo intercepta y aplica portes gratis en cuanto el pedido supera el importe, sin distinguir por zona ni transportista. La solución es ponerlo a 0 y delegar toda la lógica en las condiciones por zona.

¿Por qué Baleares aplica el mismo umbral que España Peninsular?

Porque el módulo usa Country::getIdZone() para determinar la zona, y para España siempre devuelve la zona Peninsular independientemente de la provincia. Hay que sustituirlo por Address::getZoneById() en los dos ficheros del módulo donde aparece.

¿La barra de progreso sigue funcionando en modo condiciones?

Sí, pero solo en el carrito y el checkout, cuando el cliente tiene una dirección asignada. En fichas de producto, sin dirección, no se muestra. Es el comportamiento correcto: sin dirección no es posible saber el umbral específico del cliente.

¿Qué pasa si actualizo el módulo wkavailfreeshipping?

Las modificaciones en Cart.php (override) y en AfsCondition.php se perderán. Hay que reaplicarlas tras cualquier actualización del módulo. Conviene guardar los ficheros modificados o documentar los cambios exactos con números de línea.

¿Cómo detecto clientes extranjeros con provincia española en su dirección?

Con una consulta SQL que cruce ps_address con ps_state y ps_country_lang, filtrando por id_country != 6 AND id_state != 0. Esas direcciones pueden causar que el sistema les asigne el transportista de España Peninsular en lugar del que corresponde a su país.

¿Tienes una tienda PrestaShop con problemas de envío?

Diagnóstico, corrección de módulos y configuración de zonas de transporte. Si tu tienda está en Huesca, Aragón o en cualquier otro punto de España, el trato es directo con el técnico, sin intermediarios.