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 porStoreGetProductsParams,.strict()) no aceptacurrency_codenicountry_codepara resolver precios calculados — soloregion_idexplícito. Ver playbook de precios. - El campo
thumbnailde un producto no se autocompleta a partir del arrayimages— 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, bucketari-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): esS3_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: simedia.cruzcloud.netdeja de rutear bien (mismo patrón deforward_hostroto 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.
Subscriber revalidate-storefront.ts y sus límites¶
- Escucha 6 eventos de Medusa (
product.created/updated/deleted+ los 3 equivalentes deproduct-variant) y por cada uno hace un únicoPOSTa${STOREFRONT_URL}/api/revalidatecon un header secreto (x-revalidate-secret) — esa ruta del frontend llamarevalidateTag("medusa-products")de Next.js. - Se degrada solo, no tira el evento entero: si
STOREFRONT_URL/REVALIDATE_SECRETno están configurados, o elfetchfalla, solo loguea unwarny 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 (searchWithFacetsopcional,/api/searchcayendo 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
POSTindependientes al mismo endpoint.revalidateTagde 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=http2desde el día uno (ver ADR 0003) y ajustar el MTU deflannel.1desde el primerdeploy-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_mtuendeploy-lab.sh, la variable de entorno del túnel), pero se aprendieron reactivamente, no se anticiparon. - Usar
git merge-base --is-ancestorpara 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.
- No escribir un script que regenere credenciales completas en cada
corrida (
create-commerce-secrets.shrota 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_CODEapuntaba 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, encommerce-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
/cambiosy/preguntas-frecuentescon 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 porobject-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 contrawindow.innerWidth/innerHeighten cadamousemove(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 elementosposition: fixeden la página de producto. - Ambiente de Staging + versión real en el footer: ver ADR 0011 para el detalle completo.