En este tutorial, vamos a implementar el descuento parcial de envío (`X% OFF` o `$X OFF`, además del envío 100% gratis que ya existe).
Lo que vamos a modificar en este tutorial, son 4 puntos:
product-form.tpl: El label de envío en la página de producto (PDP).- El calculador de envío (atributos `data-*` de cada opción).
- La línea de "Descuento en envío" en el resumen del carrito.
- El JS que conecta la selección de envío con la clase compartida `LS.ShippingDiscountRow`.
HTML
1. Label de envío en la PDP
Vamos a modificar el snipplet snipplets/product/product-form.tpl. Hoy el tema muestra un texto fijo (`"Envío gratis"`) cuando el producto tiene envío gratis. Vamos a resolver los 3 casos (gratis / descuento porcentual / descuento fijo):
{% set shipping_best_discount = cart.free_shipping.min_price_free_shipping %} {% set has_product_free_shipping = product.free_shipping %} {% set is_full_free_shipping = shipping_best_discount.is_free or not shipping_best_discount.discount_type %} {% if has_product_free_shipping or is_full_free_shipping %} {% set shipping_discount_label = "Envío gratis" | translate %} {% else %} {% set shipping_discount = shipping_best_discount.discount_type == 'percentage' ? (shipping_best_discount.discount_value | round) ~ '%' : shipping_best_discount.discount_value_money %} {% set shipping_discount_label = shipping_discount ~ ' ' ~ ('our_components.free_shipping_bar.off_shipping' | tt) %} {% endif %}
{{ shipping_discount_label }}
Cualquier mensaje de progreso que hoy solo debería aparecer con envío 100% gratis (por ejemplo "superando los $X") necesita la condición `is_full_free_shipping` agregada, para no mostrarse también en un descuento parcial:
{% if not product.is_non_shippable and has_product_available and has_free_shipping and not has_product_free_shipping and is_full_free_shipping %} {% endif %}
2. Calculador de envío
Vamos a seguir con los cambios en snipplets/shipping/shipping-calculator-item.tpl. Agregamos dos atributos `data-*` al `<input>` cada opción:
data-cost={% if option.show_price %} {% if option.cost.value == 0 %}{{ 'Gratis' | translate }}{% else %}{{ option.cost }{%endif %}{% else %} {{ 'A convenir' | translate }} {% endif %}
data-original-cost="{% if option.has_partial_shipping_discount %}{{ option.old_cost }}{% endif %} data-shipping-discount="{% if option.has_partial_shipping_discount %}{{ option.discount }}{% endif %}
Y ajustamos la condición del precio tachado (compare-at), que antes solo aparecía con envío 100% gratis `option.cost.value == 0`:
{% if option.old_cost.value and option.old_cost.value > option.cost.value %}
{{ option.old_cost }} {% endif %}
3. Línea de descuento en el resumen del carrito
Vamos a modificar el snipplet snipplets/cart-totals.tpl agregando una línea oculta por defecto, justo después del bloque que muestra el costo de envío `#shipping-cost`:
<div class="js-shipping-discount-row row mb-3 font-medium text-accent" style="display:none;"><span class="col-auto text-uppercase">{{ "our_components.promotions.shipping_discount_row" | tt }}</span><span class="js-shipping-discount-amount col text-right"></span></div>
Para que la clase JS funcione, el markup del tema necesita:
- Un elemento con `id="shipping-cost"` — ahí se escribe el costo (el costo original, cuando hay descuento).
- La línea de descuento con la clase `js-shipping-discount-row`, conteniendo un elemento `.js-shipping-discount-amount` (ahí se escribe el valor `-$X`). Si el tema no tiene hoy ninguna línea de costo de envío en el resumen (el costo va directo al total, sin mostrarse aparte), primero hay que crear esa línea "Envío: $X" — sin ella no hay dónde anclar el descuento.
JS
4. JS: conectar la selección de envío
Vamos a modificar el JS del tema (static/js/store.js.tpl o static/js/store-v2.js.tpl, según el bundle). Donde hoy la selección de envío actualiza el `#shipping-cost` a mano, reemplazamos por la clase compartida:
var $selectedShipping = $('.js-selected-shipping-method'); if ($selectedShipping.length) { LS.ShippingDiscountRow.apply($selectedShipping[0], { containerSelector: '#cart-shipping-container' }); $("#shipping-cost").removeClass('opacity-40'); } ``` Y en el evento de selección (al elegir una opción): ```js LS.ShippingDiscountRow.apply(elem, { containerSelector: '#cart-shipping-container' }); $("#shipping-cost").removeClass('opacity-40');
`LS.ShippingDiscountRow` ya está disponible globalmente — no hace falta importar nada, es parte del bundle de la plataforma. El `containerSelector` evita que una selección en otra parte de la página (por ejemplo el mini-cart del header) pise el costo mostrado en el resumen del carrito; si el tema tiene un único `#shipping-cost` en toda la página, se puede omitir esa opción. Esta llamada tiene que ejecutarse tanto en el evento de selección como en el page-load (cuando ya hay una opción guardada por defecto), para que el descuento se vea también sin que el usuario interactúe.
CSS
Requisito: para que los estilos se apliquen correctamente, hay que ubicarlos en static/css/style-async.scss.tpl.
.js-shipping-discount-row { display: none; } .price-compare { text-decoration: line-through; opacity: 0.5; }
Traducciones
En este paso agregamos los textos para las traducciones en el archivo config/translations.txt
Activación
Para activar la funcionalidad, accede al administrador de la tienda y dirígete a la sección **Descuentos > Promociones**. Desde allí, crea una nueva promoción de envío (100% gratis, `X% OFF` o `$X OFF`), configura los parámetros según sea necesario y guarda los cambios. 