/* global React, CATALOGO, CAT_LABELS, CAT_TOTALS, PATRONES, detectarPatron, ICONOS_INFO,
Placeholder, Eyebrow, Arr, HeartIcon, Reveal, useWishlist, useUI, waLink */
const { useState, useMemo, useEffect } = React;
/* ─── Portada del catálogo — polaroids por categoría ─── */
function CatalogPortada({ onSelect }) {
const isMob = typeof window !== "undefined" && window.innerWidth < 760;
const CATS = [
{ id: "mecedoras", bg: "#ede4d4" },
{ id: "sillas", bg: "#dde8e0" },
{ id: "comedores", bg: "#dde4ee" },
{ id: "combos", bg: "#f0e4d4" },
{ id: "mesas", bg: "#e8e4da" },
{ id: "sofas", bg: "#ead8e4" },
{ id: "percheros", bg: "#d4e8d4" },
{ id: "sillas-bar", bg: "#f0ddd8" },
].filter(c => (CATALOGO||[]).some(p => p.categoria === c.id));
const CAT_HERO_LOCAL_P = { sofas: "guajira-lineal", "sillas-bar": "diamante-precolombino-3" };
const getPhoto = (catId) => {
const ovr = CAT_HERO_LOCAL_P[catId] || (window.CAT_HERO_OVERRIDES||{})[catId];
if (ovr) { const p = (CATALOGO||[]).find(x => (x.id === ovr || x.slug === ovr) && x.foto); if (p) return p.foto; }
return (CATALOGO||[]).find(p => p.categoria === catId && p.foto)?.foto || null;
};
const minPrecio = (catId) => {
const ps = (CATALOGO||[]).filter(p => p.categoria === catId && p.precio).map(p => p.precio);
return ps.length ? Math.min(...ps).toLocaleString("es-CO") : null;
};
return (
{/* Header */}
El catálogo · DAVA'S · 2026
¿Qué espacio vasa transformar?
Elige tu categoría y encuentra la pieza que va a vivir contigo.
{/* Grid de polaroids */}
{CATS.map((c) => {
const photo = getPhoto(c.id);
const label = (CAT_LABELS?.combos && c.id === 'combos' ? 'Conjuntos' : null) || CAT_LABELS[c.id] || c.id;
const count = (CATALOGO||[]).filter(p => p.categoria === c.id).length;
const desde = minPrecio(c.id);
const pad = isMob ? 9 : 13;
const bot = isMob ? 36 : 48;
return (
onSelect(c.id)}
style={{ boxShadow:"0 20px 56px rgba(0,0,0,.5)" }}>
{photo
?
:
}
{label}
{count} piezas{desde ? ` · desde $${desde}` : ""}
);
})}
{/* Ver todo */}
onSelect("__all__")}>
Ver todo el catálogo →
);
}
/* ─── Tarjeta de producto — estilo polaroid ─── */
// Productos sin foto no se muestran al público
function CatProd({ p, onOpen, onModelo }) {
if (!p.foto) return null;
const wl = useWishlist();
const isOn = wl.has(p.id);
return (
onOpen(p)}
style={{ background:"#fff", padding:"10px 10px 44px", boxShadow:"0 12px 32px rgba(0,0,0,.13)", cursor:"pointer", position:"relative", transition:"transform .32s cubic-bezier(.2,.7,.2,1), box-shadow .32s", borderRadius:2 }}>
{/* Foto */}
{ e.stopPropagation(); wl.toggle({ id:p.id, name:p.nombre, family:CAT_LABELS[p.categoria], pattern:detectarPatron(p.variante), price:p.precio.toLocaleString("es-CO") }); }}
style={{ position:"absolute", top:8, right:8 }}>
{/* Nombre a mano + precio */}
{p.nombre}
$ {p.precio.toLocaleString("es-CO")}
);
}
function Catalog({ initialFilter, setPage, openModelo, openFicha }) {
const initial = { cat: "all", modelo: "all", patron: "all", q: "", sort: "destacado", ...(initialFilter || {}) };
const [f, setF] = useState(initial);
const [drawerOpen, setDrawerOpen] = useState(false);
// "portada" = grid de polaroids | "list" = catálogo filtrado
const startMode = (initialFilter?.cat && initialFilter.cat !== "all") ? "list" : "portada";
const [viewMode, setViewMode] = useState(startMode);
const set = (k, v) => { setF((x) => ({ ...x, [k]: v })); setViewMode("list"); };
const clearAll = () => { setF({ cat: "all", modelo: "all", patron: "all", q: "", sort: "destacado" }); setViewMode("portada"); };
// ── Todos los hooks SIEMPRE (reglas de React) ──
// Bloqueo scroll cuando drawer abierto
useEffect(() => {
document.body.style.overflow = drawerOpen ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [drawerOpen]);
useEffect(() => { window.scrollTo({ top: 0, behavior: "instant" }); }, [initialFilter?.cat]);
// Reporta al header cuánto mide el banner de categoría (con foto), para que
// sepa hasta dónde ir en blanco/transparente y desde dónde pasar a oscuro.
// Si no hay categoría activa (f.cat === "all") no hay banner → 0.
const bannerRef = React.useRef(null);
useEffect(() => {
if (f.cat === "all") {
document.documentElement.style.setProperty("--dark-zone-h", "0px");
return;
}
const el = bannerRef.current;
if (!el) { document.documentElement.style.setProperty("--dark-zone-h", "0px"); return; }
const update = () => document.documentElement.style.setProperty("--dark-zone-h", el.offsetHeight + "px");
update();
const ro = new ResizeObserver(update);
ro.observe(el);
window.addEventListener("resize", update);
return () => { ro.disconnect(); window.removeEventListener("resize", update); };
}, [f.cat]);
// Sync cuando cambia initialFilter desde afuera (ej. clic en categoría del home)
useEffect(() => {
if (initialFilter?.cat && initialFilter.cat !== "all") {
setF(prev => ({ ...prev, ...(initialFilter||{}) }));
setViewMode("list");
}
}, [initialFilter?.cat]);
// Modelos derivados del catálogo
const modelos = useMemo(() => {
const counts = {};
CATALOGO.forEach((p) => { counts[p.nombre] = (counts[p.nombre] || 0) + 1; });
return Object.entries(counts).sort((a, b) => b[1] - a[1]).map(([name, n]) => ({ name, n }));
}, []);
// Filtrado
const filtered = useMemo(() => {
let out = CATALOGO.slice();
if (f.cat !== "all") out = out.filter((p) => p.categoria === f.cat);
if (f.modelo !== "all") out = out.filter((p) => p.nombre === f.modelo);
if (f.patron !== "all") out = out.filter((p) => detectarPatron(p.variante) === f.patron);
if (f.q) {
const q = f.q.toLowerCase();
out = out.filter((p) =>
p.nombre.toLowerCase().includes(q) ||
(p.variante || "").toLowerCase().includes(q) ||
p.id.toLowerCase().includes(q)
);
}
if (f.sort === "destacado") out.sort((a, b) => (b.destacado - a.destacado));
if (f.sort === "precio-asc") out.sort((a, b) => a.precio - b.precio);
if (f.sort === "precio-desc") out.sort((a, b) => b.precio - a.precio);
if (f.sort === "nombre") out.sort((a, b) => a.nombre.localeCompare(b.nombre));
return out;
}, [f]);
const activeChips = [];
if (f.cat !== "all") activeChips.push({ key: "cat", label: CAT_LABELS[f.cat], kind: "Categoría" });
if (f.modelo !== "all") activeChips.push({ key: "modelo", label: f.modelo, kind: "Modelo" });
if (f.patron !== "all") activeChips.push({ key: "patron", label: f.patron, kind: "Patrón" });
if (f.q) activeChips.push({ key: "q", label: `"${f.q}"`, kind: "Buscar" });
// ── Portada de categorías (early return DESPUÉS de todos los hooks) ──
if (viewMode === "portada") {
return (
{
if (catId === "__all__") { setViewMode("list"); }
else { set("cat", catId); }
}}
/>
);
}
// ── Vista de lista/catálogo ──
// Banners diseñados por categoría (desktop + mobile)
// Banners horizontales — funcionan para desktop y mobile con object-fit:cover
const CAT_BANNERS = {
sillas: { desk: "/banner_sillas_desk.jpg", mob: "/banner_sillas_desk.jpg" },
mecedoras: { desk: "/banner_mecedoras_desk.jpg", mob: "/banner_mecedoras_desk.jpg" },
combos: { desk: "/banner_combos_desk.jpg", mob: "/banner_combos_desk.jpg" },
comedores: { desk: "/banner_comedores_desk.jpg", mob: "/banner_comedores_desk.jpg" },
mesas: { desk: "/banner_mesas_desk.jpg", mob: "/banner_mesas_desk.jpg" },
sofas: { desk: "/banner_sofas_desk.jpg", mob: "/banner_sofas_desk.jpg" },
percheros: { desk: "/banner_percheros_desk.jpg", mob: "/banner_percheros_desk.jpg" },
"sillas-bar": { desk: "/banner_sillabar_desk.jpg", mob: "/banner_sillabar_desk.jpg" },
};
// Display names override (combos → Conjuntos)
const CAT_DISPLAY = { ...CAT_LABELS, combos: "Conjuntos" };
// Local hero overrides por slug (sofás y sillas-bar)
const CAT_HERO_LOCAL = { sofas: "guajira-lineal", "sillas-bar": "diamante-precolombino-3" };
const isMob2 = typeof window !== "undefined" && window.innerWidth < 760;
const getCatHero = (catId) => {
const ovr = CAT_HERO_LOCAL[catId] || (window.CAT_HERO_OVERRIDES||{})[catId];
if (ovr) { const p = CATALOGO.find(x => (x.id === ovr || x.slug === ovr) && x.foto); if (p) return p; }
return CATALOGO.find(p => p.categoria === catId && p.foto) || null;
};
const CAT_COPIES = {
mecedoras: { copy:"Hecha para quedarte.\nHecha para ti.", sub:"Hierro forjado, madera noble y tejido a mano. Una mecedora que es tuya desde el primer movimiento." },
sillas: { copy:"La silla que\ndefine el espacio.", sub:"Cada silla lleva horas de tejido artesanal y una estructura en hierro fundida en nuestro taller." },
comedores: { copy:"Alrededor de esta\nmesa pasan cosas.", sub:"Comedores tejidos a mano para las reuniones que importan. Hierro y madera noble, sin atajos." },
combos: { copy:"El conjunto\nque transforma el espacio.", sub:"Dos sillas, una mesa y un tejido que las une. Diseñados para vivir juntos, tejidos a mano en nuestro taller." },
comedores: { copy:"La mesa que hace\nque nadie quiera irse.", sub:"Comedores tejidos a mano para las reuniones que importan. Hierro y madera noble, sin atajos." },
mesas: { copy:"La base de\ncada historia.", sub:"Hierro forjado en nuestro taller, integrado con madera noble. Sin tornillos escondidos." },
sofas: { copy:"Donde la tarde\nse vuelve larga.", sub:"Sofás tejidos hilo a hilo, construidos para el uso real. No para la foto." },
percheros: { copy:"Lo primero que\nves al llegar a casa.", sub:"Hierro y tejido artesanal en un solo objeto. El detalle que eleva cualquier entrada." },
"sillas-bar": { copy:"Para las noches\nque no tienen prisa.", sub:"Sillas de bar con tejido artesanal y hierro. Para bares, restaurantes y casas con barra." },
};
return (
{/* ── Banner editorial cuando hay categoría activa ── */}
{f.cat !== "all" && (() => {
const banners = CAT_BANNERS[f.cat];
const hero = getCatHero(f.cat);
const { copy, sub } = CAT_COPIES[f.cat] || { copy: CAT_LABELS[f.cat] || f.cat, sub:"" };
const count = CATALOGO.filter(p => p.categoria === f.cat).length;
// Si hay banner diseñado, usarlo con overlay de copie
if (banners) {
const { copy: banCopy } = CAT_COPIES[f.cat] || {};
return (
{/* Gradiente + overlay texto */}
{CAT_DISPLAY[f.cat] || CAT_LABELS[f.cat]} · {count} piezas
{banCopy && (
{banCopy}
)}
{e.currentTarget.style.borderColor="#fff";e.currentTarget.style.color="#fff";}}
onMouseLeave={e=>{e.currentTarget.style.borderColor="rgba(247,242,234,.4)";e.currentTarget.style.color="rgba(247,242,234,.8)";}}>
← Todas las categorías
);
}
// Banner generado por código (fallback)
return (
{CAT_DISPLAY[f.cat] || CAT_LABELS[f.cat]} · {count} piezas
{copy}
{sub}
{e.currentTarget.style.borderColor="rgba(247,242,234,.5)";e.currentTarget.style.color="#F7F2EA";}}
onMouseLeave={e=>{e.currentTarget.style.borderColor="rgba(247,242,234,.22)";e.currentTarget.style.color="rgba(247,242,234,.55)";}}>
← Todas las categorías
{hero?.foto &&
}
); // end fallback banner
})()}
{/* ── Tabs de categoría (scroll normal con la página, sin recortarse) ── */}
{f.cat !== "all" && (
{Object.entries(CAT_LABELS).map(([k, label]) => (
set("cat", k)}>{label}
))}
)}
{/* Hero original solo cuando no hay categoría */}
{f.cat === "all" &&
Inicio · Catálogo
{filtered.length}piezas .
Tejido a mano · entrega en 15 días
Filtra por categoría, modelo o patrón. Toca cualquier pieza para verla en grande y pedirla por WhatsApp.
Filtros activos:
{activeChips.length === 0 ? (
— ninguno · mostrando todo el catálogo
) : (<>
{activeChips.map((c) => (
set(c.key, c.key==="q"?"":"all")}>
{c.kind} · {c.label} ×
))}
Limpiar todo
>)}
}
set("q", e.target.value)}
/>
Categoría
set("cat", "all")} data-on={f.cat === "all"}>
Todas
{CATALOGO.length}
{Object.entries(CAT_LABELS).map(([k, label]) => {
const n = CATALOGO.filter((p) => p.categoria === k).length;
return (
set("cat", k)}>
{label}
{n}
);
})}
Modelo
set("modelo", "all")}>
Todos
{modelos.length}
{modelos.slice(0, 14).map((m) => (
set("modelo", m.name)}>
{m.name}
{m.n}
))}
Patrón
set("patron", "all")}>
Todos
—
{PATRONES.map((p) => {
const n = CATALOGO.filter((x) => detectarPatron(x.variante) === p).length;
if (n === 0) return null;
return (
set("patron", p)}>
{p}
{n}
);
})}
{filtered.length} resultados
Ordenar
set("sort", e.target.value)} style={{ marginLeft: 10 }}>
Destacados
Precio · menor
Precio · mayor
Nombre A — Z
{filtered.length === 0 ? (
— Sin resultados con esos filtros.
) : (
{filtered.map((p) => (
))}
)}
{/* MOBILE: FAB de filtros */}
setDrawerOpen(true)}>
Filtros
{activeChips.length > 0 ? {activeChips.length} : null}
↑
{/* MOBILE: drawer de filtros */}
setDrawerOpen(false)} />
Filtros
{activeChips.length > 0 ? (
Limpiar todo
) : null}
¿Qué buscas?
set("cat", "all")}>
Todas {CATALOGO.length}
{Object.entries(CAT_LABELS).map(([k, label]) => {
const n = CATALOGO.filter((p) => p.categoria === k).length;
if (n === 0) return null;
return (
set("cat", k)}>
{label} {n}
);
})}
setDrawerOpen(false)}>
Ver {filtered.length} {filtered.length === 1 ? "resultado" : "resultados"} →
);
}
window.Catalog = Catalog;
window.CatProd = CatProd;