feat(F-156): completed feature

This commit is contained in:
chattie
2026-08-22 06:21:35 +02:00
parent 3b74fc10d2
commit 7a8595bebb
11 changed files with 181 additions and 12 deletions

View File

@@ -6445,6 +6445,23 @@
"close": true "close": true
}, },
"completed_at": "2026-08-21T20:37:03Z" "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"
} }
] ]
} }

View File

@@ -2,9 +2,13 @@ import ContentPage from '@/components/content/ContentPage';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { fetchPage } from '@/lib/api'; 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 = { export const metadata: Metadata = {
title: 'Quiénes somos', 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 = ` const FALLBACK_HTML = `

View File

@@ -2,9 +2,13 @@ import ContentPage from '@/components/content/ContentPage';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { fetchPage } from '@/lib/api'; 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 = { export const metadata: Metadata = {
title: 'Contacto', 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 = ` const FALLBACK_HTML = `

View File

@@ -2,9 +2,13 @@ import ContentPage from '@/components/content/ContentPage';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { fetchPage } from '@/lib/api'; 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 = { export const metadata: Metadata = {
title: 'Envíos y entregas', 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 = ` const FALLBACK_HTML = `

View File

@@ -127,6 +127,6 @@ export function formatPrice(cents: number): string {
} }
export function calcGrossPrice(netCents: number, vatRate: 'general' | 'reduced'): number { 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)); return Math.round(netCents * (1 + rate));
} }

View File

@@ -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 ✅
```

View File

@@ -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"
}

View File

@@ -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."
}

View File

@@ -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": []
}

View File

@@ -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."
}

View File

@@ -1,11 +1,33 @@
{ {
"feature_id": null, "feature_id": "F-156",
"stage": "idle", "stage": "close",
"agent": "leader", "agent": "leader",
"action": "Sin ejecución activa", "action": "Validate gates and close F-156",
"state": "waiting", "state": "running",
"next_agent": "leader", "next_agent": "reviewer",
"waiting_for": "Seleccionar una feature pending y actualizar este estado", "waiting_for": "review_gate",
"updated_at": "2026-08-21T20:37:03Z", "updated_at": "2026-08-22T04:21:17Z",
"timeline": [] "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"
}
]
} }