"use strict"; // Contants: const _HASHBNDW = "gadnic_produccion_eds9l"; const _COMPANY = "gadnic_produccion"; const _PURLBNDW = window.location.protocol + "//s2.braindw.com"; const _NSURLBNDW = window.location.protocol + "//s2.braindw.com"; const _GRAPHQL_URL = "https://u2.vrein.ai/graphql"; const _TENANT_ID = "gadnic"; const _GUID = ''; const _BDW_GETGUID_SECRET = "9DIIDJ7DHDA8SDUA9SUOKDS2309.DJDJC.99DD8U3"; let _resolvedGuid = _GUID; let _resolvedGuidPromise = null; const bdwResolveGuid = async () => { // Un token sin reemplazar es truthy y viajaria como GUID basura. if (_resolvedGuid && _resolvedGuid.indexOf("{") !== -1) _resolvedGuid = ""; if (_resolvedGuid) return _resolvedGuid; if (_resolvedGuidPromise) return _resolvedGuidPromise; const storedGuid = localStorage.getItem("bdw_guid"); if (storedGuid) { _resolvedGuid = storedGuid; return _resolvedGuid; } // Cookie first-party escrita por el pixel del store. Cubre el caso en que el // servidor no pudo inyectar el GUID por tener bloqueada su cookie de tercero // (Safari/iOS con ITP, webviews), y evita pedir uno nuevo al pedo. const cookieMatch = /(^|;)\s*bdw_session=([^;]*)/.exec(document.cookie); if (cookieMatch) { _resolvedGuid = decodeURIComponent(cookieMatch[2]); if (_resolvedGuid) return _resolvedGuid; } const email = getEmail() || ""; const getGuidUrl = "https://s2.braindw.com/tracking/GetGuid" + "?HASH=" + encodeURIComponent(_HASHBNDW) + "¤tguid=" + "&url=" + encodeURIComponent(window.location.href + "/") + "&email=" + encodeURIComponent(email) + "×tamp=" + Date.now(); _resolvedGuidPromise = (async function () { try { const response = await fetch(getGuidUrl, { headers: { "Cache-Control": "no-store, no-cache, max-age=0", Pragma: "no-cache", "bdw-secretcode": _BDW_GETGUID_SECRET, bdw_secretcode: _BDW_GETGUID_SECRET, }, }); if (!response.ok) return ""; const text = await response.text(); const normalized = typeof text === "string" ? text.trim() : ""; // Asignar ANTES de guardar: al reves se persistia el valor viejo (vacio, // porque con valor se hubiera retornado antes), asi que el cache nunca // servia y cada carga volvia a pedir un GUID nuevo. _resolvedGuid = normalized || ""; if (_resolvedGuid) localStorage.setItem("bdw_guid", _resolvedGuid); return _resolvedGuid; } catch (e) { return ""; } finally { _resolvedGuidPromise = null; } })(); return _resolvedGuidPromise; }; // ===================== DATA LAYER HELPERS ===================== /** * Finds and returns the first object in window.dataLayer that contains a page_type field. * Returns null if dataLayer is not available or no page_type entry exists. */ const getDataLayerPageInfo = () => { if (!Array.isArray(window.dataLayer)) return null; return window.dataLayer.find((entry) => entry && typeof entry.page_type === "string") || null; }; /** * Maps window.dataLayer page_type to a BDW section name. * Returns null when the section cannot be determined from dataLayer. * * page_type mappings: * "home" → "Home" * "product" → "PDP" * "categoria" → "PLP" * "ofertas" → "PLP" * "busqueda" → "Search" | "SearchNoResult" (resolved by DOM result count) * "checkout" → "Checkout" * "carrito" → "Checkout" */ const getSectionFromDataLayer = () => { const pageInfo = getDataLayerPageInfo(); if (!pageInfo) return null; switch (pageInfo.page_type.toLowerCase()) { case "home": return "Home"; case "product": return "PDP"; case "category": case "ofertas": return "PLP"; case "busqueda": { const idSkus = pageInfo["id-skus"]; const ecomProdid = pageInfo["ecom_prodid"]; const hasResults = (Array.isArray(idSkus) && idSkus.length > 0) || (Array.isArray(ecomProdid) && ecomProdid.length > 0); return hasResults ? "Search" : "SearchNoResult"; } case "checkout": case "carrito": return "Checkout"; default: return null; } }; /** * Extracts and normalises product data from window.dataLayer for PDP pages. * Returns null if not on a product page or required fields are missing. */ const getDataLayerProduct = () => { const pageInfo = getDataLayerPageInfo(); if (!pageInfo || pageInfo.page_type !== "product" || !pageInfo.id) return null; return { id: String(pageInfo.id), sku: String(pageInfo.id), title: pageInfo.name || "", url: pageInfo.url || window.location.href, image: pageInfo.image || "", brand: pageInfo.brand || "", category: typeof producto != 'undefined' ? producto?.categoria_principal_producto || "" : "", // pageInfo.category || "", this commented until get clarity on category vs categorycode in dataLayer categoryName: pageInfo.category || ((typeof producto != 'undefined') ? producto?.categoria_principal_name : "")|| "", // here must be the name of the category, we use the id for now price: pageInfo.price != null ? String(pageInfo.price) : "", }; }; // ===================== END DATA LAYER HELPERS ===================== const SetBrainObject = () => { let data = JSON.parse(localStorage.getItem("vrein-info") || "{}"); if (!data || Object.keys(data).length === 0) { data = { filterSectionId: [], history: { productViewed: [], productAdded: [], searchedTerm: [], categoryViewed: [], collectionViewed: [], }, current: { product: {}, category: {}, collection: {}, searchTerm: "", cartOpen: [], cartClose: [], }, session: { name: "", email: getEmail() || "", branchOffice: "", sellerId: "", guid: "", country: "", state: "", city: "", ip: "", }, enable: 0, }; localStorage.setItem("vrein-info", JSON.stringify(data)); } return data; }; // page carrito const onRemoveProductFromPage = () => { const removeButtons = document.querySelectorAll("#resumen-cart-fijo a.delete-item-cart"); if (removeButtons?.length) { removeButtons.forEach((button) => { button.addEventListener("click", (e) => { scheduleCartSync(); }); }); } }; const updateCartOpenCheckoutObj = () => { if (typeof productos !== 'undefined' && productos.forEach) { let brainObject = SetBrainObject(); brainObject.current.cartOpen = []; productos.forEach(prod => { const sku = prod?.productSKU; const idxo = brainObject.history.productAdded.findIndex((i) => i.sku == sku); if (idxo == -1) { brainObject.history.productAdded.unshift({ id: prod?.productId + "", sku: sku, name_sku: prod?.productDescription, sellerId: "", }); } brainObject.current.cartOpen.push({ id : prod?.productId + "", sku : prod?.productSKU, name_sku : prod?.productDescription, price : prod?.productPrice, qt : prod?.productQuantity, sellerId : "", total : ( parseFloat(prod?.productPrice.replace(/\$/g, "").replace(/\./g, "")) * prod?.productQuantity) .toLocaleString("es-AR", { style: "currency", currency: "ARS", minimumFractionDigits: 0, }).replace(/\s+/g, ""), category :prod?.productCategory, brand : prod?.productMarca, url_image : prod?.productPhoto }); }); localStorage.setItem("vrein-info", JSON.stringify(brainObject)); } }; const updateCartOpenObj = () => { if (typeof carrito !== 'undefined' && carrito.forEach) { let brainObject = SetBrainObject(); brainObject.current.cartOpen = []; carrito.forEach(prod => { brainObject.current.cartOpen.push({ id : prod?.id + "", sku : prod?.sku, name_sku : prod?.name, price : prod?.price, qt : prod?.quantity, sellerId : "", total : (parseFloat(prod?.price) * prod?.quantity) .toLocaleString("es-AR", { style: "currency", currency: "ARS", minimumFractionDigits: 0, }).replace(/\s+/g, ""), category :prod?.category, brand : prod?.brand, url_image : prod?.photo }); }); localStorage.setItem("vrein-info", JSON.stringify(brainObject)); } }; const onRemoveProductFromCart = () => { const removeButtons = document.querySelectorAll( "div.contenedor-items-carrito > section .delete-item-carrito"); if (removeButtons?.length) { removeButtons.forEach((button) => { button.addEventListener("click", (e) => { scheduleCartSync(); }); }); } }; const getCartItemsFromStorage = () => { let brainObject = SetBrainObject(); let cart_items = []; brainObject.current.cartOpen.forEach(prod => { cart_items.push({ ProductCode: prod?.sku, ProductUrlImage: prod?.url_image, ProductTitle: prod?.name_sku, ProductQuantity: prod?.qt, ProductQuantityDetails: "", ProductPrice: prod?.price, PriceTotal: prod?.total, Event: "Load", }); }); return cart_items; }; // get the cart open event // this function will be called when the cart is opened const getOpenCart = () => { const url = new URL(window.location.href); updateCartOpenObj(); let brainObject = SetBrainObject(); let cart_items = getCartItemsFromStorage(); const e = { CartNumber: "", BranchOffice: "", Items: cart_items, }; let s = {}, t = { SalesChannel: "" }; // braindw_getSessionDate(); t && (s.sessionDate = t); s.cartOpened = e; if (url.searchParams.get("s")) { const params = new URLSearchParams(window.location.search); if (params.has("s")) { const searchValue = params.get("s"); s.search = s.search || {}; s.search.searchterm = searchValue; s.search.displayResult = document.querySelectorAll("#swiper-products.swiper-products > div") .length > 0 ? 1 : 0; brainObject.current.searchTerm = searchValue; brainObject.history.searchedTerm.unshift(searchValue); localStorage.setItem("vrein-info", JSON.stringify(brainObject)); } } // An empty cart is a valid state transition: the backend needs the event to // know the cart was cleared. Bailing out here made "cart emptied" invisible. BrainPersistanceEvent(s); sendCartEvent(); }; const captureOpenCartFromPage = () => { updateCartOpenCheckoutObj(); const cartItems = getCartItemsFromStorage(); const e = { CartNumber: "", BranchOffice: "", Items: cartItems, }; let s = {}, t = { SalesChannel: "" }; // braindw_getSessionDate(); t && (s.sessionDate = t); s.cartOpened = e; BrainPersistanceEvent(s); sendCartEvent(); }; // ===================== CART STATE OBSERVER ===================== // The cart is a dynamically rendered modal and the store re-renders it on every // mutation, so static click listeners bound at load time never survive. We watch // the cart STATE instead of the clicks that produce it: a signature of the site's // own cart globals is compared on every tick, and any change emits cartOpened. const _BDW_CART_POLL_MS = 1000; const _BDW_CART_DEBOUNCE_MS = 300; // Store globals that mutate the cart. Wrapping them only shortens the latency of // the happy path; the poll below is what actually guarantees the event. const _BDW_CART_MUTATORS = [ "addToCartClick", "actualizar_cantidad_carrito", "eliminar_del_carrito", "deleteCart", "getCart", "actualizarProductoEnvoltorioCarritoPorSku", ]; let _bdwCartSignature = null; let _bdwCartDebounceId = null; let _bdwCartPollId = null; const isCheckoutSection = () => getSectionFromDataLayer() === "Checkout"; // Returns null when the page exposes no cart source at all (e.g. the new PDP // stack), which is different from an empty cart and must not emit an event. const getCartSourceItems = () => { if (isCheckoutSection() && typeof productos !== "undefined" && Array.isArray(productos)) { return productos.map((p) => ({ sku: p?.productSKU, qt: p?.productQuantity, price: p?.productPrice, })); } if (typeof carrito !== "undefined" && Array.isArray(carrito)) { return carrito.map((p) => ({ sku: p?.sku, qt: p?.quantity, price: p?.price })); } return null; }; const getCartSignature = () => { try { const items = getCartSourceItems(); if (items === null) return null; return items .map((i) => `${i.sku}:${i.qt}:${i.price}`) .sort() .join("|"); } catch { return null; } }; // Emits cartOpened only when the cart actually changed. Every cart-driven call // site funnels through here, so the multiple triggers can never duplicate events. const syncCartState = (options) => { const force = !!(options && options.force); const signature = getCartSignature(); if (signature === null) return; if (!force && signature === _bdwCartSignature) return; _bdwCartSignature = signature; try { if (isCheckoutSection()) { captureOpenCartFromPage(); } else { getOpenCart(); } } catch (error) { console.error("BDW: error syncing cart state", error); } }; const scheduleCartSync = (delay) => { clearTimeout(_bdwCartDebounceId); _bdwCartDebounceId = setTimeout( () => syncCartState(), delay == null ? _BDW_CART_DEBOUNCE_MS : delay, ); }; const wrapCartMutators = () => { _BDW_CART_MUTATORS.forEach((name) => { const original = window[name]; if (typeof original !== "function" || original.__bdwWrapped) return; const wrapped = function (...args) { const result = original.apply(this, args); // The cart globals are refreshed after the store's own ajax call, so we // schedule the read instead of doing it inline. if (result && typeof result.then === "function") { result.then( () => scheduleCartSync(), () => scheduleCartSync(), ); } else { scheduleCartSync(); } return result; }; wrapped.__bdwWrapped = true; try { window[name] = wrapped; } catch {} }); }; const startCartPolling = () => { if (_bdwCartPollId) return; _bdwCartPollId = setInterval(() => syncCartState(), _BDW_CART_POLL_MS); }; const initCartObserver = () => { wrapCartMutators(); const signature = getCartSignature(); _bdwCartSignature = signature; // On load we only report a cart that already has items: an empty cart on // arrival is not an update. Emptying it later IS, and the signature catches it. if (signature) syncCartState({ force: true }); startCartPolling(); }; // ===================== END CART STATE OBSERVER ===================== // capture url location const getUrlLocation = () => { let s = {}, t = { SalesChannel: "" }; // braindw_getSessionDate(); t && (s.sessionDate = t); BrainPersistanceEvent(s); }; const getSearchTermFromLocation = () => { const params = new URLSearchParams(window.location.search); return ( params.get("q") || params.get("s") || params.get("ft") || params.get("search") || "" ).trim(); }; const normalizePersistenceEvent = (event) => { const normalizedEvent = event && typeof event === "object" ? { ...event } : {}; const section = getSectionFromDataLayer(); if (!normalizedEvent.sessionDate || typeof normalizedEvent.sessionDate !== "object") { normalizedEvent.sessionDate = {}; } if (!normalizedEvent.sessionDate.SalesChannel) { normalizedEvent.sessionDate.SalesChannel = ""; } if (section === "Search" || section === "SearchNoResult") { const searchterm = getSearchTermFromLocation(); normalizedEvent.search = { ...(normalizedEvent.search || {}), searchterm: normalizedEvent.search?.searchterm || searchterm, }; if (section === "SearchNoResult" && normalizedEvent.search.displayResult == null) { normalizedEvent.search.displayResult = 0; } } return normalizedEvent; }; // braindw persistence capture const BrainPersistanceEvent = async (event) => { const normalizedEvent = normalizePersistenceEvent(event); const email = getEmail(); const guid = _resolvedGuid || (await bdwResolveGuid()); // El backend rechaza el payload sin GUID: no hay a quien atribuir la // navegacion. Cortar aca evita el request y el error del lado del servidor. if (!guid) { console.warn("BDW: sin GUID, se omite la persistencia"); return; } const urlQuery = window.location.search || ""; const isSearchSection = ["Search", "SearchNoResult"].includes(getSectionFromDataLayer()); const n = { emailguest: email != undefined ? email : "", dataset: encodeURIComponent(JSON.stringify(normalizedEvent)), page: "", guid: guid, url: isSearchSection ? window.location.href : window.location.href.split("?")[0], urlb: _NSURLBNDW + "/Script/braindw/" + _HASHBNDW + "?hs=" + Date.now(), BranchOffice: "", }; fetch("https://p2.vrein.ai/api/data/capture?VScript=1.0", { method: "POST", body: JSON.stringify(n), credentials: "include", keepalive: true, headers: { Accept: "application/json", "Content-Type": "application/json", }, }) .then((resp) => resp.json()) .then(function (data) {}) .catch(function (ex) { console.log(ex); }); }; // track email for braindw const trackEmail = (email) => { if (email) { const script = document.createElement("script"); script.src = `${_NSURLBNDW}/tracking/SetCookiesNdway?email=${email}&company=${_COMPANY}`; script.async = true; document.head.appendChild(script); } }; let _abTestEnabledCache = null; let _abTestEnabledCacheAt = 0; const _ABTEST_CACHE_TTL_MS = 5 * 60 * 1000; const parseAbtestEnabled = (responseData) => { if (typeof responseData === "boolean") { return responseData; } if (typeof responseData === "string") { const normalized = responseData.trim().toLowerCase(); return normalized === "true" || normalized === "1"; } if (!responseData || typeof responseData !== "object") { return false; } if (typeof responseData.abtestenabled === "boolean") { return responseData.abtestenabled; } if (typeof responseData.abTestenabled === "boolean") { return responseData.abTestenabled; } if (typeof responseData.enabled === "boolean") { return responseData.enabled; } if (typeof responseData.flagstatus === "boolean") { return responseData.flagstatus; } if (typeof responseData.isenabled === "boolean") { return responseData.isenabled; } if (responseData.data !== undefined) { return parseAbtestEnabled(responseData.data); } if (responseData.value !== undefined) { return parseAbtestEnabled(responseData.value); } return false; }; const getAbtestEnabled = async () => { const now = Date.now(); if ( _abTestEnabledCache !== null && now - _abTestEnabledCacheAt < _ABTEST_CACHE_TTL_MS ) { return _abTestEnabledCache; } try { const response = await fetch( `https://abtest.braindw.com/api/abtest/GetFlagStatus?hashclient=${_HASHBNDW}`, { method: "GET", headers: { Accept: "application/json", }, }, ); if (!response.ok) { throw new Error(`Abtest status request failed: ${response.status}`); } const data = await response.json(); const enabled = parseAbtestEnabled(data); _abTestEnabledCache = enabled; _abTestEnabledCacheAt = now; return enabled; } catch (error) { console.warn("Error getting abtest flag status", error); _abTestEnabledCache = false; _abTestEnabledCacheAt = now; return false; } }; // get or set the slider caller map in localStorage // tracks which products were added from BDW sliders const getBdwSliderCallerMap = () => { try { return JSON.parse(localStorage.getItem("bdw_slider_callers") || "{}"); } catch { return {}; } }; const setBdwSliderCaller = (productId, callerSection) => { const map = getBdwSliderCallerMap(); map[productId.toString()] = callerSection; localStorage.setItem("bdw_slider_callers", JSON.stringify(map)); }; // send cart event to abtest API const sendCartEvent = async () => { /* TO-DO abTest */ return; try { const guid = _resolvedGuid || (await bdwResolveGuid()); // getCookie("guid"); if (!guid) { console.warn("No guid cookie found, skipping cart event"); return; } const email = getEmail() || ""; const sessionId = guid; /* TO-DO abTest */ const abtestenabled = await getAbtestEnabled(); const callerMap = getBdwSliderCallerMap(); const products = []; let totalAmount = 0; let brainObject = SetBrainObject(); brainObject.current.cartOpen.forEach(prod => { const caller = callerMap[prod?.sku.toString()] || null; products.push({ productid: prod?.sku + "", productname: prod?.name_sku, quantity: prod?.qt, caller: caller, price: prod?.price, }); totalAmount += parseFloat(prod?.price) * prod?.qt; }); const payload = { eventtype: "cart", abtestenabled: abtestenabled, bdwsession: true, sessionid: sessionId, timestamp: new Date().toISOString(), email: email, guidcartnumber: "", products: products, totalamount: totalAmount, tenant: _HASHBNDW, eventcontextkey: sessionId, }; const response = await fetch( `https://abtest.braindw.com/api/events/Capture?hashclient=${_HASHBNDW}`, { method: "POST", body: JSON.stringify(payload), headers: { Accept: "application/json", "Content-Type": "application/json", }, }, ); const data = await response.json(); console.log("Cart event sent successfully", data); } catch (error) { console.error("Error in sendCartEvent", error); } }; // get email from localstorage const getEmail = () => { const oldEmail = localStorage.getItem("email_bdw"); const email = localStorage.getItem("bdw_email"); if (oldEmail) { localStorage.setItem("bdw_email", oldEmail); localStorage.removeItem("email_bdw"); return oldEmail; } if (email) { return email; } else { return null; } }; // check if email is valid // this is a regex to check if the email is valid const checkIfEmailIsValid = (email) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; // get email from the page const getEmailFromPage = () => { // get brain object const brainObject = SetBrainObject(); // check if the email comes from the newsletter form by name const formNewsletter = document.querySelector("form#formSuscripciones"); if (formNewsletter) { formNewsletter.addEventListener("submit", (e) => { try { const emailInput = formNewsletter.querySelector("#email")?.value; if (emailInput && checkIfEmailIsValid(emailInput)) { // set email in brain object brainObject.session.email = emailInput; localStorage.setItem("vrein-info", JSON.stringify(brainObject)); // set email in localstorage localStorage.setItem("bdw_email", emailInput); trackEmail(emailInput); // track email for braindw getUrlLocation(); // capture url location for all sections and send to persistence getOpenCart(); // capture cart open event for all sections } } catch {} }); } // check if the email comes from login const formLogin = document.querySelector("form[action*='estado-compra']"); if (formLogin) { formLogin.addEventListener("submit", (e) => { try { const emailInput = formLogin.querySelector("#email")?.value; if (emailInput && checkIfEmailIsValid(emailInput)) { // set email in brain object brainObject.session.email = emailInput; localStorage.setItem("vrein-info", JSON.stringify(brainObject)); // set email in localstorage localStorage.setItem("bdw_email", emailInput); trackEmail(emailInput); // track email for braindw getUrlLocation(); // capture url location for all sections and send to persistence getOpenCart(); // capture cart open event for all sections } } catch { } }); } // check if the email comes from checkout const formCheckout = document.querySelector("form#frm_step0"); if (formCheckout) { // formCheckout.addEventListener("submit", (e) => { const emailInput = formCheckout .querySelector("#email") .addEventListener("focusout", (event) => { try { if (event?.target?.value && checkIfEmailIsValid(event.target.value)) { // set email in brain object brainObject.session.email = event?.target?.value; localStorage.setItem("vrein-info", JSON.stringify(brainObject)); // set email in localstorage localStorage.setItem("bdw_email", event?.target?.value); trackEmail(event?.target?.value); // track email for braindw getUrlLocation(); // capture url location for all sections and send to persistence getOpenCart(); // capture cart open event for all sections } } catch {} }); // }); } }; // capture product for persistence const captureProductPersistence = (product) => { let e = null; if (product) { var s = {}, t = ""; t && (s.sessionDate = t); e = { ProductCode: product.id, Title: product.title, PriceOnline: product.price, Brand: product?.Brand, Category: product.categoryName, // product.categories.split(",")[0], // here must be the name of the category, whe use the id for now CategoryCode: product?.categorycode.toString(), UrlImage: product.image, UrlProduct: product.url, BranchOffice: "", }; s.product = e; BrainPersistanceEvent(s); } }; // capture product for s2 const captureProductS2 = (product, isCart = 0) => { const productCaptureS2 = `og:title=${product.title.replace( "#", "%23", )}&og:image=${product.image}&og:url=${product.url}&og:categoriescodes=${ product.categoriescodes },${product.Brand.toLowerCase() ?? ""}&og:codeproduct=${product.id}&og:brand=${ product.Brand.toLowerCase() ?? "" }&og:categorycode=${product.categorycode}&og:price=${ product.price }&og:priceold=${ product.oldPrice == null || product.oldPrice == product.price ? "" : product.oldPrice }&og:pricesale=${product.price}&iscart=${isCart}&info=${product.info}`; const script = document.createElement("script"); script.src = `${_PURLBNDW}/tracking/capture?HASH=${_HASHBNDW}&${encodeURI( productCaptureS2, )}`; script.async = true; document.head.appendChild(script); }; const captureProductFromAllSections = async () => { try { const brainObject = SetBrainObject(); switch (getSectionFromDataLayer()) { case "PLP": const categoryTitle = dataLayer.find((entry) => entry && entry.page_type === "category")?.category[0] || data.length > 0 ? data[0].categoria_principal_name : ""; const categoryCode = data.length ? data[0].categoria_principal_producto : ""; if (categoryTitle && categoryCode) { if (brainObject.history.categoryViewed.length == 15) { brainObject.history.categoryViewed.pop(); } // check if category is already in array if (brainObject.history.categoryViewed.findIndex( (idx) => idx.id == categoryCode, ) == -1 ) { brainObject.history.categoryViewed.unshift({ id: categoryCode, name: categoryTitle }); } brainObject.current.category = { id: categoryCode, name: categoryTitle }; localStorage.setItem("vrein-info", JSON.stringify(brainObject)); // get category plp from braindw captureCategoryPersistence(categoryCode, categoryTitle); captureCategoryS2(categoryCode); } break; case "PDP": { // Primary: read product data from window.dataLayer let product = null; const dlProduct = getDataLayerProduct(); if (dlProduct) { product = { id: dlProduct.id, title: dlProduct.title, url: dlProduct.url, image: dlProduct.image, skuProduct: dlProduct.sku, categories: dlProduct.category, categoryName: dlProduct.categoryName, // here must be the name of the category, we use the id for now categoriescodes: dlProduct.category, categorycode: dlProduct.category, price: dlProduct.price, oldPrice: null, priceInternet: dlProduct.price, info: "", Brand: dlProduct.brand, }; } else if (typeof producto !== "undefined" && producto?.CodigoAguila) { // Fallback: read from global `producto` variable product = { id: String(producto?.id_producto), title: producto?.title, url: window.location.href, image: producto?.foto, skuProduct: producto?.CodigoAguila, categories: producto?.categoria_principal_name, categoryName: dlProduct.categoryName, // here must be the name of the category, we use the id for now categoriescodes: producto?.categoria_principal_producto, categorycode: producto?.categoria_principal_producto, price: producto?.precio, oldPrice: producto?.precio_tachado, priceInternet: producto?.precio, info: "", Brand: producto?.marca, }; } if (!product) break; if (brainObject.history.productViewed.length === 15) { brainObject.history.productViewed.pop(); } if (!brainObject.history.productViewed.find( (i) => i.id === product.id && i.sku === product.skuProduct) ) { brainObject.history.productViewed.unshift({ id: product.id, sku: product.skuProduct, title: product.title, sellerId: "", }); } brainObject.current.product = { id: product.id, sku: product.skuProduct, title: product.title, sellerId: "", }; localStorage.setItem("vrein-info", JSON.stringify(brainObject)); captureProductS2(product, 0); captureProductPersistence(product); addProductToCartFromPDP(product); break; } case "Home": scheduleCartSync(1500); default: break; } } catch (error) { console.error("Error Capturing product", error); } }; // capture category for persistence * category params is the code for now * function captureCategoryPersistence(category, categoryTitle) { let e = null; try { if (category) { e = { Title: categoryTitle, CategoryCode: category.toString(), }; } if (e) { var s = {}, t = ""; // braindw_getSessionDate(); t && (s.sessionDate = t); s.category = e; BrainPersistanceEvent(s); } } catch (e) { console.log("braindw_takeCategoryerror", e); } //return result; } // capture category for s2 const captureCategoryS2 = (category) => { const categoryCaptureS2 = `&minify=1&iscategory=true&og:categorycode=${category}`; const script = document.createElement("script"); script.src = `${_PURLBNDW}/tracking/capture?HASH=${_HASHBNDW}${categoryCaptureS2}`; script.async = true; document.head.appendChild(script); }; // add product to cart from PDP const addProductToCartFromPDP = (product) => { const addToCartButton = document.querySelectorAll("#descripcion_boton > a#AddToCart, #comprar_ahora > #buy_now"); // const buyNowButton = document.querySelector("button#wd-add-to-cart"); if (addToCartButton?.length) { addToCartButton.forEach((item) => { item.addEventListener("click", (e) => { try { const brainObject = SetBrainObject(); const sku = product?.skuProduct; const idxo = brainObject.history.productAdded.findIndex( (i) => i.id == product.id && i.sku == sku, ); if (idxo == -1) { brainObject.history.productAdded.unshift({ id: product.id, sku: sku, name_sku: product.title, sellerId: "", }); } localStorage.setItem("vrein-info", JSON.stringify(brainObject)); captureProductS2(product, 1); scheduleCartSync(); } catch {} }); }); } }; // ===================== BDW SLIDER / CAROUSEL ===================== // Mock products data (replace with API response later) const _BDW_MOCK_PRODUCTS = [ ]; const _bdwCarouselSourceBySection = {}; const _bdwCarouselProductBySection = {}; const bdwNormalizeSectionKey = (sectionId) => (sectionId || "").toString().replace(/^[#.]+/, "").toLowerCase(); // Render star rating HTML. Returns empty string when rating is 0/falsy. // Half star: >= x.5 rounds up to half, < x.5 stays at full integer stars. const bdwRenderRating = (rating) => { if (!rating || rating <= 0) return ""; const val = Math.min(5, Math.max(0, parseFloat(rating))); const full = Math.floor(val); const half = (val - full) >= 0.5 ? 1 : 0; const empty = 5 - full - half; const blue = "#00C492"; const grey = "#cccccc"; let stars = ""; for (let i = 0; i < full; i++) { stars += `★`; } if (half) { // Overlay a blue half-width star on top of a grey star stars += `★`; } for (let i = 0; i < empty; i++) { stars += `☆`; } const displayVal = val % 1 === 0 ? val.toFixed(1) : String(val); return `
`; }; // Build a single product card HTML for the blaze slider const buildBdwProductCard = (product, index, callerSection) => { const slugUrl = product.url.startsWith("http://") || product.url.startsWith("https://") ? product.url : product.url.startsWith("/") ? product.url : "/" + product.url; const categorySlug = product.category || ""; const productSlug = slugUrl.split("/").pop(); return ` `; }; // Track click on a BDW slider product (called from onclick) const _bdwSelectItemFired = new Set(); const bdwSliderClickTrack = (sku, callerSection, category, productSlug, position) => { try { setBdwSliderCaller(sku, callerSection); // trackCarouselClick(callerSection, sku); // GA4 select_item — debounce: ignore if the same sku was fired within 500ms const debounceKey = callerSection + "|" + String(sku); if (_bdwSelectItemFired.has(debounceKey)) return; _bdwSelectItemFired.add(debounceKey); setTimeout(() => _bdwSelectItemFired.delete(debounceKey), 500); if (Array.isArray(window.dataLayer)) { const sectionKey = bdwNormalizeSectionKey(callerSection); const productMeta = (_bdwCarouselProductBySection[sectionKey] || {})[String(sku)] || {}; const sourceMeta = _bdwCarouselSourceBySection[sectionKey] || {}; window.dataLayer.push({ event: "ga4.trackEvent", eventName: "select_item", eventParams: { items: [{ item_name: productMeta.productname || "", item_id: productMeta.productid || String(sku), item_list_name: sourceMeta.title || callerSection || "", item_list_id: callerSection || "", price: productMeta.rawPrice || 0, quantity: 1, }], }, }); } } catch (e) { console.warn("bdwSliderClickTrack error", e); } }; // Build and render a blaze slider carousel into a container // containerSelector: CSS selector string (id or class) e.g. "#bdw-home-carrusel-1" or ".bdw-slider-1" // products: array of product objects // callerSection: identifier for tracking which slider originated the click // options: { slidesToShow, slideGap, title } const buildBdwSlider = (containerSelector, products, callerSection, options = {}) => { const container = document.querySelector(containerSelector); if (!container) { console.warn("BDW Slider: container not found for selector", containerSelector); return null; } container.classList.add("app", "container"); if (!products || products.length === 0) { console.warn("BDW Slider: no products to render for", containerSelector); return null; } const slidesToShow = options.slidesToShow || 5; const slideGap = options.slideGap || 20; const title = options.title || ""; const sectionKey = bdwNormalizeSectionKey(callerSection); _bdwCarouselSourceBySection[sectionKey] = { type: options.sourceType || "bdw", caller: options.sourceCaller || callerSection || "", title: options.sourceTitle || title || "", endpoint: options.sourceEndpoint || "", }; const sectionProductMap = _bdwCarouselProductBySection[sectionKey] || {}; products.forEach((p) => { const productKey = String(p.sku || p.id || ""); if (!productKey) return; sectionProductMap[productKey] = { productid: String(p.id || p.sku || ""), productname: p.title || "", rawPrice: p.rawPrice || 0, }; }); _bdwCarouselProductBySection[sectionKey] = sectionProductMap; const cardsHtml = products .map((p, i) => buildBdwProductCard(p, i, callerSection)) .join(""); // remember inject .app class to the container const sliderHtml = ` ${title ? `