# Notas sueltas / aprendizajes > Esto es un scratchpad estructurado, no un playbook — sigue creciendo > con notas cortas a medida que aparecen. Cada entrada está extraída de > un incidente o hallazgo real ya documentado en otra parte del sitio, > no es especulación. ## Gotchas de Medusa v2 - El Store API (`/store/products`, validado por `StoreGetProductsParams`, `.strict()`) **no acepta** `currency_code` ni `country_code` para resolver precios calculados — solo `region_id` explícito. Ver [playbook de precios](../playbooks/incidente-precios-medusa.md). - El campo `thumbnail` de un producto **no se autocompleta** a partir del array `images` — hay que setearlo explícito desde el Admin si el frontend lo usa como imagen hero en listados. ## MinIO / S3 provider - Medusa sube imágenes de producto a MinIO (`minio-svc:9000`, bucket `ari-products`) usando el provider S3-compatible estándar — MinIO no sabe que no es AWS real, y Medusa tampoco necesita saberlo. - La URL pública que termina viendo el navegador **no** es la interna (`minio-svc:9000`): es `S3_FILE_URL=https://media.cruzcloud.net/ari-products`, un dominio propio proxied por NPM hacia el mismo MinIO. Esto tiene una consecuencia directa que ya rompió producción una vez: si `media.cruzcloud.net` deja de rutear bien (mismo patrón de `forward_host` roto que el resto del sitio), **todas** las imágenes de producto del storefront se caen aunque MinIO y Medusa estén perfectamente sanos — porque el navegador nunca habla con MinIO directo, solo con esa URL pública. Ver [Red y exposición](../arquitectura/red-y-exposicion.md). ## Subscriber `revalidate-storefront.ts` y sus límites - Escucha 6 eventos de Medusa (`product.created/updated/deleted` + los 3 equivalentes de `product-variant`) y por cada uno hace un único `POST` a `${STOREFRONT_URL}/api/revalidate` con un header secreto (`x-revalidate-secret`) — esa ruta del frontend llama `revalidateTag("medusa-products")` de Next.js. - **Se degrada solo, no tira el evento entero**: si `STOREFRONT_URL`/`REVALIDATE_SECRET` no están configurados, o el `fetch` falla, solo loguea un `warn` y sigue — un fallo de red hacia el frontend nunca revierte ni bloquea la operación real en Medusa (crear/editar un producto). Mismo criterio de "degradar, no romper" que se repite en el resto del storefront (`searchWithFacets` opcional, `/api/search` cayendo a `{}` si Meilisearch no responde). - **Límite real, todavía no un problema**: no hay debounce ni deduplicación — si una operación dispara varios eventos juntos (ej. editar un producto con 3 variantes a la vez), son varios `POST` independientes al mismo endpoint. `revalidateTag` de Next.js es barato e idempotente, así que hoy no se nota, pero si el catálogo creciera mucho y las ediciones masivas se volvieran frecuentes, sería el primer lugar donde agregar algún tipo de agrupación. ## Qué haría distinto si volviera a montar este lab desde cero - **Fijar `TUNNEL_TRANSPORT_PROTOCOL=http2` desde el día uno** (ver [ADR 0003](../decisiones/0003-por-que-http2-sobre-quic-tunnel.md)) y **ajustar el MTU de `flannel.1` desde el primer `deploy-lab.sh`** — ambos terminaron siendo incidentes reales (504 intermitente, blackhole cross-node) que en retrospectiva eran predecibles: red doméstica con NAT agresivo + Docker-in-Docker multi-nodo son una combinación conocida para tener problemas de MTU y de transporte UDP. Hoy los dos ya están automatizados (`fix_flannel_mtu` en `deploy-lab.sh`, la variable de entorno del túnel), pero se aprendieron reactivamente, no se anticiparon. - **Usar `git merge-base --is-ancestor` para la promoción segura del CI desde el primer pipeline**, no después de ver la condición de carrera en vivo (dos pipelines corriendo en serie sobre el mismo commit, el segundo pisando o saltándose la promoción del primero). El patrón correcto no es complicado una vez que se conoce — el costo fue no anticiparlo. - **Separar siempre "chequeo de disponibilidad" de "migraciones"** en init containers distintos, y que el chequeo use el mínimo de parámetros posible (host/puerto/usuario/db) en vez de reusar una URI de conexión pensada para la app. Confirmado como decisión correcta en el [incidente de crashloop de Medusa](../playbooks/incidente-crashloop-medusa.md). - **No escribir un script que regenere credenciales completas en cada corrida** (`create-commerce-secrets.sh` rota JWT/COOKIE/passwords de Postgres/Redis/MinIO enteros cada vez que se ejecuta, incluso si solo hacía falta agregar una clave nueva) — un script de setup inicial y uno de "agregar una clave a un secret ya vivo" deberían haber sido dos herramientas distintas desde el principio, no una sola que hay que saber cuándo NO correr completa. ## CSS Grid: un scroller horizontal sin `min-w-0` puede desbordar todo el grid Encontrado en producción (2026-09-13/14) en la página de producto del storefront: un síntoma que parecía un bug de imágenes ("se ve como si abriera en tamaño real" en Chrome/Edge Android) resultó ser, en realidad, un desborde de layout. El carrusel horizontal de "También te puede gustar" (`overflow-x-auto`, sin `flex-wrap`) vivía como hijo directo de un CSS Grid. Por spec, el tamaño mínimo automático de un item de grid es el tamaño mínimo de su contenido **a menos que el propio item** tenga `overflow` distinto de `visible` — y como el `div` que envolvía al carrusel no lo tenía, el ancho mínimo intrínseco de *todas* las tarjetas del carrusel sin wrappear se contaba como el mínimo de esa columna, forzando **todo el grid compartido** (imagen, título, specs) a ~800px de ancho en un viewport real de ~380px. **Lección reusable:** cualquier fila con scroll horizontal (carrusel, tira de miniaturas, chips de filtro) que sea hijo directo de un CSS Grid necesita `min-w-0` en su wrapper (o `overflow` seteado directamente en el item de grid) — si no, su contenido sin wrappear puede reventar el ancho de todo el grid en silencio, y el síntoma visible no tiene nada que ver con imágenes ni con DPR. Diagnosticado en vivo comparando `window.innerWidth` (reportaba el ancho desbordado) contra el viewport real del dispositivo. ## Rediseño de página de producto y correcciones de datos reales (2026-09-13/14) Sesión de correcciones guiadas por capturas reales del usuario en navegadores reales (no reportes abstractos): - **Bodega mal configurada:** `ENVIOCLICK_ORIGIN_DANE_CODE` apuntaba a Bogotá (`11001000`) cuando la bodega real está en Santiago de Cali, Valle del Cauca (`76001000`) — afectaba el cálculo real de envío (EnvíoClick) en cada checkout. Corregido en el ConfigMap del frontend, en `commerce-backend/medusa-config.ts` (incluyendo un fallback obsoleto que también apuntaba a Bogotá) y en el registro real de la bodega en Medusa (vía admin temporal, mismo patrón que el resto del lab para ediciones puntuales en vivo). - **Redes sociales y páginas de ayuda reales:** el footer enlazaba cuentas placeholder; se verificó en vivo la cuenta real de Instagram antes de linkearla (la primera sugerida no era la correcta) y se activaron los enlaces reales de Instagram/Facebook/WhatsApp, además de las páginas `/cambios` y `/preguntas-frecuentes` con contenido real (ya existían en código, no estaban enlazadas). - **Zoom estilo Amazon en la galería de producto:** iteración en tres pasos hasta llegar a lo pedido — `transform: scale()` sobre una imagen ya recortada por `object-cover` (no funcionaba: lo recortado nunca estaba en el DOM), superposición in-place (técnicamente correcta pero no la UX pedida), y finalmente una lupa que sigue el cursor más un panel lateral que magnifica esa región — el patrón real de Amazon. El tamaño/posición del panel se calcula en JS contra `window.innerWidth/innerHeight` en cada `mousemove` (no un breakpoint CSS fijo), con fallback a la superposición in-place si no hay espacio. - **Barra flotante de "Añadir al carrito" en mobile: removida, no reajustada.** Pasó por varias iteraciones de lógica de mostrar/ocultar (con `IntersectionObserver`) que seguían superponiéndose a otro contenido en capturas reales del usuario. En vez de seguir ajustando el umbral, se eliminó el componente por completo y se dejó el botón inline normal, visible también en mobile — el mismo patrón que usa la mayoría de tiendas. Verificado con Playwright: cero elementos `position: fixed` en la página de producto. - **Ambiente de Staging + versión real en el footer:** ver [ADR 0011](../decisiones/0011-ambiente-staging.md) para el detalle completo.