diff --git a/workloads/docs-portal/docs/decisiones/0004-evaluacion-stack-multi-categoria.md b/workloads/docs-portal/docs/decisiones/0004-evaluacion-stack-multi-categoria.md new file mode 100644 index 0000000..d2b149d --- /dev/null +++ b/workloads/docs-portal/docs/decisiones/0004-evaluacion-stack-multi-categoria.md @@ -0,0 +1,140 @@ +# 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](0005-rediseno-storefront.md)) 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. + +**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](0005-rediseno-storefront.md)) y volver al modelo + metadata/legacy-map; no hay una razón identificada hoy para hacerlo. diff --git a/workloads/docs-portal/docs/decisiones/0005-rediseno-storefront.md b/workloads/docs-portal/docs/decisiones/0005-rediseno-storefront.md new file mode 100644 index 0000000..116f711 --- /dev/null +++ b/workloads/docs-portal/docs/decisiones/0005-rediseno-storefront.md @@ -0,0 +1,266 @@ +# ADR 0005 — Rediseño del storefront para catálogo multi-categoría + +**Estado:** propuesta — pendiente de aprobación antes de Etapa 2 +**Fecha:** 2026-08-23 + +> Este documento es la propuesta de Etapa 1 del rediseño de ARI +> Shopping. No incluye código de producción — solo auditoría, +> decisión (ver también [ADR 0004](0004-evaluacion-stack-multi-categoria.md)), +> patrones de UX y datos demo ya creados y visibles en producción +> como línea base. + +## 1. Resumen de la auditoría + +El detalle completo y la evidencia (queries reales contra Admin API +y Store API) están en [ADR 0004](0004-evaluacion-stack-multi-categoria.md). +En corto: + +- Medusa nunca tuvo categorías/colecciones reales en uso — toda la + taxonomía visible hoy (Figuras, Peluches, Mochilas, Marvel, Toy + Story...) viene de una tabla hardcodeada en el frontend + (`legacyProductMeta` en `lib/headless/providers/medusa.ts`), no de + datos vivos. +- La navegación (`navbar.tsx`) y los tiles de home + (`category-tiles.tsx`) son arrays hardcodeados — no escalan a + 10-20 categorías sin redeploy por cada una nueva. +- Bug real descubierto en vivo: `/store/products` descarta la + relación `+categories` aunque `/admin/products` sí la devuelve — + confirmado creando categorías reales y viéndolas caer al fallback + "Variedades" en el storefront público. +- El filtrado es 100% client-side sobre un máximo de 100 productos + por request — techo real a la escala proyectada. +- La búsqueda es substring matching en dos capas, sin ranking ni + tolerancia a errores. +- **El flujo Admin → `revalidate-storefront.ts` → `revalidateTag` se + confirmó intacto y funcionando en vivo** durante la creación de los + 9 productos demo de la sección 4 — ninguna propuesta de este + documento lo modifica. + +## 2. Decisión de stack + +**Evolucionar Next.js + Medusa**, no reescribir. Detalle completo, +opciones evaluadas y justificación en +[ADR 0004](0004-evaluacion-stack-multi-categoria.md). Resumen de los +tres cambios de Etapa 2: + +1. Categorías/colecciones reales de Medusa en vez de + metadata/legacy-map. +2. Fix de la relación `+categories` en Store API. +3. Nav y tiles de home data-driven en vez de arrays hardcodeados. + +Un motor de búsqueda dedicado (Meilisearch autohospedado) queda +identificado como el siguiente paso natural, no parte del alcance +inmediato. + +## 3. Patrones de UX de referencia + +Se priorizaron patrones implementables con el modelo de datos de +Medusa v2 tal cual quedó confirmado en la auditoría (categorías con +jerarquía, colecciones, `variants[].options`, `inventory_quantity`, +`compareAtPrice`), no patrones que exigirían rediseñar el modelo de +datos desde cero. + +### De Amazon + +1. **Filtros facetados en sidebar con breadcrumb de categoría.** + Directamente soportado por la jerarquía `parent_category_id` / + `mpath` ya confirmada en Medusa — un breadcrumb + `Inicio > Mochilas > Marvel` es una caminata de `category.parent_category`, + sin dato nuevo que agregar. +2. **Conteo de resultados por valor de filtro** (`Marca (12)`, + `Mochilas (34)`). Requiere agregación — o server-side simple por + ahora, o vía facetas de Meilisearch cuando se agregue. +3. **Carrusel "Comprados juntos frecuentemente" en la ficha de + producto**, reutilizando el concepto de `collection` en vez de + inventar una relación nueva. `components/product/product-carousel.tsx` + ya existe en el repo — es extender, no crear. +4. **Barra fija de "Agregar al carrito" en móvil.** + `components/product/mobile-add-to-cart.tsx` ya existe — validar + qué falta en vez de asumir que hay que construirla desde cero. + +### De Walmart + +1. **Grid de catálogo con tiles promocionales intercalados**, no solo + tarjetas de producto homogéneas. `components/home/category-tiles.tsx` + ya es ese mismo primitivo visual — la propuesta es extenderlo al + catálogo, no crear un componente nuevo. +2. **Mega-menú de "Departamentos"** (multi-columna al pasar el mouse + o al tocar), en vez de la fila horizontal de 6 píldoras actual. + Ataca directamente el problema de escala del nav (hallazgo de la + auditoría) — factible una vez el nav lea de + `/store/product-categories` en vez de un array fijo. +3. **Badges de "Antes/Ahora" por caída de precio.** El tipo + `Product.compareAtPrice` ya existe en `lib/headless/types.ts` y ya + se calcula en `medusa.ts` (`compareAtPrice`) — hoy casi no se usa + en la UI. Ganancia visual barata, cero cambio de modelo de datos. +4. **Accesos directos por categoría desde el home**, por encima del + hero. ARI ya hace esto con `category-tiles.tsx` — la propuesta es + que la lista deje de ser un array de 4 tiles hardcodeado y pase a + ser data-driven, igual que el nav. + +### De eBay + +1. **Selector de variante inline en la tarjeta de producto** (no solo + en la ficha), ej. swatches de talla/color en el grid. La forma + `variants[].options[].{option.title, value}` que ya devuelve + Medusa (confirmado en el detalle de `Cybertron Sentinel`, opción + "Tamaño"/"Estándar") ya trae exactamente ese dato — falta + renderizarlo en `product-card.tsx`, no traerlo. +2. **Mensaje de urgencia por inventario real** ("Quedan 3"). Medusa ya + expone `inventory_quantity` por variante — hoy el frontend lo + colapsa a un booleano `available`; la propuesta es exponer el + número cuando sea bajo, sin cambiar el dato de origen. +3. **Badges de vendedor/condición (nuevo/usado).** Explícitamente + **descartado** — ARI es un solo vendedor con inventario 100% + nuevo; este patrón de eBay no aplica al modelo de negocio real y + se incluye aquí solo para dejar constancia de que se evaluó y se + descartó, no que se pasó por alto. +4. **Watchlist con contador visible.** Ya implementado — el + storefront tiene `wishlist-drawer.tsx` y `useWishlistStore` + funcionando. Se lista como confirmación de que este patrón ya + está adoptado, no como pendiente. + +### Wireframes en texto + +**Home (mega-menú Walmart + tiles Amazon/Walmart):** + +``` +┌─────────────────────────────────────────────────┐ +│ [Logo] [Buscador predictivo] ♡ 🛒 │ +├─────────────────────────────────────────────────┤ +│ ☰ Juguetes ▾ Mochilas ▾ Educativos Bebés ... │ ← mega-menú, no píldoras +├─────────────────────────────────────────────────┤ +│ [Hero / banner promocional] │ +├───────────┬───────────┬───────────┬──────────────┤ +│ Juguetes │ Mochilas │Educativos │ Acción/avent. │ ← tiles, data-driven +├───────────┴───────────┴───────────┴──────────────┤ +│ Destacados: [card][card][card][card] │ +└─────────────────────────────────────────────────┘ +``` + +**Categoría con facetas (Amazon) + breadcrumb:** + +``` +Inicio > Mochilas > Marvel ← breadcrumb real (mpath) +┌────────────┬──────────────────────────────────────┐ +│ Categoría │ [card+swatch][card+swatch][card] │ +│ ☐ Marvel(6)│ [card+swatch][card+swatch][card] │ +│ ☐ Frozen(4)│ │ +│ Marca │ Antes $139.900 Ahora $109.900 │ ← badge Walmart +│ ☐ Disney │ │ +│ Precio │ │ +│ [====○===] │ │ +└────────────┴──────────────────────────────────────┘ +``` + +**Tarjeta de producto (eBay: variante inline + urgencia):** + +``` +┌───────────────────┐ +│ [imagen] │ +│ [S][M][L] │ ← swatches de variante inline +│ Mochila Marvel │ +│ $119.900 │ +│ ⚠ Quedan 3 │ ← inventario real, no booleano +└───────────────────┘ +``` + +## 4. Taxonomía de categorías propuesta + +Basada en las categorías ya visibles hoy (Juguetes para bebés, +Educativos, Muñecas y peluches, Acción y aventura, Mochilas, +Variedades) y en cómo ya se agrupan los productos actuales por +franquicia (Marvel, Frozen, Disney, Bob Esponja bajo Mochilas). Se +creó **realmente en Medusa** — no es solo texto de este documento — +vía Admin API, con jerarquía real (`parent_category_id`), 6 +categorías raíz y 15 subcategorías (21 nodos totales, dentro del +rango de 10-20 que se buscaba escalar): + +``` +Juguetes +├── Figuras de acción +├── Muñecas y peluches +├── Juegos de mesa y rompecabezas +└── Vehículos y control remoto + +Bebés y primera infancia +├── Juguetes para bebés (0-3 años) +└── Accesorios de cuna y paseo + +Educativos +├── STEM y ciencia +└── Libros y aprendizaje + +Mochilas y morrales +├── Marvel +├── Frozen +├── Disney +├── Bob Esponja +└── Escolares sin licencia + +Acción y aventura +├── Bicicletas y patinetas +└── Juegos al aire libre + +Variedades +``` + +Además de las categorías (jerarquía exclusiva, "el pasillo del +producto"), se crearon 2 **colecciones** reales para franquicias que +cruzan varias categorías a la vez (ej. "Marvel" aparece tanto en +Figuras de acción como en Mochilas): `Universo Marvel` y +`Reino de Frozen`. Esta distinción categoría-vs-colección es +intencional: categoría es dónde vive el producto en el árbol, +colección es una vitrina temática que puede cruzar categorías — el +mismo patrón que ya usa Medusa nativamente y que la auditoría +confirmó operativo. + +## 5. Productos demo creados (línea base en producción) + +Creados vía Admin API de Medusa, mismo flujo validado con +`Cybertron Sentinel` (Admin → MinIO → `media.cruzcloud.net` → +frontend), precio real en la región Colombia +(`reg_01KZT86WPAH7A7ZZRDSX3350V2`, COP), con descripción e imagen. +Prefijo `[DEMO]` para identificarlos y poder limpiarlos después. +**Validados en vivo en el storefront actual** (antes de cualquier +cambio de código) el 2026-08-23 — los 9 responden `HTTP 200` en su +página de producto y aparecen listados en `/catalog`: + +| Producto | Categoría | Colección | Precio COP | URL | +|---|---|---|---|---| +| [DEMO] Guerrero Cósmico X-7 | Juguetes > Figuras de acción | Universo Marvel | $89.900 | `/product/demo-guerrero-cosmico-x-7` | +| [DEMO] Mochila Escudo Vengador | Mochilas > Marvel | Universo Marvel | $119.900 | `/product/demo-mochila-escudo-vengador` | +| [DEMO] Mochila Reino Helado | Mochilas > Frozen | Reino de Frozen | $109.900 | `/product/demo-mochila-reino-helado` | +| [DEMO] Kit de Robótica Junior | Educativos > STEM y ciencia | — | $149.900 | `/product/demo-kit-de-robotica-junior` | +| [DEMO] Set de Bloques Lógicos | Educativos > STEM y ciencia | — | $69.900 | `/product/demo-set-de-bloques-logicos` | +| [DEMO] Sonajero Suave Nubes | Bebés > Juguetes para bebés | — | $39.900 | `/product/demo-sonajero-suave-nubes` | +| [DEMO] Patineta Urbana Flash | Acción y aventura > Bicicletas y patinetas | — | $199.900 | `/product/demo-patineta-urbana-flash` | +| [DEMO] Set de Camping Junior | Acción y aventura > Juegos al aire libre | — | $89.900 | `/product/demo-set-de-camping-junior` | +| [DEMO] Caja Sorpresa ARI | Variedades | — | $49.900 | `/product/demo-caja-sorpresa-ari` | + +**Comportamiento observado en la línea base actual (importante para +Etapa 2):** los 9 productos muestran su marca (`metadata.brand`) y +precio correctamente en el storefront de hoy, pero su categoría real +**no** se refleja todavía — aparecen bajo "Variedades" por el bug de +Store API descrito en la sección 1 y en el ADR 0004. Esto es +evidencia en vivo, no hipotética, de por qué el fix de esa relación +es el primer paso de Etapa 2: sin él, ninguna categoría real se verá +nunca en el storefront público, sin importar qué tan bien esté +modelada en Medusa Admin. + +Imágenes: subidas vía `/admin/uploads` (mismo pipeline S3/MinIO que +`Cybertron Sentinel`), placeholders claramente marcados como `DEMO` +en el texto de la imagen — no fotos reales de producto, ya que el +objetivo es validar la tubería Admin → MinIO → NPM → frontend, no el +contenido visual final. + +Usuario admin temporal `demo-audit@ari.local` usado para esta +auditoría — pendiente de borrar una vez aprobada esta propuesta. + +## 6. Próximos pasos + +Pendiente de tu aprobación explícita antes de iniciar Etapa 2. Cuando +apruebes, Etapa 2 se dividirá en sub-fases (datos/categorías primero, +luego navegación, luego componentes visuales), validando en cada paso +contra estos 9 productos demo que precio/descripción/imágenes siguen +actualizándose correctamente desde Admin. diff --git a/workloads/docs-portal/mkdocs.yml b/workloads/docs-portal/mkdocs.yml index bc9fcc6..11fbcc0 100644 --- a/workloads/docs-portal/mkdocs.yml +++ b/workloads/docs-portal/mkdocs.yml @@ -89,6 +89,8 @@ nav: - "0001 · Por qué k3d": decisiones/0001-por-que-k3d.md - "0002 · Por qué Argo CD": decisiones/0002-por-que-argocd-vs-manual.md - "0003 · HTTP/2 sobre QUIC": decisiones/0003-por-que-http2-sobre-quic-tunnel.md + - "0004 · Stack multi-categoría": decisiones/0004-evaluacion-stack-multi-categoria.md + - "0005 · Rediseño storefront": decisiones/0005-rediseno-storefront.md - Plantilla ADR: decisiones/plantilla-adr.md - Playbooks: - Crashloop Medusa: playbooks/incidente-crashloop-medusa.md