Saltar a contenido

ADR 0004 — Stack para el catálogo multi-categoría (Fase 5)

Estado: aceptada Fecha: 2026-08-23

Contexto

ARI Shopping nació como nicho de juguetes pero el catálogo real ya cubre 5-6 familias de producto (juguetes, mochilas, acción y aventura, educativos, variedades) y se proyecta seguir creciendo. Antes de rediseñar UI/UX (ver ADR 0005) había que confirmar si Next.js + Medusa sigue siendo apropiado a esa escala, o si hay una limitación real — no teórica — que justifique cambiar de stack.

Restricción no negociable: cualquier alternativa debe preservar el flujo ya validado Admin → evento product.updated → revalidate-storefront.ts → POST /api/revalidate → revalidateTag("medusa-products"), que hoy actualiza precio, descripción, imágenes y título en el storefront sin intervención manual.

Auditoría hecha contra el sistema real (no contra la documentación de Medusa), vía Admin API y Store API en commerce.cruzcloud.net:

  1. Medusa nunca tuvo categorías ni colecciones reales en uso. GET /admin/product-categories y GET /admin/collections devolvían count: 0 antes de esta auditoría, para los 18 productos existentes (17 de seed + Cybertron Sentinel, el producto de prueba del incidente de revalidación). Todo lo que el storefront muestra hoy como categoría/colección/marca/edad para esos 17 productos viene de una tabla estática (legacyProductMeta, ~170 líneas) hardcodeada en lib/headless/providers/medusa.ts, indexada por handle de producto — no de datos vivos en Medusa. Confirmar un cambio de categoría hoy para un producto de seed requiere editar código del frontend y redesplegar, no una acción de Admin.
  2. La navegación superior es un array de 6 entradas hardcodeado en components/layout/navbar.tsx — no deriva de Medusa. Lo mismo para los 4 tiles de components/home/category-tiles.tsx. Ninguno de los dos escala a 10-20 categorías sin un redeploy por cada categoría nueva.
  3. Bug real, no teórico, descubierto durante esta auditoría: /store/products (la API pública que usa el storefront) descarta en silencio la expansión de relación +categories, aunque /admin/products sí la devuelve correctamente. Se confirmó creando 21 categorías reales (con jerarquía, ej. Mochilas > Marvel) y 9 productos de prueba vía Admin API: la jerarquía se ve perfecta en /admin/products/.../?fields=*categories, pero la misma consulta exacta que hace lib/headless/providers/medusa.ts contra /store/products nunca trae categories en la respuesta. Validado en vivo: shop.cruzcloud.net/product/demo-guerrero-cosmico-x-7 muestra "Variedades" (el fallback) en vez de "Figuras de acción", pese a tener la categoría real asignada en Medusa. Nunca se había detectado porque ningún producto había tenido una categoría real asignada antes de esta auditoría.
  4. El filtrado es 100% client-side sobre un máximo de 100 productos traídos en una sola llamada (fetchProducts(filters, limit = 100) en medusa.ts). No hay conteos de faceta por categoría/marca ni paginación real. Con 18 productos es invisible; a la escala de 10-20 categorías con decenas de productos cada una, 100 productos totales es un techo real y cercano.
  5. La búsqueda es un híbrido de dos capas de substring matching — el parámetro ?q= de Medusa (coincidencia básica tipo ILIKE sobre título/descripción, sin ranking) más un segundo filtro client-side sobre el mismo set de ≤100 productos ya traído. No hay tolerancia a errores de tipeo ni relevancia real.
  6. El frontend ya tiene una capa de abstracción "headless" (lib/headless/client.ts + providers/{mock,shopify,medusa}.ts, seleccionable por HEADLESS_PROVIDER) que desacopla los componentes de React del proveedor de catálogo concreto. Esto reduce mucho el riesgo de agregar piezas nuevas sin tocar el core.

Ninguno de estos hallazgos es un límite de Next.js o de Medusa como plataforma — son decisiones de implementación (metadata en vez de categorías reales, filtrado client-side, nav hardcodeado, una relación de Store API sin exponer) tomadas cuando el catálogo era de 17-18 productos en un solo dominio (juguetes). Ese es exactamente el tipo de "deuda de alcance", no de stack.

Opciones consideradas

Opción A favor En contra
Evolucionar Next.js + Medusa (adoptar categorías/colecciones reales, arreglar la relación de Store API, nav data-driven, agregar motor de búsqueda dedicado detrás de la misma capa headless) Cero riesgo para el flujo Admin→revalidate (no toca esa capa); reutiliza infraestructura ya operativa (Postgres, Redis, MinIO, revalidación por evento); los 3 gaps reales encontrados son arreglos acotados, no una reescritura; la capa CatalogProvider ya está pensada para esto Requiere una migración de datos (metadata/legacy-map → categorías/colecciones reales) para los 17 productos de seed
Reescribir el backend a un headless commerce distinto (ej. Shopify, Saleor) Motores de búsqueda/facetas más maduros de fábrica Ya existe un shopifyCatalogProvider sin terminar en el propio repo — no resuelve nada que Medusa no resuelva con las categorías reales ya probadas en esta auditoría; reescribe el flujo Admin→revalidate desde cero, justo la pieza que el usuario pidió explícitamente no arriesgar; ningún hallazgo de la auditoría apunta a un límite de Medusa en sí, sino a que sus categorías nunca se usaron
Agregar un motor de búsqueda/filtros dedicado (Meilisearch) sobre Medusa, sin tocar el core Resuelve el techo real de 100 productos y el filtrado client-side sin migrar de backend; se autohospeda en el mismo k3d, sin costo de SaaS; se alimenta con el mismo patrón de evento (product.created/updated) que ya usa revalidate-storefront.ts — indexa una copia de solo lectura, nunca escribe hacia Medusa Un componente operativo más que mantener (aunque acotado); solo tiene sentido una vez el catálogo supere el techo de 100 productos, no de inmediato

Decisión

Evolucionar Next.js + Medusa, no cambiar de stack. Se adoptan tres cambios acotados (a detallar en Etapa 2):

  1. Migrar la taxonomía de legacyProductMeta + product.metadata a categorías y colecciones reales de Medusa (ya validado que soportan jerarquía vía parent_category_id/mpath).
  2. Corregir la relación +categories que /store/products descarta hoy, para que el storefront pueda leer la categoría real en vez de caer siempre al fallback "Variedades".
  3. Reemplazar los arrays hardcodeados de navbar.tsx y category-tiles.tsx por datos leídos de /store/product-categories (endpoint público, confirmado funcionando y devolviendo las 21 categorías de prueba correctamente).

Un motor de búsqueda dedicado (Meilisearch, autohospedado) queda identificado como el siguiente paso natural una vez el catálogo crezca más allá de ~100 productos activos, pero no es parte del alcance inmediato de Etapa 2 — se revisará cuando ese techo esté cerca. Actualización 2026-08-25: terminada la Etapa 2 (ver ADR 0005), el disparador real resultó ser la complejidad de facetas de la taxonomía, no el volumen de productos — ver ADR 0006.

Compatibilidad con Medusa Admin confirmada: ninguna de las tres opciones evaluadas, incluida la elegida, toca revalidate-storefront.ts ni el endpoint /api/revalidate. El flujo Admin → product.updated → revalidación sigue siendo el mismo mecanismo, y se validó en vivo durante esta misma auditoría: los 9 productos de prueba creados vía Admin API dispararon el evento product.created, y aparecieron en shop.cruzcloud.net/catalog sin ninguna acción manual de cache-busting ni redeploy.

Consecuencias

  • Se gana una taxonomía real, editable desde Medusa Admin (categorías con jerarquía), sin depender de un archivo de código para clasificar productos.
  • Se acepta como deuda técnica temporal: los 17 productos de seed seguirán usando el fallback de legacyProductMeta hasta que se les asignen categorías reales explícitamente (migración de datos, no de código, en Etapa 2).
  • El fix de la relación +categories en Store API es la pieza más urgente y menos opcional de las tres — sin él, ninguna categoría real (ni la de los productos de seed ni la de los nuevos) se reflejaría jamás en el storefront público, sin importar qué tan bien esté modelada en Medusa.
  • Revertir esta decisión implicaría descartar toda la taxonomía real ya creada (21 categorías, 2 colecciones, 9 productos demo — ver ADR 0005) y volver al modelo metadata/legacy-map; no hay una razón identificada hoy para hacerlo.