feat(F-156): completed feature
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = `
|
||||||
|
|||||||
@@ -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 = `
|
||||||
|
|||||||
@@ -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 = `
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
50
work/artifacts/F-156/implementer.md
Normal file
50
work/artifacts/F-156/implementer.md
Normal 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 ✅
|
||||||
|
```
|
||||||
20
work/artifacts/F-156/leader-close.json
Normal file
20
work/artifacts/F-156/leader-close.json
Normal 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"
|
||||||
|
}
|
||||||
18
work/artifacts/F-156/qa.json
Normal file
18
work/artifacts/F-156/qa.json
Normal 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."
|
||||||
|
}
|
||||||
15
work/artifacts/F-156/reviewer.json
Normal file
15
work/artifacts/F-156/reviewer.json
Normal 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": []
|
||||||
|
}
|
||||||
15
work/artifacts/F-156/security.json
Normal file
15
work/artifacts/F-156/security.json
Normal 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."
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user