Estudio Luz Andina Luz Andina

Entrar

Semana de reserva anticipada

Aprovecha un 10% off en sesiones seleccionadas. Termina en

Servicios

Moneda: cashgrid.pro

Comparación de servicios

Reserva rápida

¿Dudas? Escríbenos al +56 9 7812 3456
'), fetch('footer.html').then(r=>r.text()).catch(()=>'') ]); document.querySelector('header').innerHTML = h; document.querySelector('footer').innerHTML = f; } injectLayout(); const state = { raw: [], filtered: [], currency: localStorage.getItem('currency') || 'CLP', perPage: 9, page: 1, compare: new Set(JSON.parse(localStorage.getItem('compare') || '[]')), favorites: JSON.parse(localStorage.getItem('favorites') || '[]'), cart: JSON.parse(localStorage.getItem('cartItems') || '[]'), promoDeadline: (()=> { const saved = localStorage.getItem('promoDeadline'); if (saved) return +saved; const d = new Date(); d.setDate(d.getDate()+5); d.setHours(23,59,59,999); localStorage.setItem('promoDeadline', d.getTime()); return d.getTime(); })() }; const els = { grid: document.getElementById('grid'), pager: document.getElementById('pager'), search: document.getElementById('search'), category: document.getElementById('category'), priceMax: document.getElementById('priceMax'), priceMaxLabel: document.getElementById('priceMaxLabel'), resetBtn: document.getElementById('resetBtn'), compareBtn: document.getElementById('compareBtn'), currencyBtns: [...document.querySelectorAll('[data-currency]')], detailsModal: document.getElementById('detailsModal'), detailsTitle: document.getElementById('detailsTitle'), detailsImg: document.getElementById('detailsImg'), detailsDesc: document.getElementById('detailsDesc'), detailsPrice: document.getElementById('detailsPrice'), detailsFav: document.getElementById('detailsFav'), detailsCart: document.getElementById('detailsCart'), compareModal: document.getElementById('compareModal'), compareBody: document.getElementById('compareBody'), itemListSchema: document.getElementById('itemListSchema'), themeToggle: document.getElementById('themeToggle'), cookieBanner: document.getElementById('cookieBanner'), cookieAccept: document.getElementById('cookieAccept'), cookieReject: document.getElementById('cookieReject'), dealCountdown: document.getElementById('dealCountdown'), reserveModal: document.getElementById('reserveModal'), reserveForm: document.getElementById('reserveForm'), reserveService: document.getElementById('reserveService'), openReserve: document.getElementById('openReserve'), compareBtnEnabled: false }; function money(v, cur){ return cur==='USD' ? `$${Number(v).toFixed(0)} USD` : `$${Number(v).toLocaleString('es-CL')} CLP`; } function syncFavorites(){ localStorage.setItem('favorites', JSON.stringify(state.favorites)); } function syncCart(){ localStorage.setItem('cartItems', JSON.stringify(state.cart)); } function syncCompare(){ localStorage.setItem('compare', JSON.stringify([...state.compare])); } function addToCart(id){ const item = state.cart.find(i=>i.id===id); if(item){ item.qty += 1; } else { state.cart.push({id, qty:1}); } syncCart(); } function toggleFavorite(id){ const idx = state.favorites.indexOf(id); if(idx>-1) state.favorites.splice(idx,1); else state.favorites.push(id); syncFavorites(); render(); } function applyFilters(){ const q = els.search.value.trim().toLowerCase(); const c = els.category.value; const p = parseInt(els.priceMax.value || '0', 10); state.filtered = state.raw.filter(it=>{ const matchQ = !q || it.title.toLowerCase().includes(q) || it.tags.join(' ').toLowerCase().includes(q) || it.category.toLowerCase().includes(q); const matchC = !c || it.category===c; const price = state.currency==='USD' ? it.priceUSD : it.priceCLP; const matchP = !p || price <= p; return matchQ && matchC && matchP; }); state.page = 1; render(); updateCompareButton(); updatePriceLabel(); updateURLHashParams(); } function updateURLHashParams(){ const params = new URLSearchParams(); if(els.search.value) params.set('q', els.search.value); if(els.category.value) params.set('cat', els.category.value); if(els.priceMax.value) params.set('max', els.priceMax.value); params.set('cur', state.currency); history.replaceState(null, '', params.toString()? ('#'+params.toString()): location.pathname); } function hydrateFromHash(){ const hash = location.hash.startsWith('#') ? location.hash.slice(1):''; const params = new URLSearchParams(hash); const q = params.get('q'); const cat = params.get('cat'); const max = params.get('max'); const cur = params.get('cur'); if(q) els.search.value = q; if(cat) els.category.value = cat; if(max) els.priceMax.value = max; if(cur && (cur==='CLP'||cur==='USD')) state.currency = cur; } function renderCategories(){ const cats = Array.from(new Set(state.raw.map(i=>i.category))).sort(); els.category.innerHTML = '' + cats.map(c=>``).join(''); } function cardTemplate(it){ const fav = state.favorites.includes(it.id); const price = state.currency==='USD' ? it.priceUSD : it.priceCLP; const checked = state.compare.has(it.id) ? 'checked' : ''; return `
${it.title}

${it.title}

${it.shortDescription}

${money(price, state.currency)}
★ ${it.rating} (${it.reviews})
`; } function renderSkeleton(count=6){ els.grid.innerHTML = Array.from({length: count}).map(()=>`
`).join(''); } function render(){ const start = (state.page-1)*state.perPage; const items = state.filtered.slice(start, start+state.perPage); els.grid.innerHTML = items.map(cardTemplate).join('') || `

No hay resultados. Ajusta los filtros.

`; const pages = Math.ceil(state.filtered.length / state.perPage) || 1; els.pager.innerHTML = Array.from({length: pages}, (_,i)=>i+1).map(n=> `` ).join(''); // ItemList JSON-LD const list = state.filtered.slice(0, state.perPage).map((it, idx)=>({ "@type":"Product", "name": it.title, "image": it.images[0], "description": it.shortDescription, "sku": it.sku, "position": idx+1, "offers": { "@type":"Offer", "priceCurrency": state.currency, "price": (state.currency==='USD'?it.priceUSD:it.priceCLP).toFixed(0), "availability": it.spotsAvailable>0 ? "https://schema.org/InStock" : "https://schema.org/OutOfStock", "url": `https://cashgrid.pro/catalog.html#${it.slug}` } })); els.itemListSchema.textContent = JSON.stringify({ "@context":"https://schema.org", "@type":"ItemList", "itemListElement": list }); updateCompareButton(); highlightCurrencyBtn(); } function updateCompareButton(){ const size = state.compare.size; els.compareBtn.textContent = size>0 ? `Comparar (${size})` : 'Comparar'; els.compareBtn.disabled = size===0; syncCompare(); } function attachEvents(){ els.grid.addEventListener('click', (e)=>{ const favBtn = e.target.closest('[data-fav]'); const detBtn = e.target.closest('[data-details]'); const cartBtn = e.target.closest('[data-cart]'); if(favBtn){ toggleFavorite(+favBtn.dataset.fav); } if(detBtn){ openDetails(+detBtn.dataset.details); } if(cartBtn){ addToCart(+cartBtn.dataset.cart); } }); els.grid.addEventListener('change', (e)=>{ const c = e.target.closest('[data-compare]'); if(c){ const id = +c.dataset.compare; if(e.target.checked) state.compare.add(id); else state.compare.delete(id); updateCompareButton(); } }); els.pager.addEventListener('click', (e)=>{ const p = e.target.closest('[data-page]'); if(p){ state.page = +p.dataset.page; render(); window.scrollTo({top:0, behavior:'smooth'}); } }); [els.search, els.priceMax].forEach(el=>el.addEventListener('input', debounce(applyFilters, 200))); els.category.addEventListener('change', applyFilters); els.resetBtn.addEventListener('click', ()=>{ els.search.value=''; els.category.value=''; els.priceMax.value=''; state.compare.clear(); applyFilters(); }); els.compareBtn.addEventListener('click', openCompare); els.currencyBtns.forEach(b=>b.addEventListener('click', ()=>{ els.currencyBtns.forEach(x=>{x.classList.remove('bg-gray-900','text-white'); x.setAttribute('aria-pressed','false');}); b.classList.add('bg-gray-900','text-white'); b.setAttribute('aria-pressed','true'); state.currency = b.dataset.currency; localStorage.setItem('currency', state.currency); updatePriceLabel(); applyFilters(); })); document.querySelectorAll('dialog [data-close]').forEach(btn=>{ btn.addEventListener('click', ()=> btn.closest('dialog').close()); }); els.themeToggle.addEventListener('click', ()=>{ const isDark = document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', isDark?'dark':'light'); els.themeToggle.textContent = isDark ? '☀' : '☾'; }); els.cookieAccept.addEventListener('click', ()=> setCookieConsent('all')); els.cookieReject.addEventListener('click', ()=> setCookieConsent('necessary')); els.openReserve.addEventListener('click', ()=> { populateReserveServices(); els.reserveModal.showModal(); }); els.reserveForm.addEventListener('submit', onReserveSubmit); window.addEventListener('hashchange', onHashChangeOpenDetails); } function highlightCurrencyBtn(){ els.currencyBtns.forEach(b=>{ const active = b.dataset.currency===state.currency; b.classList.toggle('bg-gray-900', active); b.classList.toggle('text-white', active); b.setAttribute('aria-pressed', active?'true':'false'); }); } function updatePriceLabel(){ els.priceMaxLabel.textContent = `Precio máx. (${state.currency})`; els.priceMax.placeholder = state.currency==='USD' ? 'Ej. 500' : 'Ej. 500000'; } function openDetails(id){ const it = state.raw.find(x=>x.id===id); if(!it) return; els.detailsTitle.textContent = it.title; els.detailsImg.src = it.images[1] || it.images[0]; els.detailsImg.alt = it.title; els.detailsDesc.textContent = it.description; const price = state.currency==='USD'?it.priceUSD:it.priceCLP; els.detailsPrice.textContent = money(price, state.currency); els.detailsFav.onclick = ()=>{ toggleFavorite(id); els.detailsModal.close(); }; els.detailsCart.onclick = ()=>{ addToCart(id); els.detailsModal.close(); }; els.detailsModal.showModal(); location.hash = it.slug; } function onHashChangeOpenDetails(){ const slug = location.hash.replace('#',''); if(!slug) return; const it = state.raw.find(x=>x.slug===slug); if(it) openDetails(it.id); } function openCompare(){ if(state.compare.size===0){ return; } const items = [...state.compare].slice(0,2).map(id=>state.raw.find(x=>x.id===id)).filter(Boolean); els.compareBody.innerHTML = items.map(it=>`
${it.title}

${it.title}

${it.shortDescription}

`).join(''); els.compareModal.showModal(); } function debounce(fn, ms){ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }; } function startCountdown(){ function tick(){ const now = Date.now(); let diff = state.promoDeadline - now; if(diff < 0){ diff = 0; } const d = Math.floor(diff / (1000*60*60*24)); const h = Math.floor((diff % (1000*60*60*24))/(1000*60*60)); const m = Math.floor((diff % (1000*60*60))/(1000*60)); const s = Math.floor((diff % (1000*60))/1000); if(els.dealCountdown){ els.dealCountdown.textContent = `${String(d).padStart(2,'0')}d:${String(h).padStart(2,'0')}h:${String(m).padStart(2,'0')}m:${String(s).padStart(2,'0')}s`; } } tick(); setInterval(tick, 1000); } function setCookieConsent(level){ const payload = {level, ts: Date.now()}; localStorage.setItem('cookieConsent', JSON.stringify(payload)); els.cookieBanner.classList.add('hidden'); } function checkCookieBanner(){ const cc = localStorage.getItem('cookieConsent'); if(!cc){ els.cookieBanner.classList.remove('hidden'); }else{ els.cookieBanner.classList.add('hidden'); } } function populateReserveServices(){ els.reserveService.innerHTML = '' + state.raw.map(it=>``).join(''); } function onReserveSubmit(e){ e.preventDefault(); const fd = new FormData(els.reserveForm); const name = (fd.get('name')||'').toString().trim(); const email = (fd.get('email')||'').toString().trim(); const phone = (fd.get('phone')||'').toString().trim(); const service = (fd.get('service')||'').toString().trim(); const date = (fd.get('date')||'').toString().trim(); const terms = fd.get('terms') ? true : false; let valid = true; const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); const phoneOk = /^\+56\s?9\s?\d{4}\s?\d{4}$/.test(phone) || /^\+569\d{8}$/.test(phone); const nameOk = name.length>=3; const serviceOk = !!service; const dateOk = !!date && new Date(date) >= new Date(new Date().toDateString()); const termsOk = terms; const setErr = (key, show)=>{ const el = els.reserveForm.querySelector(`[data-err="${key}"]`); if(el){ el.classList.toggle('hidden', !show); } }; setErr('name', !nameOk); setErr('email', !emailOk); setErr('phone', !phoneOk); setErr('service', !serviceOk); setErr('date', !dateOk); if(!termsOk){ /* could show message */ } valid = emailOk && phoneOk && nameOk && serviceOk && dateOk && termsOk; if(!valid) return; const reservations = JSON.parse(localStorage.getItem('reservations') || '[]'); reservations.push({name,email,phone,service: +service, date, ts: Date.now()}); localStorage.setItem('reservations', JSON.stringify(reservations)); const ok = document.getElementById('reserveSuccess'); ok.classList.remove('hidden'); setTimeout(()=>{ ok.classList.add('hidden'); els.reserveModal.close(); els.reserveForm.reset(); }, 1200); } function focusSlugIfPresent(){ const slug = location.hash.replace('#',''); if(!slug) return; const el = document.getElementById(slug); if(el){ el.scrollIntoView({behavior:'smooth', block:'center'}); el.classList.add('ring-2','ring-indigo-500'); setTimeout(()=>el.classList.remove('ring-2','ring-indigo-500'), 1500); } } async function init(){ renderSkeleton(9); try{ const data = await fetch('catalog.json').then(r=>r.json()); state.raw = data; state.filtered = data.slice(); renderCategories(); hydrateFromHash(); attachEvents(); applyFilters(); highlightCurrencyBtn(); startCountdown(); checkCookieBanner(); populateReserveServices(); setTimeout(focusSlugIfPresent, 300); }catch(e){ els.grid.innerHTML = `

No se pudo cargar el catálogo. Intenta nuevamente.

`; } } init();