fix(ecommerce): nav y tiles data-driven contra la jerarquia real de categorias
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (pull_request) Successful in 35s
Build and Push Frontend / Construir y Subir Imagen (pull_request) Skipped

Sub-fase 2 de Etapa 2 (ADR 0005 seccion 6). Navbar y CategoryTiles usaban
arrays hardcodeados (Figuras/Peluches/Mochilas/Marvel) que ya no coinciden
con los nombres reales de categoria en Medusa desde que se elimino
legacyProductMeta (PR #29) -- todos esos links de navegacion quedaron
rotos silenciosamente (0 productos al filtrar).

- lib/headless: nuevo tipo Category + metodo getCategories() en
  CatalogProvider (medusa via /store/product-categories, mock deriva de
  su catalogo plano, shopify sin implementar como el resto de sus metodos).
- Navbar: arbol real de categorias (6 raices/21 nodos), dropdown de
  hijos por raiz en desktop, lista anidada en el sheet movil. Colecciones
  del sheet ahora se derivan de los productos reales en vez de un array
  hardcodeado con 2 nombres obsoletos (Marvel/Frozen vs. los reales
  Universo Marvel/Reino de Frozen).
- CategoryTiles: tiles de home por categoria raiz real; imagen solo donde
  ya existe asset, gradiente honesto donde no (no hay fotografia para las
  6 raices todavia, deuda explicita del ADR).
- catalog-client/catalog page: cuando el query param `category` es una
  raiz con hijos, se expande a los nombres reales de sus hijos para que
  el filtro (igualdad exacta de producto.category) siga funcionando --
  sin esto, tiles/nav apuntando a una raiz mostrarian 0 productos porque
  ningun producto tiene la raiz asignada directamente.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013dunTcReWMKhWgCPkvAVHD
This commit is contained in:
devopsandClaude Sonnet 5 committed 2026-08-24 21:21:18 -05:00
1 parent 0508c6328e
commit 8742ce714c
11 files changed
+323 -130

No files matched your search

+18 -4
View File
@@ -1,6 +1,6 @@
import type { Metadata } from "next";
import { CatalogClient } from "@/components/catalog/catalog-client";
import { getProducts } from "@/lib/headless/client";
import { getCategories, getProducts } from "@/lib/headless/client";
export const metadata: Metadata = {
title: "Catálogo",
@@ -17,17 +17,31 @@ function single(value: string | string[] | undefined): string {
export default async function CatalogPage({ searchParams }: CatalogPageProps) {
const params = await searchParams;
const products = await getProducts();
const [products, categories] = await Promise.all([getProducts(), getCategories()]);
const category = single(params.category) || "Todos";
const collection = single(params.collection);
const query = single(params.q);
const section = single(params.section);
// Los links de navbar/tiles apuntan a categorias raiz (ej. "Mochilas y
// morrales"), pero ningun producto tiene esa categoria asignada
// directamente -- solo sus hijos ("Marvel", "Frozen", etc.). Este mapa
// expande cada raiz-con-hijos al nombre real de sus hijos para que el
// filtro siga funcionando por igualdad exacta de `product.category`.
const categoryGroups: Record<string, string[]> = {};
for (const node of categories) {
if (node.parentId) continue;
const children = categories.filter((child) => child.parentId === node.id);
if (children.length > 0) {
categoryGroups[node.name] = children.map((child) => child.name);
}
}
return (
<CatalogClient
key={`${category}:${collection}:${query}:${section}`}
key={`${category}:${collection}:${query}`}
products={products}
categoryGroups={categoryGroups}
initialCategory={category}
initialCollection={collection}
initialQuery={query}
+3 -3
View File
@@ -4,7 +4,7 @@ import { CartDrawer } from "@/components/cart/cart-drawer";
import { Footer } from "@/components/layout/footer";
import { Navbar } from "@/components/layout/navbar";
import { WishlistDrawer } from "@/components/wishlist/wishlist-drawer";
import { getProducts } from "@/lib/headless/client";
import { getCategories, getProducts } from "@/lib/headless/client";
export const metadata: Metadata = {
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? "https://shop.cruzcloud.net"),
@@ -16,6 +16,6 @@ export const metadata: Metadata = {
export const viewport: Viewport = { width: "device-width", initialScale: 1, themeColor: "#0f172a" };
export default async function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
const products = await getProducts();
return <html lang="es"><body><Navbar products={products} /><main>{children}</main><Footer /><CartDrawer /><WishlistDrawer /></body></html>;
const [products, categories] = await Promise.all([getProducts(), getCategories()]);
return <html lang="es"><body><Navbar products={products} categories={categories} /><main>{children}</main><Footer /><CartDrawer /><WishlistDrawer /></body></html>;
}
+3 -3
View File
@@ -5,10 +5,10 @@ import { HeroBento } from "@/components/home/hero-bento";
import { TrustBar } from "@/components/home/trust-bar";
import { ProductCarousel } from "@/components/product/product-carousel";
import { Button } from "@/components/ui/button";
import { getProducts } from "@/lib/headless/client";
import { getCategories, getProducts } from "@/lib/headless/client";
export default async function HomePage() {
const products = await getProducts();
const [products, categories] = await Promise.all([getProducts(), getCategories()]);
const treasures = products.filter((product) => product.isNew || product.featured).slice(0, 8);
return <><HeroBento /><TrustBar /><div className="content-auto"><CategoryTiles /></div><section className="content-auto mx-auto max-w-7xl px-4 py-14"><div className="mb-8 flex items-end justify-between gap-5"><div><span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">Novedades</span><h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Tesoros recién llegados</h2><p className="mt-3 text-slate-500">Desliza y encuentra nuevos favoritos.</p></div><Button asChild variant="outline" className="hidden sm:inline-flex"><Link href="/catalog">Ver catálogo <ArrowRight className="size-4" /></Link></Button></div><ProductCarousel products={treasures} /></section><section className="content-auto mx-auto max-w-7xl px-4 pb-20"><div className="grid overflow-hidden rounded-[2rem] bg-gradient-to-r from-cyan-500 via-blue-600 to-violet-600 text-white shadow-2xl lg:grid-cols-[1fr_.7fr]"><div className="p-8 sm:p-12"><span className="text-xs font-black uppercase tracking-[.18em] text-yellow-300">Club ARI</span><h2 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">Promociones, lanzamientos y mucha diversión.</h2><p className="mt-5 max-w-xl text-white/80">Únete a nuestra comunidad y recibe primero las novedades del catálogo.</p><Button variant="yellow" size="lg" className="mt-7">Quiero unirme</Button></div><div className="min-h-64 bg-[url('/images/promo/promo-backpacks.webp')] bg-cover bg-center" /></div></section></>;
return <><HeroBento /><TrustBar /><div className="content-auto"><CategoryTiles categories={categories} /></div><section className="content-auto mx-auto max-w-7xl px-4 py-14"><div className="mb-8 flex items-end justify-between gap-5"><div><span className="text-xs font-black uppercase tracking-[.18em] text-fuchsia-600">Novedades</span><h2 className="mt-2 text-4xl font-black tracking-tight sm:text-5xl">Tesoros recién llegados</h2><p className="mt-3 text-slate-500">Desliza y encuentra nuevos favoritos.</p></div><Button asChild variant="outline" className="hidden sm:inline-flex"><Link href="/catalog">Ver catálogo <ArrowRight className="size-4" /></Link></Button></div><ProductCarousel products={treasures} /></section><section className="content-auto mx-auto max-w-7xl px-4 pb-20"><div className="grid overflow-hidden rounded-[2rem] bg-gradient-to-r from-cyan-500 via-blue-600 to-violet-600 text-white shadow-2xl lg:grid-cols-[1fr_.7fr]"><div className="p-8 sm:p-12"><span className="text-xs font-black uppercase tracking-[.18em] text-yellow-300">Club ARI</span><h2 className="mt-3 text-4xl font-black tracking-tight sm:text-6xl">Promociones, lanzamientos y mucha diversión.</h2><p className="mt-5 max-w-xl text-white/80">Únete a nuestra comunidad y recibe primero las novedades del catálogo.</p><Button variant="yellow" size="lg" className="mt-7">Quiero unirme</Button></div><div className="min-h-64 bg-[url('/images/promo/promo-backpacks.webp')] bg-cover bg-center" /></div></section></>;
}