Compare commits

...
Author SHA1 Message Date
devops 167ae4057a fix(ecommerce): min-w-0 en Newsletter del footer, forzaba overflow a 320px 2026-09-20 18:09:10 +00:00
devops 51300c234a chore: promote v1.0.271 to production [skip ci] 2026-09-20 17:39:43 +00:00
devops 0c0efe3727 chore: release v1.0.271 [skip ci] 2026-09-20 17:39:42 +00:00
devops e4dafe9017 Merge branch 'ci/automate-staging-to-prod-promotion-2026-09-20'
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 23s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 8m47s
2026-09-20 17:30:00 +00:00
devopsandClaude Sonnet 5 fed5dfc1c1 ci(ecommerce): automatizar promocion de Staging a Produccion
Agrega el step "Promover a Produccion (CD)" al mismo job que ya
despliega a Staging en build.yaml -- misma logica de
scripts/promote-to-production.sh (sed/perl sobre el manifest de
Produccion) pero sin el read -rp interactivo, gateado por la misma
verificacion de promocion segura (merge-base --is-ancestor) que ya usa
el step de Staging.

Decision explicita del usuario 2026-09-20: cero gate humano entre
Staging y Produccion -- la promocion manual deja de ser el camino
normal, scripts/promote-to-production.sh queda solo como herramienta
de excepcion/rollback.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 17:27:37 +00:00
devops 5773154db9 chore: release v1.0.270 [skip ci] 2026-09-20 17:04:00 +00:00
devopsandClaude Sonnet 5 553b1c7a32 Merge branch 'fix/medusa-fetch-cache-multi-pod-2026-09-20'
Build and Push Frontend / Escaneo de Secretos (Gitleaks) (push) Successful in 26s
Build and Push Frontend / Construir y Subir Imagen (push) Successful in 13m9s
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 16:50:25 +00:00
devopsandClaude Sonnet 5 d9c39ba2a1 fix(ecommerce): sacar cache de fetch que competia contra force-dynamic
medusaFetch() usaba next:{revalidate:60,tags:["medusa-products"]}, cache
en memoria por-pod que sobrevivia a force-dynamic en las paginas que lo
consumen. Con frontend-deploy en 2 replicas sin cacheHandler compartido,
el POST de /api/revalidate del subscriber de Medusa solo llegaba a uno
de los dos pods, dejando al otro sirviendo datos viejos hasta que el TTL
expirara solo -- reportado por el usuario: ediciones manuales de imagen
no se reflejaban en el storefront, pero la importacion por CSV (mucho
mas lenta) si, porque el TTL tenia tiempo de sobra para expirar en
ambos pods.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 16:50:16 +00:00
gitea-actions 164223e844 chore(gitops): deploy Docs Portal v1.0.269 [skip ci] 2026-09-20 16:24:54 +00:00
devopsandClaude Sonnet 5 ee327b420c Merge branch 'docs/adr-0010-bugs-imagenes-2026-09-20'
Build and Push Docs Portal / Escaneo de Secretos (Gitleaks) (push) Successful in 26s
Build and Push Docs Portal / Construir y publicar Docs Portal (push) Successful in 6m35s
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 16:17:55 +00:00
devopsandClaude Sonnet 5 17348e6285 docs(adr-0010): agregar dos bugs reportados al checklist de staging
Importacion manual de imagenes de producto no se refleja en el
storefront (CSV si funciona), y en mobile la visualizacion de imagenes
de producto se corta obligando a scrollear. Ambos reportados por el
usuario 2026-09-20, en investigacion.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QMP7ULbxbsB5g1vELNAT3D
2026-09-20 16:17:47 +00:00
7 changed files with 106 additions and 12 deletions

No files matched your search

+51 -2
View File
@@ -129,9 +129,16 @@ jobs:
# Cambio 2026-09-14: todo push a main promueve a STAGING
# automaticamente (workloads/ecommerce-staging), no a Produccion
# directo -- decision del usuario para dejar de probar cambios en
# vivo contra Prod. Promover a Produccion (workloads/ecommerce) es
# una accion manual aparte, ver scripts/promote-to-production.sh.
# vivo contra Prod.
# Cambio 2026-09-20: la promocion de Staging a Produccion (antes
# scripts/promote-to-production.sh, manual con confirmacion
# interactiva) ahora corre sola en este mismo job -- ver el step
# "Promover a Produccion (CD)" mas abajo, justo despues del de
# Staging. Decision explicita del usuario: cero gate humano entre
# Staging y Produccion. El script manual queda solo como
# herramienta de rollback/excepcion, ya no es el camino normal.
MANIFEST_FILE: workloads/ecommerce-staging/frontend/patches/deployment.yaml
PROD_MANIFEST_FILE: workloads/ecommerce/frontend/patches/deployment.yaml
KUSTOMIZE_DIR: workloads/ecommerce-staging/frontend
IMAGE_NAME: gitea.cruzcloud.net/devops/ecommerce-frontend
@@ -604,6 +611,48 @@ jobs:
# sobrescribir commits ajenos o promocionar una versión antigua.
git push origin HEAD:main
# Automatiza lo que antes era scripts/promote-to-production.sh (paso
# manual con confirmación interactiva, read -rp). Decisión del
# usuario 2026-09-20: cero pasos manuales entre Staging y
# Producción — la misma imagen que acaba de promocionar a Staging
# arriba se promueve sola a Producción, sin gate humano en el
# medio. Mismo sed/perl que el script manual (mismo MANIFEST, otro
# archivo), corriendo como un segundo commit separado justo
# después del de Staging — no se pisan en un solo commit porque
# Staging y Producción son releases conceptualmente distintos
# (permite revertir uno sin tocar el otro).
- name: Promover a Producción (CD)
if: steps.promotion.outputs.promote == 'true'
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.vars.outputs.VERSION }}"
git config user.name "Cristian Felipe"
git config user.email "[email protected]"
# Re-fetch: el step de Staging de arriba ya pusheó un commit a
# main en este mismo job -- sin este refresh, este checkout
# local queda un commit atrás y el push de abajo se rechaza.
git fetch origin main
git checkout -B main origin/main
sed -i -E "s|(image: ${IMAGE_NAME}:).*|\1${VERSION}|g" "${PROD_MANIFEST_FILE}"
perl -0777 -pi -e "s/(- name: APP_VERSION\n\s*value: ).*/\${1}${VERSION}/" "${PROD_MANIFEST_FILE}"
git add "${PROD_MANIFEST_FILE}"
if git diff --cached --quiet; then
echo "Producción ya está en ${VERSION}."
exit 0
fi
git commit -m "chore: promote ${VERSION} to production [skip ci]"
git push origin HEAD:main
- name: Resumen del pipeline
if: always()
shell: bash
@@ -425,3 +425,27 @@ Resultados reales, uno por uno (código de cada prueba en
un dispositivo real.
21b. **Accesibilidad del checkout completo** por teclado (no solo el
buscador) — tab order y foco visible en los 3 pasos.
22. **Importación manual de imágenes no se refleja en el storefront**
(reportado por el usuario 2026-09-20): subir/editar imágenes de un
producto a mano (vs. importación por CSV, que sí funciona) no
aparece del lado del storefront. Falta reproducir contra el flujo
real de importación manual del admin de Medusa (ver
`temp-admin`/`ari-shopping-tienda` en memoria) y confirmar si es
caché (ISR de Next, `force-dynamic` ya está en la página de
producto — ver más arriba), staleness de Meilisearch (ver memoria
`search-inventory-staleness`, mismo síntoma de "el catálogo directo
sí, la búsqueda no") o algo específico del pipeline de importación
en `commerce-backend`.
23. **En mobile, la visualización de imágenes de producto se corta y
obliga a scrollear** (reportado por el usuario 2026-09-20, con
captura real de `shop.cruzcloud.net` en un producto con múltiples
fotos). Toda la clase de bug de overflow por falta de `min-w-0` ya
se revisó en vivo en esta sesión y sigue presente en
`product-gallery.tsx` y `app/product/[slug]/page.tsx` — sospecha
sin confirmar: el modal "Zoom interactivo"
(`ProductGallery`/`DialogContent`, `min-h-[70vh]` +
`overflow-hidden`) en viewport móvil real, donde la barra de
direcciones de Chrome/Safari hace que `100vh` mida más que el área
visible real. Pendiente reproducir con un dispositivo real o
emulación de `visualViewport` (Playwright con viewport móvil no
replica el comportamiento dinámico de la barra de direcciones).
@@ -25,7 +25,7 @@ spec:
runAsNonRoot: false
containers:
- name: app
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.268
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.269
# El base saca TODAS las capabilities. Probado en vivo y
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al
@@ -26,11 +26,11 @@ spec:
runAsUser: 1001
containers:
- name: app
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.266
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.271
env:
# Ver comentario en workloads/ecommerce/frontend/patches/deployment.yaml
- name: APP_VERSION
value: v1.0.266
value: v1.0.271
- name: HEADLESS_PROVIDER
value: medusa
# Backend COMPARTIDO con Producción a propósito (decisión del
@@ -34,7 +34,16 @@ const SHOP_LINKS = [
];
export function Footer() {
return <footer className="bg-ari-marino text-white"><div className="mx-auto grid max-w-7xl gap-10 px-4 py-14 sm:grid-cols-2 lg:grid-cols-4"><div><div className="flex flex-col items-start gap-3"><img src="/images/brand/logo-ari.svg" alt="ARI shopping" className="h-11 w-auto" width={200} height={64} />{/* eslint-disable-line @next/next/no-img-element -- vector propio, next/image no optimiza SVG sin habilitar dangerouslyAllowSVG */}<p className="text-xs text-white/60">Tesoros para cada aventura.</p></div><div className="mt-5 flex gap-3">{SOCIAL_LINKS.map(({ label, href, Icon }) => <a key={label} href={href} target="_blank" rel="noopener noreferrer" aria-label={label} className="text-white/80 transition hover:text-ari-coral-claro"><Icon /></a>)}</div></div><FooterColumn title="Compra" links={SHOP_LINKS} /><FooterColumn title="Ayuda" links={HELP_LINKS} /><div><h3 className="font-black">Newsletter</h3><p className="mt-3 text-sm leading-6 text-white/60">Recibe lanzamientos y promociones especiales.</p><div className="mt-4 flex rounded-full bg-white p-1"><input className="min-w-0 flex-1 bg-transparent px-4 text-sm text-slate-950 outline-none" placeholder="Tu correo" /><button className="rounded-full bg-ari-coral px-4 text-sm font-black text-white">Unirme</button></div></div></div><div className="border-t border-white/10 py-5 text-center text-xs text-white/50">© 2026 ARI Shopping · Frontend headless {appVersion} · GitOps</div></footer>;
return <footer className="bg-ari-marino text-white"><div className="mx-auto grid max-w-7xl gap-10 px-4 py-14 sm:grid-cols-2 lg:grid-cols-4"><div><div className="flex flex-col items-start gap-3"><img src="/images/brand/logo-ari.svg" alt="ARI shopping" className="h-11 w-auto" width={200} height={64} />{/* eslint-disable-line @next/next/no-img-element -- vector propio, next/image no optimiza SVG sin habilitar dangerouslyAllowSVG */}<p className="text-xs text-white/60">Tesoros para cada aventura.</p></div><div className="mt-5 flex gap-3">{SOCIAL_LINKS.map(({ label, href, Icon }) => <a key={label} href={href} target="_blank" rel="noopener noreferrer" aria-label={label} className="text-white/80 transition hover:text-ari-coral-claro"><Icon /></a>)}</div></div><FooterColumn title="Compra" links={SHOP_LINKS} /><FooterColumn title="Ayuda" links={HELP_LINKS} />{/* min-w-0: sin esto, un grid item de texto toma como minimo su ancho
max-content (una sola linea, "Recibe lanzamientos y promociones
especiales." sin wrappear, ~335px) en vez de dejarse achicar y
wrappear dentro del track del grid -- confirmado en vivo 2026-09-20
contra un viewport real de 320px: la columna entera del footer (y
con ella, la pagina completa) quedaba forzada a 335px de ancho por
este unico parrafo. Mismo bug de fondo que el ya documentado en
product-gallery.tsx/app/product/[slug]/page.tsx, en un grid en vez
de un overflow-x-auto. */}
<div className="min-w-0"><h3 className="font-black">Newsletter</h3><p className="mt-3 text-sm leading-6 text-white/60">Recibe lanzamientos y promociones especiales.</p><div className="mt-4 flex rounded-full bg-white p-1"><input className="min-w-0 flex-1 bg-transparent px-4 text-sm text-slate-950 outline-none" placeholder="Tu correo" /><button className="rounded-full bg-ari-coral px-4 text-sm font-black text-white">Unirme</button></div></div></div><div className="border-t border-white/10 py-5 text-center text-xs text-white/50">© 2026 ARI Shopping · Frontend headless {appVersion} · GitOps</div></footer>;
}
function FooterColumn({ title, links }: { title: string; links: { label: string; href: string }[] }) {
@@ -25,14 +25,14 @@ spec:
runAsUser: 1001
containers:
- name: app
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.263
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.271
env:
# La actualiza el mismo paso del CD (.gitea/workflows/build.yaml)
# que bumpea el tag de la imagen -- el footer la lee
# (process.env.APP_VERSION) para mostrar la versión real
# desplegada en vez de un "v4.1" fijo.
- name: APP_VERSION
value: v1.0.263
value: v1.0.271
- name: HEADLESS_PROVIDER
value: medusa
- name: MEDUSA_BACKEND_URL
@@ -111,14 +111,26 @@ async function medusaFetch<T>(
const publishableKey = process.env.MEDUSA_PUBLISHABLE_KEY;
// Sin next.revalidate/tags a proposito (reportado por el usuario
// 2026-09-20: imagenes editadas a mano en Medusa no se reflejaban en
// el storefront, pero por CSV si). Causa real: frontend-deploy corre 2
// replicas sin cacheHandler compartido -- cada pod tenia su propio
// cache en memoria de este fetch (next.revalidate:60), y el POST de
// /api/revalidate (revalidateTag) del subscriber de Medusa solo le
// llegaba a UNO de los dos pods via el Service, dejando al otro
// sirviendo la respuesta vieja hasta que el TTL expirara solo. Una
// edicion puntual + revisar al toque tenia ~50% de chance de pegarle
// al pod stale; una importacion CSV (minutos de duracion) le daba
// tiempo de sobra al TTL para expirar en ambos pods, disimulando el
// mismo bug. Las paginas que consumen esto ya son force-dynamic /
// dinamicas por diseño (ver comentario en app/page.tsx y
// app/product/[slug]/page.tsx) -- este next.revalidate competia contra
// esa intencion en vez de cumplirla. Sin este bloque, Next 15 no
// cachea el fetch por default dentro de una ruta dinamica.
const response = await fetch(url, {
headers: publishableKey
? { "x-publishable-api-key": publishableKey }
: undefined,
next: {
revalidate: 60,
tags: ["medusa-products"],
},
});
if (!response.ok) {