Estamos mejorando nuestra Tienda, Portal de clientes y Aenormas para ofrecerte una mejor experiencia.
Si detectas cualquier incidencia o necesitas ayuda durante el proceso de compra, puedes contactarnos a través del chat o escribirnos a normas@aenor.com.
Nuestro equipo estará encantado de ayudarte.
window.ecomGenericScripts = window.ecomGenericScripts || (function () {
/* =====================================
PROPIEDADES PRIVADAS
===================================== */
const privateProps = window.ecomGenericScripts?.privateProps || {};
/* =====================================
PROPIEDADES PÚBLICAS
===================================== */
const publicProps = {
isDebug: false
};
/* =====================================
FUNCIONES PÚBLICAS
===================================== */
//En functions declaramos la functions publicas
const functions = {};
functions.init = async function () {
await _DOMContentLoaded();
};
/* =====================================
FUNCIONES PRIVADAS
===================================== */
const _loadInit = async function () {
if (publicProps.isDebug) console.log("DOMContentLoaded ecom Generic scripts");
};
const _parsePriceText = function (text) {
if (!text) return 0;
const cleaned = String(text)
.replace(/[^\d,.-]/g, "")
.replace(/\./g, "")
.replace(",", ".");
const n = Number(cleaned);
return Number.isFinite(n) ? n : 0;
};
const _getDetailContext = function (button) {
const wrapper = document.querySelector("#ecom-scripts .data-product-wrapper");
const purchaseBox = button?.closest(".purchase-box") || document;
const langSelect = purchaseBox.querySelector(".select-language");
const formatSelect = purchaseBox.querySelector(".select-format");
const qtyInput = purchaseBox.querySelector(".qty input[type='number']");
const priceNode = purchaseBox.querySelector(".price-header .price");
const titleNode = document.querySelector(".ecom-libro .title, .ecom-norma .title, .ecom-coleccion_tematica .title, .title-book");
return {
wrapper,
purchaseBox,
langSelect,
formatSelect,
qtyInput,
priceNode,
titleNode
};
};
const _getDetailData = async function (productERC, productId, isLibro, isNorma, isColeccionTematica) {
if (!window.ecomGlobalScripts?.functions) return null;
try {
if (isLibro) {
if (productERC) return await window.ecomGlobalScripts.functions.getProductBooksByERC(productERC);
if (productId) return await window.ecomGlobalScripts.functions.getProductBooks(productId);
}
if (isColeccionTematica) {
if (productERC) return await window.ecomGlobalScripts.functions.getProductThematicCollectionsByERC(productERC);
if (productId) return await window.ecomGlobalScripts.functions.getProductThematicCollections(productId);
}
if (isNorma) {
if (productERC) return await window.ecomGlobalScripts.functions.getProductNormasDetailsByERC(productERC);
if (productId) return await window.ecomGlobalScripts.functions.getProductNormasDetails(productId);
}
} catch (e) {}
return null;
};
const _syncDetailBuyButtonState = function (purchaseBox) {
if (!purchaseBox) return;
const btn = purchaseBox.querySelector(".buy-btn");
if (!btn) return;
const langSelect = purchaseBox.querySelector(".select-language");
const formatSelect = purchaseBox.querySelector(".select-format");
const hasSelectors = !!(langSelect || formatSelect);
const hasSelection = !!(langSelect?.value && formatSelect?.value);
if (!hasSelectors) {
if (!btn.disabled) btn.disabled = true;
if (!btn.classList.contains("disabled")) btn.classList.add("disabled");
return;
}
if (btn.disabled === hasSelection) {
btn.disabled = !hasSelection;
}
btn.classList.toggle("disabled", !hasSelection);
};
const _setupQtyPriceMultiplier = function (purchaseBox) {
if (!purchaseBox || purchaseBox.dataset.aeQtyMultBound === "true") return;
purchaseBox.dataset.aeQtyMultBound = "true";
const priceNode = purchaseBox.querySelector(".price-header .price");
const qtyInput = purchaseBox.querySelector(".qty input[type='number']");
if (!priceNode || !qtyInput) return;
const formatter = window.ecomGlobalScripts?.properties?.language?.formatterPrice;
const splitPriceAndCurrency = function (text) {
const m = String(text || "").match(/^(.*?)(\s*[^\d.,\s]+)\s*$/);
if (m) return [m[1].trim(), m[2].trim()];
return [String(text || "").trim(), ""];
};
const formatPrice = function (value, currency) {
const numText = formatter
? formatter.format(value)
: value.toLocaleString("es-ES", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
return currency ? numText + " " + currency : numText;
};
const captureUnit = function () {
const parts = splitPriceAndCurrency(priceNode.textContent);
const parsed = _parsePriceText(parts[0]);
if (parsed > 0) {
purchaseBox.dataset.aeUnitPrice = String(parsed);
purchaseBox.dataset.aeUnitCurrency = parts[1];
}
};
let observer;
const startObserve = function () {
observer.observe(priceNode, { childList: true, characterData: true, subtree: true });
};
const reapply = function () {
const unit = Number(purchaseBox.dataset.aeUnitPrice) || 0;
if (!unit) return;
const qty = Math.max(1, parseInt(qtyInput.value, 10) || 1);
const total = unit * qty;
const currency = purchaseBox.dataset.aeUnitCurrency || "";
if (observer) observer.disconnect();
priceNode.textContent = formatPrice(total, currency);
if (observer) startObserve();
};
// Cuando _renderFragmentPrice (en ECOM-Global_functions/index.js) reescribe
// .price con el precio unitario tras cambiar idioma/formato, recapturamos
// el unitario y reaplicamos la cantidad actual.
observer = new MutationObserver(function () {
captureUnit();
reapply();
});
startObserve();
qtyInput.addEventListener("input", reapply);
qtyInput.addEventListener("change", reapply);
// Captura inicial por si el precio ya estaba renderizado antes de montar el observer.
captureUnit();
reapply();
};
const _bindDetailSelectorState = function (purchaseBox) {
if (!purchaseBox) return;
const sync = function () {
_syncDetailBuyButtonState(purchaseBox);
};
if (purchaseBox.dataset.aeSelectorsBound === "true") {
sync();
return;
}
purchaseBox.dataset.aeSelectorsBound = "true";
purchaseBox.addEventListener("change", function (event) {
if (!event.target?.matches(".select-language, .select-format")) return;
sync();
});
const selectorsRoot = purchaseBox.querySelector(".selector-language_format") || purchaseBox;
const observer = new MutationObserver(function () {
sync();
});
observer.observe(selectorsRoot, {
childList: true,
subtree: true
});
sync();
};
const _attachDetailAddToCart = async function () {
const buttons = document.querySelectorAll(".purchase-box .buy-btn");
if (!buttons.length) return;
buttons.forEach(btn => {
_bindDetailSelectorState(btn.closest(".purchase-box"));
_setupQtyPriceMultiplier(btn.closest(".purchase-box"));
if (btn.dataset.aeBound === "true") return;
btn.dataset.aeBound = "true";
btn.addEventListener("click", (e) => {
e.preventDefault();
const run = async () => {
const ctx = _getDetailContext(btn);
const wrapper = ctx.wrapper;
if (!wrapper) {
window.dispatchEvent(new CustomEvent("cart:error", { detail: { message: "Error al añadir el producto a la cesta" } }));
return;
}
const productId = Number(wrapper.dataset.productId) || null;
const productERC = wrapper.dataset.productErc || "";
const isLibro = wrapper.dataset.productIslibro === "true";
const isNorma = wrapper.dataset.productIsnorma === "true";
const isColeccionTematica = wrapper.dataset.productIscolecciontematica === "true";
const codIdioma = (ctx.langSelect?.value || "").toString();
const codFormato = (ctx.formatSelect?.value || "").toString();
if ((ctx.langSelect || ctx.formatSelect) && (!codIdioma || !codFormato)) {
window.dispatchEvent(new CustomEvent("cart:error", { detail: { message: "Selecciona idioma y formato" } }));
return;
}
const entryType = isLibro ? "Libro" : isColeccionTematica ? "Colección Temática" : "Norma";
const code = productERC || (ctx.titleNode?.textContent || "").trim();
const qty = Math.max(1, parseInt(ctx.qtyInput?.value, 10) || 1);
const name = (ctx.titleNode?.textContent || "").trim();
let priceToSend = 0;
const data = await _getDetailData(productERC, productId, isLibro, isNorma, isColeccionTematica);
if (Array.isArray(data) && data.length && codIdioma && codFormato) {
const entry = data.find(item => {
const langKey = item.codLanguage ?? item.language;
const fmtKey = item.codFormat ?? item.format;
return String(langKey) === codIdioma && String(fmtKey) === codFormato;
});
if (entry) {
const basePrice = Number(entry.price) || 0;
const discount = Number(entry.webDiscount) || 0;
priceToSend = discount > 0 ? Math.floor((basePrice - (basePrice * discount / 100)) * 100) / 100 : basePrice;
}
}
if (!priceToSend && ctx.priceNode) {
priceToSend = _parsePriceText(ctx.priceNode.textContent);
}
await window.ecomGlobalScripts.functions.addToCart({
entryType: entryType,
code: code,
codIdioma: codIdioma,
codFormato: codFormato,
price: priceToSend,
name: name,
amount: qty
});
};
const helper = window.ecomGlobalScripts?.functions?.withButtonSpinner;
if (typeof helper === "function") {
return helper(btn, run, { spinnerClass: "spinner-border spinner-border-sm text-light", mode: "replace" });
}
return run();
});
});
};
const _DOMContentLoaded = async function() {
//Event DOMContentLoaded
if (publicProps.isDebug) console.log("DOMContentLoaded ecom Generic scripts");
if (publicProps.isDebug) console.log("DOMContentLoaded ecom Generic scripts - _loadInit execute");
await _loadInit();
await _attachDetailAddToCart();
};
// ---- Execute Listener DOMContentLoaded ----
document.addEventListener("DOMContentLoaded", async function() {
await _DOMContentLoaded();
});
/* =====================================
API PÚBLICA
===================================== */
return {
properties: publicProps, // properties public
functions: functions // functions public
};
})();









