/* global React, ReactDOM, Header, Hero, Home, Taller, Catalog, Modelo, Ficha, Clientes, Cursos, Showroom, Wishlist, Modal, useUI, TweaksPanel, useTweaks, TweakSection, TweakRadio, TweakToggle, TweakColor, TweakSelect */ const { useState, useEffect } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "hero": "D", "accent": "#C13B1F", "density": "normal", "card": "minimal", "prices": true, "view": "desktop" }/*EDITMODE-END*/; /* ===== URLs REALES ===== Convierte el estado de ruta interno a una URL legible y viceversa. Esto da a cada página y producto su propia dirección compartible, indexable por Google y trackeable en Analytics. */ function slugify(str) { return (str || "") .toString() .normalize("NFD").replace(/[\u0300-\u036f]/g, "") .toLowerCase() .trim() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); } // route -> path string (ej: { page:"ficha", params:{productId:"ME-003"} } -> "/producto/me-003") function routeToPath(route) { const { page, params = {} } = route; switch (page) { case "home": return "/"; case "catalog": { const cat = params.filter && params.filter.cat; return cat && cat !== "all" ? `/${slugify(cat)}` : "/catalogo"; } case "modelo": return `/modelo/${slugify(params.modelo)}`; case "ficha": { // URL = /{categoria}/{slug-legible} const cat = window.CATALOGO || []; const prod = cat.find((p) => (p.id || "").toUpperCase() === (params.productId || "").toUpperCase()); if (prod && prod.catSlug && prod.slug) return `/${prod.catSlug}/${prod.slug}`; if (prod && prod.slug) return `/producto/${prod.slug}`; return `/producto/${slugify(params.productId)}`; } case "clientes": return "/clientes"; case "cursos": return "/cursos"; case "showroom": return "/showroom"; case "taller": return "/taller"; default: return "/"; } } // path string -> route (lee la URL del navegador al cargar o al navegar) function pathToRoute(pathname) { const parts = pathname.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean); if (parts.length === 0) return { page: "home", params: {} }; const [seg, val] = parts; // Rutas fijas primero if (seg === "catalogo") return { page: "catalog", params: { filter: {} } }; if (seg === "modelo") { const cat = window.CATALOGO || []; const match = cat.find((p) => slugify(p.nombre) === val); return { page: "modelo", params: { modelo: match ? match.nombre : val } }; } if (seg === "clientes") return { page: "clientes", params: {} }; if (seg === "cursos") return { page: "cursos", params: {} }; if (seg === "showroom") return { page: "showroom", params: {} }; if (seg === "taller") return { page: "taller", params: {} }; // Respaldo: URLs viejas /producto/{slug} if (seg === "producto") { const cat = window.CATALOGO || []; const match = cat.find((p) => p.slug === val) || cat.find((p) => slugify(p.id) === val); return { page: "ficha", params: { productId: match ? match.id : (val || "").toUpperCase() } }; } // ¿El primer segmento es una categoría? const labels = window.CAT_LABELS || {}; const catKey = Object.keys(labels).find((k) => slugify(k) === seg); if (catKey) { const cat = window.CATALOGO || []; if (val) { // /{categoria}/{slug} -> producto const match = cat.find((p) => p.catSlug === seg && p.slug === val); if (match) return { page: "ficha", params: { productId: match.id } }; // No encontró producto -> tratar como categoría } // /{categoria} -> catálogo filtrado return { page: "catalog", params: { filter: { cat: catKey } } }; } return { page: "home", params: {} }; } function App() { // route: { page, params } — se inicializa leyendo la URL actual const [route, setRouteRaw] = useState(() => pathToRoute(window.location.pathname)); const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const ui = useUI(); // Cada cambio de ruta pushea estado al historial Y actualiza la URL real const setRoute = (newRoute, replace = false) => { setRouteRaw(newRoute); const path = routeToPath(newRoute); if (replace) { window.history.replaceState(newRoute, "", path); } else { window.history.pushState(newRoute, "", path); } pushDataLayer(newRoute, path); }; // Envía eventos al dataLayer de GTM (GA4 + Meta Pixel los recogen desde ahí) function pushDataLayer(route, path) { window.dataLayer = window.dataLayer || []; // Pageview virtual (toda SPA lo necesita) window.dataLayer.push({ event: "spa_page_view", page_path: path, page_title: route.page, }); // Evento de ecommerce: vio un producto if (route.page === "ficha") { const cat = window.CATALOGO || []; const p = cat.find((x) => (x.id || "").toUpperCase() === (route.params.productId || "").toUpperCase()); if (p) { window.dataLayer.push({ ecommerce: null }); // limpia el objeto previo window.dataLayer.push({ event: "view_item", ecommerce: { currency: "COP", value: p.precio, items: [{ item_id: p.id, item_name: p.nombre, item_variant: p.variante || "", item_category: (window.CAT_LABELS && window.CAT_LABELS[p.categoria]) || p.categoria, price: p.precio, }], }, }); } } // Evento: vio una lista (catálogo o categoría) if (route.page === "catalog") { window.dataLayer.push({ event: "view_item_list", item_list_name: (route.params.filter && route.params.filter.cat) || "catalogo-completo", }); } } // Escucha el botón atrás del navegador useEffect(() => { // Reemplaza el estado inicial con la ruta leída de la URL (deep-link directo) const initial = pathToRoute(window.location.pathname); window.history.replaceState(initial, "", routeToPath(initial)); const onPop = (e) => { if (e.state && e.state.page) { setRouteRaw(e.state); } else { setRouteRaw(pathToRoute(window.location.pathname)); } }; window.addEventListener("popstate", onPop); return () => window.removeEventListener("popstate", onPop); }, []); const setPage = (page, params = {}) => setRoute({ page, params }); const openCatalog = (initialFilter = {}) => setRoute({ page: "catalog", params: { filter: initialFilter } }); const openCategoria = (cat) => openCatalog({ cat }); const openModelo = (modeloName) => setRoute({ page: "modelo", params: { modelo: modeloName } }); const openFicha = (productOrId) => { const id = typeof productOrId === "string" ? productOrId : (productOrId && productOrId.id); if (!id) return; setRoute({ page: "ficha", params: { productId: id } }); }; useEffect(() => { document.documentElement.style.setProperty("--accent", t.accent); }, [t.accent]); useEffect(() => { if (t.view === "mobile") { const el = document.querySelector(".phone-scroll"); if (el) el.scrollTo({ top: 0, behavior: "instant" }); } else { window.scrollTo({ top: 0, behavior: "instant" }); } }, [route.page, route.params, t.view]); // Hero variant determines if header should be dark over the home page const headerDark = (route.page === "home" && t.hero === "A") || route.page === "taller" || route.page === "modelo" || route.page === "showroom"; const densityCls = t.density === "compact" ? "dens-compact" : t.density === "airy" ? "dens-airy" : ""; const viewMobile = t.view === "mobile"; const openProductModal = (p) => ui.openModal(p); // Expose route handlers globally so Home cards, Modal "Ver ficha", etc. can call them useEffect(() => { window.__davasRoute = { openCatalog, openCategoria, openModelo, openFicha, setPage }; }, [route]); const renderPage = () => { switch (route.page) { case "home": return ( <> setPage(p === "home" ? "catalog" : p)} openProduct={openProductModal} /> setPage(p)} showPrices={t.prices} cardStyle={t.card} /> ); case "taller": return ; case "catalog": return ; case "modelo": return setPage("catalog")} />; case "ficha": return setPage("catalog")} />; case "clientes": return setPage("catalog")} />; case "cursos": return ; case "showroom": return ; default: return null; } }; const PageContent = ( <>
setPage(p)} dark={headerDark} mobile={viewMobile} /> {renderPage()} ); return (
{viewMobile ? ( <>
DAVA'S · MOBILE PREVIEW · {route.page.toUpperCase()}
{PageContent}
) : ( PageContent )} { if (v === "modelo") openModelo("Pavo Real"); else if (v === "ficha") openFicha("ME-003"); else setPage(v); }} /> setTweak("hero", v)} /> setTweak("accent", v)} /> setTweak("density", v)} /> setTweak("view", v)} /> setTweak("card", v)} /> setTweak("prices", v)} />
); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render();