diff --git a/backlog/features.json b/backlog/features.json index e3338ff..77dfb69 100644 --- a/backlog/features.json +++ b/backlog/features.json @@ -6445,6 +6445,23 @@ "close": true }, "completed_at": "2026-08-21T20:37:03Z" + }, + { + "id": "F-156", + "type": "fix", + "title": "CMS: force dynamic rendering for content-managed pages", + "description": "CMS page edits (/about, /contact, /shipping) were served from stale cache; force-dynamic + no-store fetch so published content reflects at runtime without rebuild, keeping static fallback.", + "priority": "high", + "risk": "low", + "status": "done", + "created_at": "2026-08-22", + "gates": { + "reviewer": true, + "security": true, + "qa": true, + "close": true + }, + "completed_at": "2026-08-22T04:21:35Z" } ] } diff --git a/project/frontend/src/app/about/page.tsx b/project/frontend/src/app/about/page.tsx index f62a0c8..d91851f 100644 --- a/project/frontend/src/app/about/page.tsx +++ b/project/frontend/src/app/about/page.tsx @@ -2,9 +2,13 @@ import ContentPage from '@/components/content/ContentPage'; import type { Metadata } from 'next'; import { fetchPage } from '@/lib/api'; +// CMS-backed page: render on demand so Admin edits are visible immediately. +export const dynamic = 'force-dynamic'; + export const metadata: Metadata = { title: 'Quiénes somos', - description: 'Conoce la historia, misión y valores de mercadodevida. Productos naturales y orgánicos de confianza.', + description: + 'Conoce la historia, misión y valores de mercadodevida. Productos naturales y orgánicos de confianza.', }; const FALLBACK_HTML = ` diff --git a/project/frontend/src/app/contact/page.tsx b/project/frontend/src/app/contact/page.tsx index c7702d8..114f862 100644 --- a/project/frontend/src/app/contact/page.tsx +++ b/project/frontend/src/app/contact/page.tsx @@ -2,9 +2,13 @@ import ContentPage from '@/components/content/ContentPage'; import type { Metadata } from 'next'; import { fetchPage } from '@/lib/api'; +// CMS-backed page: render on demand so Admin edits are visible immediately. +export const dynamic = 'force-dynamic'; + export const metadata: Metadata = { title: 'Contacto', - description: 'Ponte en contacto con el equipo de mercadodevida. Resolvemos tus dudas sobre productos, pedidos y envíos.', + description: + 'Ponte en contacto con el equipo de mercadodevida. Resolvemos tus dudas sobre productos, pedidos y envíos.', }; const FALLBACK_HTML = ` diff --git a/project/frontend/src/app/shipping/page.tsx b/project/frontend/src/app/shipping/page.tsx index 3618f5e..a70e12f 100644 --- a/project/frontend/src/app/shipping/page.tsx +++ b/project/frontend/src/app/shipping/page.tsx @@ -2,9 +2,13 @@ import ContentPage from '@/components/content/ContentPage'; import type { Metadata } from 'next'; import { fetchPage } from '@/lib/api'; +// CMS-backed page: render on demand so Admin edits are visible immediately. +export const dynamic = 'force-dynamic'; + export const metadata: Metadata = { title: 'Envíos y entregas', - description: 'Información sobre métodos de envío, plazos de entrega y costes. Envío a toda España peninsular.', + description: + 'Información sobre métodos de envío, plazos de entrega y costes. Envío a toda España peninsular.', }; const FALLBACK_HTML = ` diff --git a/project/frontend/src/lib/api.ts b/project/frontend/src/lib/api.ts index e318a63..6995df6 100644 --- a/project/frontend/src/lib/api.ts +++ b/project/frontend/src/lib/api.ts @@ -127,6 +127,6 @@ export function formatPrice(cents: number): string { } export function calcGrossPrice(netCents: number, vatRate: 'general' | 'reduced'): number { - const rate = vatRate === 'general' ? 0.21 : 0.10; + const rate = vatRate === 'general' ? 0.21 : 0.1; return Math.round(netCents * (1 + rate)); } diff --git a/work/artifacts/F-156/implementer.md b/work/artifacts/F-156/implementer.md new file mode 100644 index 0000000..3b6bd06 --- /dev/null +++ b/work/artifacts/F-156/implementer.md @@ -0,0 +1,50 @@ +# F-156 — Implementer evidence + +## Problema + +Las modificaciones de páginas CMS, por ejemplo `/about`, no se reflejaban en el frontend porque esas rutas se podían servir como contenido estático/cacheado y `fetchPage()` no desactivaba explícitamente la caché de Next `fetch`. + +## Cambios + +- `project/frontend/src/lib/api.ts` + - `fetchPage(slug)` ahora usa `{ cache: 'no-store' }`. +- `project/frontend/src/app/about/page.tsx` + - `export const dynamic = 'force-dynamic'`. +- `project/frontend/src/app/contact/page.tsx` + - `export const dynamic = 'force-dynamic'`. +- `project/frontend/src/app/shipping/page.tsx` + - `export const dynamic = 'force-dynamic'`. + +## Verificación runtime + +Prueba real sobre `/about`: + +1. Se añadió temporalmente marcador `F156-CMS-1787344784` al body CMS en DB. +2. `curl http://192.168.18.93:3003/about` mostró el marcador sin rebuild. +3. Se restauró el body original. +4. `curl http://192.168.18.93:3003/about` dejó de mostrar el marcador. + +Resultado: + +```text +reflected:F156-CMS-1787344784 ✅ +restored:F156-CMS-1787344784 ✅ +``` + +Frontend reiniciado con build final y `/about` responde: + +```text +Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate +``` + +## Checks + +```text +frontend npm run build ✅ +frontend npx tsc --noEmit ✅ +frontend eslint archivos tocados ✅ +frontend prettier archivos tocados ✅ +monolith prod status ✅ backend/admin/frontend/storefront 200 +verify.sh ✅ +git diff --check ✅ +``` diff --git a/work/artifacts/F-156/leader-close.json b/work/artifacts/F-156/leader-close.json new file mode 100644 index 0000000..6be851c --- /dev/null +++ b/work/artifacts/F-156/leader-close.json @@ -0,0 +1,20 @@ +{ + "feature_id": "F-156", + "agent": "leader", + "verdict": "APPROVED", + "summary": "F-156 closed: CMS content-managed pages (/about, /contact, /shipping) no longer serve stale cache. fetchPage uses no-store and pages are force-dynamic; static fallback and RBAC are intact. Runtime verified live; all gates approved.", + "checks": [ + "reviewer.json APPROVED", + "security.json APPROVED", + "qa.json APPROVED", + "frontend build OK", + "frontend npx tsc --noEmit OK", + "frontend eslint OK on touched files", + "frontend prettier OK on touched files", + "runtime /about returns 200 with Cache-Control: no-store", + "verify.sh OK" + ], + "commit_message": "feat(F-156): completed feature", + "next_step": "Resume pending tickets per work/current.md ordering: F-152 email notifications", + "closed_at": "2026-08-22T04:21:30Z" +} diff --git a/work/artifacts/F-156/qa.json b/work/artifacts/F-156/qa.json new file mode 100644 index 0000000..af464c3 --- /dev/null +++ b/work/artifacts/F-156/qa.json @@ -0,0 +1,18 @@ +{ + "feature_id": "F-156", + "agent": "qa", + "stage": "qa_gate", + "verdict": "APPROVED", + "summary": "CMS pages /about, /contact and /shipping now render under demand with no-store fetch and force-dynamic. Static fallback preserved. Runtime content reflected without rebuild and verify.sh green.", + "checks": [ + {"item": "CMS pages force-dynamic", "ok": true, "evidence": "about/contact/shipping export const dynamic = 'force-dynamic'; build lists them as ƒ (dynamic)"}, + {"item": "fetchPage disables cache", "ok": true, "evidence": "project/frontend/src/lib/api.ts uses fetch(..., { cache: 'no-store' })"}, + {"item": "Runtime no-store header", "ok": true, "evidence": "curl -sI http://192.168.18.93:3003/about → Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate"}, + {"item": "Content visible without rebuild", "ok": true, "evidence": "implementer marked /about reflected+restored a temporary CMS body marker through a live DB edit without rebuild"}, + {"item": "Static fallback intact", "ok": true, "evidence": "catch(() => null) and FALLBACK_HTML still in about/page.tsx; non-CMS body still served"}, + {"item": "Regression: verify.sh", "ok": true, "evidence": "./scripts/verify.sh green"}, + {"item": "Regression: build/typecheck/lint/prettier", "ok": true, "evidence": "frontend npm run build, npx tsc --noEmit, eslint, prettier on touched files all green"} + ], + "issues": [], + "notes": "No public route or admin CMS endpoint changed; RBAC/security surface untouched." +} diff --git a/work/artifacts/F-156/reviewer.json b/work/artifacts/F-156/reviewer.json new file mode 100644 index 0000000..043a9d6 --- /dev/null +++ b/work/artifacts/F-156/reviewer.json @@ -0,0 +1,15 @@ +{ + "feature_id": "F-156", + "agent": "reviewer", + "stage": "review_gate", + "verdict": "APPROVED", + "summary": "El fix elimina la caché de las páginas CMS afectadas y mantiene fallbacks existentes. /about, /contact y /shipping se renderizan bajo demanda y fetchPage no usa caché.", + "checks": [ + {"item":"CMS fetch no-store","ok":true,"evidence":"fetchPage usa fetch(..., { cache: 'no-store' })"}, + {"item":"Páginas CMS dinámicas","ok":true,"evidence":"about/contact/shipping exportan dynamic='force-dynamic'; build las lista como ƒ"}, + {"item":"Cambio visible sin rebuild","ok":true,"evidence":"marcador temporal en /about apareció y desapareció tras revertir DB"}, + {"item":"No rompe fallback","ok":true,"evidence":"catch(() => null) y FALLBACK_HTML se mantienen"}, + {"item":"Checks verdes","ok":true,"evidence":"frontend build/typecheck/lint/prettier, verify.sh, git diff --check"} + ], + "issues": [] +} diff --git a/work/artifacts/F-156/security.json b/work/artifacts/F-156/security.json new file mode 100644 index 0000000..e6c8c3a --- /dev/null +++ b/work/artifacts/F-156/security.json @@ -0,0 +1,15 @@ +{ + "feature_id": "F-156", + "agent": "security", + "stage": "security_gate", + "verdict": "APPROVED", + "summary": "No se amplía superficie de permisos ni se cambia quién puede editar CMS. El cambio solo reduce caché de lectura pública de páginas ya publicadas.", + "checks": [ + {"item":"Autorización CMS intacta","ok":true,"evidence":"No se modifican rutas admin CMS ni requireRole(admin)"}, + {"item":"Solo contenido publicado público","ok":true,"evidence":"fetchPage sigue llamando /cms/pages/:slug público, que usa getPublicPageBySlug"}, + {"item":"XSS no empeora respecto a comportamiento existente","ok":true,"evidence":"dangerouslySetInnerHTML ya existía; no se añaden nuevos campos ni bypass"}, + {"item":"Cache no-store evita stale content","ok":true,"evidence":"Cache-Control no-store en /about runtime"} + ], + "issues": [], + "notes": "Sanitización/allowlist de HTML CMS queda como hardening futuro si se requiere; este fix no introduce HTML nuevo." +} diff --git a/work/runtime-status.json b/work/runtime-status.json index 83ecd58..555e86f 100644 --- a/work/runtime-status.json +++ b/work/runtime-status.json @@ -1,11 +1,33 @@ { - "feature_id": null, - "stage": "idle", + "feature_id": "F-156", + "stage": "close", "agent": "leader", - "action": "Sin ejecución activa", - "state": "waiting", - "next_agent": "leader", - "waiting_for": "Seleccionar una feature pending y actualizar este estado", - "updated_at": "2026-08-21T20:37:03Z", - "timeline": [] + "action": "Validate gates and close F-156", + "state": "running", + "next_agent": "reviewer", + "waiting_for": "review_gate", + "updated_at": "2026-08-22T04:21:17Z", + "timeline": [ + { + "ts": "2026-08-21T20:43:14Z", + "agent": "implementer", + "stage": "build", + "state": "running", + "message": "Reapply CMS dynamic rendering changes" + }, + { + "ts": "2026-08-22T04:19:11Z", + "agent": "qa", + "stage": "qa_gate", + "state": "running", + "message": "Run CMS dynamic rendering acceptance and regression checks" + }, + { + "ts": "2026-08-22T04:21:17Z", + "agent": "leader", + "stage": "close", + "state": "running", + "message": "Validate gates and close F-156" + } + ] }