diff --git a/workloads/docs-portal/docs/aprendizajes/notas-sueltas.md b/workloads/docs-portal/docs/aprendizajes/notas-sueltas.md index 0e00191..39f0924 100644 --- a/workloads/docs-portal/docs/aprendizajes/notas-sueltas.md +++ b/workloads/docs-portal/docs/aprendizajes/notas-sueltas.md @@ -81,4 +81,70 @@ 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. \ No newline at end of file + 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. \ No newline at end of file diff --git a/workloads/docs-portal/docs/decisiones/0011-ambiente-staging.md b/workloads/docs-portal/docs/decisiones/0011-ambiente-staging.md new file mode 100644 index 0000000..569282a --- /dev/null +++ b/workloads/docs-portal/docs/decisiones/0011-ambiente-staging.md @@ -0,0 +1,115 @@ +# ADR 0011 — Ambiente de Staging para el storefront + +**Estado:** aceptada — desplegado y verificado en vivo +**Fecha:** 2026-09-14 + +## Contexto + +Hasta este punto, todo cambio de frontend se probaba directamente +contra `shop.cruzcloud.net` (producción): el mismo push a `main` que +disparaba el build de imagen también actualizaba el manifiesto real +que sirve tráfico. Cada iteración de UI (rediseño de la página de +producto, zoom estilo Amazon, ajustes mobile) implicaba validar en +vivo sobre el sitio real, sin una red de contención — un cambio roto +llegaba directo al usuario final antes de poder revertirlo. + +El usuario pidió explícitamente un ambiente de Staging para dejar de +probar en producción ("Me interesa crear el ambiente Staging para no +estar probando en Prod"). + +## Opciones consideradas + +| Opción | A favor | En contra | +|---|---|---| +| **Namespace + Ingress propios, backend compartido con Prod** — elegida | Cero costo adicional de Postgres/Medusa/Meilisearch en un NAS ya ajustado de recursos; rápido de armar reusando el mismo patrón workload+governance de `ecommerce` | Staging y Prod comparten datos reales (catálogo, inventario, órdenes) — no aísla pruebas que muten datos de negocio | +| Backend propio para Staging (Postgres/Medusa/Meilisearch duplicados) | Aislamiento completo, se podrían probar migraciones de datos sin riesgo | Duplica el consumo de recursos de la NAS sin necesidad todavía (decisión explícita del usuario de descartar esto por ahora) | +| Feature flags / preview deploys por PR en vez de un ambiente fijo | Sin infraestructura nueva que mantener | Gitea Actions en este lab no tiene un mecanismo de preview-per-branch ya armado; más esfuerzo de tooling que un namespace nuevo | + +## Decisión + +Namespace `ecommerce-staging` nuevo, con su propio Ingress +(`staging.cruzcloud.net`) y su propio Deployment de frontend (misma +imagen Docker que Prod), pero apuntando al **mismo backend Medusa** que +producción (`medusa-svc.ecommerce.svc.cluster.local:9000` — FQDN +cross-namespace, un nombre corto solo resuelve dentro del mismo +namespace). Flujo de promoción: **todo push a `main` despliega a +Staging automáticamente**; promover a Producción es una acción manual +separada (`scripts/promote-to-production.sh`). + +## Qué se armó + +- `apps-registry/workloads/ecommerce-staging/` — overlay Kustomize: + 1 réplica (no 2 como Prod — ambiente de verificación, no de tráfico + real), requests/limits más chicos, mismo `securityContext` que Prod + (mismas restricciones del Dockerfile: `runAsUser` numérico, + `NET_BIND_SERVICE`, `readOnlyRootFilesystem`). +- `apps-registry/apps/ecommerce-staging-app.yaml` + + `ecommerce-staging-governance-app.yaml` — Applications de Argo CD + (workload + governance separadas, misma convención que el resto del + lab). +- `platform-infra/ecommerce-staging-governance/` — ResourceQuota, + LimitRange y NetworkPolicy propios del namespace nuevo. +- `.gitea/workflows/build.yaml` — el paso de CD (`Actualizar + Manifiesto GitOps`) ahora apunta a + `workloads/ecommerce-staging/frontend/patches/deployment.yaml` en + vez del manifiesto de Prod: cada push a `main` bumpea el tag de + imagen (y, desde el mismo día, la variable `APP_VERSION` — ver nota + de implementación) de Staging automáticamente. +- `scripts/promote-to-production.sh` — lee el tag actualmente + desplegado en Staging, pide confirmación explícita, y lo aplica al + manifiesto real de Producción (`workloads/ecommerce/frontend/patches/deployment.yaml`). + Nunca corre automáticamente: es la única vía para que un cambio + llegue a `shop.cruzcloud.net`. +- Secrets `commerce-storefront` y `gitea-registry-secret` copiados + (no regenerados) desde el namespace `ecommerce` hacia + `ecommerce-staging`, para que el frontend de Staging tenga las + mismas credenciales reales contra el backend compartido. + +## Descubrimiento de red real (no solo Kubernetes) + +Confirmado en vivo el mismo día: aunque Cloudflare tiene un wildcard +`*.cruzcloud.net`, eso **no alcanza** para exponer un Ingress nuevo. La +cadena real es Cloudflare → túnel `cloudflared` → **Nginx Proxy +Manager** → `k3d-lab-cluster-serverlb` (puerto 90) → Traefik → Service. +Cada subdominio que ya funciona (`shop`, `commerce`, `media`, etc.) +tiene su propia fila de Proxy Host en NPM apuntando a +`192.168.68.61:90`. Un Ingress nuevo sin esa fila devuelve la página +por defecto de NPM aunque Traefik ya esté enrutando bien internamente +— verificado con `curl -H "Host: staging.cruzcloud.net" +http://192.168.68.61:90/` (funcionaba) contra el dominio público +(seguía devolviendo la página por defecto hasta que se agregó la fila +en NPM). Ver [Red y exposición](../arquitectura/red-y-exposicion.md). +Esta fila de NPM la agregó el usuario manualmente — no hay credenciales +de ese panel disponibles para automatizarlo. + +## Nota de implementación (2026-09-14): versión real visible en el footer + +El footer mostraba un string fijo (`v4.1`), inútil para distinguir a +simple vista si Staging y Producción están en la misma versión. +Se agregó una variable de entorno `APP_VERSION` (leída en +`footer.tsx` vía `process.env.APP_VERSION`, sin necesidad de prefijo +`NEXT_PUBLIC_` por ser un Server Component) a ambos manifiestos de +Deployment; el mismo paso del CD que bumpea el tag de imagen ahora +también bumpea `APP_VERSION` (vía `perl -0777` para una sustitución +multilínea, ya que el nombre y el valor de la variable viven en líneas +distintas del YAML — un `sed` de una sola línea no alcanza). +`promote-to-production.sh` hace el mismo bump al promover. Verificado +en vivo con Playwright: tras el primer push, Staging mostró `v1.0.263` +mientras Producción seguía en `v4.1` (la imagen vieja no leía la +variable); tras promover, ambos ambientes quedaron visualmente +distinguibles por versión real en su footer. + +## Consecuencias + +- Ganado: ningún cambio de frontend vuelve a validarse por primera vez + contra tráfico real; Staging y Producción ahora se distinguen a + simple vista por la versión mostrada en el footer. +- Deuda aceptada: Staging comparte datos reales de negocio con + Producción (mismo Medusa/Postgres) — una prueba que cree o modifique + órdenes/inventario en Staging afecta datos reales. Si esto se vuelve + un problema, la opción descartada de backend propio para Staging + queda como camino de escalamiento. +- Para revertir: apuntar de nuevo `MANIFEST_FILE`/`KUSTOMIZE_DIR` en + `build.yaml` hacia `workloads/ecommerce/...` y eliminar el namespace + `ecommerce-staging` — el namespace de Producción no cambia de forma + en ningún momento de este ADR. diff --git a/workloads/docs-portal/mkdocs.yml b/workloads/docs-portal/mkdocs.yml index ab04a7d..6d0da90 100644 --- a/workloads/docs-portal/mkdocs.yml +++ b/workloads/docs-portal/mkdocs.yml @@ -97,6 +97,8 @@ nav: - "0007 · Identidad visual propia": decisiones/0007-identidad-visual-propia.md - "0008 · Identidad Navy & Coral": decisiones/0008-identidad-navy-coral.md - "0009 · Checkout real": decisiones/0009-checkout-real.md + - "0010 · Pago y envío reales": decisiones/0010-pago-envio-reales-y-storefront.md + - "0011 · Ambiente de Staging": decisiones/0011-ambiente-staging.md - Plantilla ADR: decisiones/plantilla-adr.md - Playbooks: - Crashloop Medusa: playbooks/incidente-crashloop-medusa.md