Merge pull request 'docs(decisiones): ADR 0004 (stack multi-categoria) y 0005 (rediseno storefront)' (#26) from fix/fase5-adr-taxonomia-storefront into main
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 56s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 18m31s

This commit was merged in pull request #26.
This commit is contained in:
devops committed 2026-08-23 04:39:28 +00:00
commit 3fefd44c49
3 files changed
+408

No files matched your search

@@ -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.
@@ -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 `[email protected]` 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.
+2
View File
@@ -89,6 +89,8 @@ nav:
- "0001 · Por qué k3d": decisiones/0001-por-que-k3d.md - "0001 · Por qué k3d": decisiones/0001-por-que-k3d.md
- "0002 · Por qué Argo CD": decisiones/0002-por-que-argocd-vs-manual.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 - "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 - Plantilla ADR: decisiones/plantilla-adr.md
- Playbooks: - Playbooks:
- Crashloop Medusa: playbooks/incidente-crashloop-medusa.md - Crashloop Medusa: playbooks/incidente-crashloop-medusa.md