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
};
})();
EN 12572-2:2017
Artificial climbing structures - Part 2: Safety requirements and test methods for bouldering walls
| Edition date: |
2024-01-30
In Force
|
|---|---|
| Summary: | This European Standard specifies the safety requirements and calculation methods for bouldering walls, including the safety zone.
This European Standard is applicable when the bouldering is in normal use.
This European Standard is not applicable to ice climbing, dry tooling, playground equipment and deep water soloing. Diese Europäische Norm legt die sicherheitstechnischen Anforderungen und Prüfverfahren für Boulder-wände einschließlich der Sicherheitszone fest. Diese Europäische Norm gilt für den üblichen Gebrauch der Boulderanlagen. Diese Europäische Norm gilt nicht für Eisklettern, Drytooling (Eisklettern an künstlichen Wänden), Spiel-platzgeräte und Deep Water Soloing (seilfreies Klettern über tiefem Wasser). La présente Norme européenne spécifie les exigences de sécurité et les méthodes de calcul applicables aux blocs d’escalade, y compris la zone de sécurité. La présente Norme européenne est applicable lorsque les blocs d’escalade sont utilisés normalement. La présente Norme européenne ne s’applique pas à l’escalade sur glace, au dry tooling, aux équipements d’aires de jeux ou à l’escalade sans assurage au-dessus de l’eau (« deep water soloing »). |
| ICS: | 97.220.10-Sports facilities |
| CTN: | CEN/TC 136 - 6118 |
|
Standards Cancellations |
Es anulada por prEN 12572-2 |
|
Reemplazo Normas |
Reemplaza a EN 12572-2:2008 |










