docs: ADR 0011 (ambiente de Staging) + notas de la sesión 2026-09-13/14
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 27s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 6m1s

Documenta el ambiente de Staging (staging.cruzcloud.net, promoción
manual, footer con version real), y agrega a notas-sueltas la
correccion de bodega Cali, redes sociales reales, rediseño de galeria
estilo Amazon, la leccion de min-w-0 en CSS Grid, y la remocion
definitiva de la barra flotante de carrito en mobile. De paso, suma a
la nav el ADR 0010 que existía en el repo pero nunca se había
enlazado.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012Y4df3Z7odr9Cg8F2nw6uV
This commit is contained in:
devopsandClaude Sonnet 5 committed 2026-09-14 05:00:19 +00:00
1 parent 009d5c63df
commit fe4ed8005a
3 files changed
+183

No files matched your search

@@ -82,3 +82,69 @@
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.
@@ -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.
+2
View File
@@ -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