feat(F-141): completed feature
This commit is contained in:
@@ -6212,6 +6212,38 @@
|
||||
"security": false,
|
||||
"qa": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "F-141",
|
||||
"type": "fix",
|
||||
"title": "Next builds: isolate frontend workspace and avoid API prerender dependency",
|
||||
"description": "Fix Next.js workspace-root warnings and prevent customer frontend production builds from failing when the backend API is unavailable during static prerendering.",
|
||||
"priority": "high",
|
||||
"risk": "med",
|
||||
"status": "done",
|
||||
"created_at": "2026-08-21",
|
||||
"gates": {
|
||||
"reviewer": true,
|
||||
"security": true,
|
||||
"qa": true,
|
||||
"close": true
|
||||
},
|
||||
"completed_at": "2026-08-21T20:02:01Z"
|
||||
},
|
||||
{
|
||||
"id": "F-142",
|
||||
"type": "feature",
|
||||
"title": "Reporting: architecture and implementation task plan",
|
||||
"description": "Analyze existing ecommerce, POS, orders, payments, customers, products, stock, cash, refunds, discounts and taxes; produce reporting architecture and phased implementation tasks before dashboard coding.",
|
||||
"priority": "high",
|
||||
"risk": "med",
|
||||
"status": "pending",
|
||||
"created_at": "2026-08-21",
|
||||
"gates": {
|
||||
"reviewer": false,
|
||||
"security": false,
|
||||
"qa": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import type { NextConfig } from "next";
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import type { NextConfig } from 'next';
|
||||
|
||||
const frontendRoot = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
turbopack: {
|
||||
root: frontendRoot,
|
||||
},
|
||||
outputFileTracingRoot: frontendRoot,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{ protocol: 'https', hostname: '**' },
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { fetchProducts } from '@/lib/api';
|
||||
|
||||
// This legacy admin route depends on the live catalog API.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Admin Productos — mercadodevida',
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { Metadata } from 'next';
|
||||
import { fetchBrandBySlug, fetchProducts, formatPrice } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// ISR para fichas de marca (F-132).
|
||||
export const revalidate = 3600;
|
||||
// Brand data comes from the live backend; resolve it at request time.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { Metadata } from 'next';
|
||||
import { fetchBrands } from '@/lib/api';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
// ISR para /brands (F-132).
|
||||
export const revalidate = 3600;
|
||||
// The catalog is served by the live backend; never fetch it during build.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Marcas — mercadodevida',
|
||||
|
||||
@@ -4,8 +4,8 @@ import type { Metadata } from 'next';
|
||||
import { fetchCategoryBySlug, fetchProducts, fetchBrands } from '@/lib/api';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// ISR para fichas de categoría (F-132).
|
||||
export const revalidate = 3600;
|
||||
// Category data comes from the live backend; resolve it at request time.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
|
||||
@@ -3,8 +3,9 @@ import type { Metadata } from 'next';
|
||||
import { fetchCategories } from '@/lib/api';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
// ISR para /categories (F-132).
|
||||
export const revalidate = 3600;
|
||||
// Categories depend on the live catalog API. Do not call the API during
|
||||
// production builds: the backend may be deployed separately or unavailable.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Categorías — mercadodevida',
|
||||
|
||||
@@ -4,7 +4,8 @@ import CategoriesGrid from '@/components/home/CategoriesGrid';
|
||||
import BrandsSection from '@/components/home/BrandsSection';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
export const revalidate = 3600; // ISR: revalidate every hour
|
||||
// Home sections load catalog/CMS data from the live backend.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function HomePage() {
|
||||
return (
|
||||
|
||||
@@ -15,8 +15,8 @@ import ProductAddToCart from '@/components/cart/ProductAddToCart';
|
||||
import ProductAttributes from '@/components/product/ProductAttributes';
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
|
||||
// ISR para fichas de producto (F-132).
|
||||
export const revalidate = 3600;
|
||||
// Product data comes from the live backend; resolve it at request time.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>;
|
||||
|
||||
@@ -5,9 +5,8 @@ import { fetchProducts, fetchBrands, fetchCategories, formatPrice } from '@/lib/
|
||||
import { formatRichText } from '@/lib/format-rich-text';
|
||||
import { CmsBlock } from '@/components/content/CmsBlock';
|
||||
|
||||
// ISR: revalidar listado cada hora para que la home / productos reflejen
|
||||
// cambios del catálogo sin necesidad de redeploy (F-132).
|
||||
export const revalidate = 3600;
|
||||
// The catalog is served by the live backend; never fetch it during build.
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Productos — mercadodevida',
|
||||
|
||||
42
work/artifacts/F-141/architect.md
Normal file
42
work/artifacts/F-141/architect.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# F-141 — Arquitectura del fix de build Next.js
|
||||
|
||||
## Diagnóstico
|
||||
|
||||
Hay tres aplicaciones Next independientes en el monorepo:
|
||||
|
||||
- `project/apps/admin` — panel admin.
|
||||
- `project/frontend` — customer frontend, con `/categories` como Server Component async.
|
||||
- `project/storefront` — storefront SEO.
|
||||
|
||||
`project/frontend` no tenía `turbopack.root` ni `outputFileTracingRoot`. Además, `/categories` declara ISR (`revalidate = 3600`) y ejecuta `fetchCategories()` durante el prerender. Cuando `NEXT_PUBLIC_API_URL` apunta a `192.168.18.93:3000` y el backend no está levantado, el build falla con `ECONNREFUSED`.
|
||||
|
||||
El error posterior que intenta leer:
|
||||
|
||||
```text
|
||||
project/frontend/.next/prerender-manifest.json
|
||||
```
|
||||
|
||||
es una consecuencia de que el build del frontend aborta antes de generar su manifest, no un archivo que deba copiarse desde otra aplicación.
|
||||
|
||||
## Diseño
|
||||
|
||||
1. Fijar la raíz de Turbopack a cada aplicación:
|
||||
- `project/frontend/next.config.ts`: `turbopack.root = frontendRoot`.
|
||||
- Mantener la configuración equivalente ya aplicada en admin y storefront.
|
||||
2. Fijar `outputFileTracingRoot` a la raíz de cada app para que Next no recorra el workspace ni confunda `.next` de otra aplicación.
|
||||
3. Convertir `/categories` a renderizado dinámico (`dynamic = 'force-dynamic'`) porque sus datos dependen del API vivo. Así el build no hace llamadas de negocio al backend; el fetch sucede cuando se atiende la petición.
|
||||
4. Mantener la fuente de verdad y el contrato API intactos. No introducir mocks, datos paralelos ni fallback de categorías vacío.
|
||||
|
||||
## No incluido
|
||||
|
||||
- No cambiar las URLs del API.
|
||||
- No ocultar errores de runtime.
|
||||
- No modificar `storefront` más allá de la configuración necesaria.
|
||||
- No transformar todos los reportes o dashboards; eso pertenece a F-142.
|
||||
|
||||
## Verificación
|
||||
|
||||
- `cd project/frontend && rm -rf .next && NEXT_PUBLIC_API_URL=http://192.168.18.93:3000 npm run build` debe terminar aunque el backend no esté disponible.
|
||||
- La salida no debe seleccionar `project/package-lock.json` ni mostrar el warning de lockfiles.
|
||||
- `project/frontend/.next/prerender-manifest.json` debe existir después del build.
|
||||
- Admin, backend y storefront no deben perder su build.
|
||||
52
work/artifacts/F-141/implementer.md
Normal file
52
work/artifacts/F-141/implementer.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# F-141 — Implementer evidence
|
||||
|
||||
## Cambios realizados
|
||||
|
||||
- `project/frontend/next.config.ts`
|
||||
- Añadidos `turbopack.root` y `outputFileTracingRoot`, ambos fijados a `frontendRoot`.
|
||||
- El frontend ya no hereda la raíz de workspace de `project/package-lock.json` ni intenta mezclar el `.next` de `frontend` con otra aplicación.
|
||||
- Rutas del customer frontend que consultan catálogo/CMS en servidor:
|
||||
- `src/app/page.tsx`
|
||||
- `src/app/categories/page.tsx`
|
||||
- `src/app/categories/[slug]/page.tsx`
|
||||
- `src/app/brands/page.tsx`
|
||||
- `src/app/brands/[slug]/page.tsx`
|
||||
- `src/app/products/page.tsx`
|
||||
- `src/app/products/[slug]/page.tsx`
|
||||
- `src/app/admin/products/page.tsx`
|
||||
- Se cambió ISR estático (`revalidate = 3600`) por `dynamic = 'force-dynamic'` donde los datos son obligatorios.
|
||||
- `/search` ya era dinámico y no requirió cambios.
|
||||
|
||||
## Motivo técnico
|
||||
|
||||
`/categories` ejecutaba `fetchCategories()` durante el prerender estático. Con `NEXT_PUBLIC_API_URL=http://192.168.18.93:3000` y backend apagado, Next abortaba el build con `ECONNREFUSED`. Convertir las páginas dependientes de API en dinámicas mantiene la fuente de verdad en el backend y traslada el fetch al request runtime; no añade mocks ni datos paralelos.
|
||||
|
||||
El error posterior de `frontend/.next/prerender-manifest.json` era consecuencia del build abortado antes de escribir el manifest. Tras el build correcto el archivo vuelve a existir.
|
||||
|
||||
## Verificación
|
||||
|
||||
```text
|
||||
cd project/frontend
|
||||
rm -rf .next
|
||||
NEXT_PUBLIC_API_URL=http://192.168.18.93:3000 npm run build ✅
|
||||
```
|
||||
|
||||
Resultado frontend:
|
||||
|
||||
- sin warning de múltiples lockfiles;
|
||||
- sin `ECONNREFUSED`;
|
||||
- sin error de prerender `/categories` ni `/brands`;
|
||||
- TypeScript correcto;
|
||||
- `project/frontend/.next/prerender-manifest.json` presente;
|
||||
- rutas de catálogo aparecen como `ƒ` (server-rendered on demand).
|
||||
|
||||
También verificado:
|
||||
|
||||
```text
|
||||
project/apps/admin: npm run build ✅
|
||||
project/storefront: npm run build ✅
|
||||
project/frontend: npx tsc --noEmit ✅
|
||||
./scripts/verify.sh ✅
|
||||
```
|
||||
|
||||
No se modificaron contratos API ni el backend. La aplicación sigue obteniendo ventas/catálogo del sistema real en runtime.
|
||||
20
work/artifacts/F-141/leader-close.json
Normal file
20
work/artifacts/F-141/leader-close.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"feature_id": "F-141",
|
||||
"agent": "leader",
|
||||
"verdict": "APPROVED",
|
||||
"summary": "F-141 cerrado: se eliminaron los warnings de raíz de workspace del customer frontend y el build ya no depende de que el backend esté disponible durante el prerender de catálogo.",
|
||||
"checks": [
|
||||
"reviewer.json APPROVED",
|
||||
"security.json APPROVED",
|
||||
"qa.json APPROVED",
|
||||
"frontend clean build with API unavailable passed",
|
||||
"frontend prerender-manifest.json present",
|
||||
"admin build passed",
|
||||
"storefront build passed",
|
||||
"backend build/tests passed",
|
||||
"verify.sh passed"
|
||||
],
|
||||
"commit_message": "fix(F-141): isolate frontend Next build from API prerender",
|
||||
"next_step": "Start F-142: Reporting architecture and implementation task plan",
|
||||
"closed_at": "2026-08-21T20:04:00Z"
|
||||
}
|
||||
21
work/artifacts/F-141/qa.json
Normal file
21
work/artifacts/F-141/qa.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"feature_id": "F-141",
|
||||
"agent": "qa",
|
||||
"stage": "qa_gate",
|
||||
"verdict": "APPROVED",
|
||||
"reviewed_at": "2026-08-21T20:03:00Z",
|
||||
"summary": "La regresión está cubierta: el customer frontend compila con la URL del backend inaccesible, genera el manifest y clasifica las páginas dependientes del API como dinámicas. Las otras aplicaciones y backend siguen verdes.",
|
||||
"acceptance_traceability": [
|
||||
{"criterion":"No workspace-root warning in frontend build","ok":true,"evidence":"NEXT_PUBLIC_API_URL=http://192.168.18.93:3000 npm run build: no multiple-lockfile warning"},
|
||||
{"criterion":"/categories build does not fail on ECONNREFUSED","ok":true,"evidence":"Clean frontend build completed with unreachable 192.168.18.93:3000; /categories listed as ƒ"},
|
||||
{"criterion":"No missing prerender manifest after successful build","ok":true,"evidence":"test -f project/frontend/.next/prerender-manifest.json → manifest-present"},
|
||||
{"criterion":"Admin regression check","ok":true,"evidence":"rm -rf apps/admin/.next && npm run build → exit 0"},
|
||||
{"criterion":"Storefront regression check","ok":true,"evidence":"rm -rf storefront/.next && npm run build → exit 0"},
|
||||
{"criterion":"Backend regression check","ok":true,"evidence":"npm run build → exit 0; npm test → 191 passed, 56 skipped"},
|
||||
{"criterion":"TypeScript checks","ok":true,"evidence":"frontend npx tsc --noEmit → exit 0; backend build typecheck → exit 0"},
|
||||
{"criterion":"Verify harness","ok":true,"evidence":"./scripts/verify.sh → exit 0"}
|
||||
],
|
||||
"checks": [],
|
||||
"issues": [],
|
||||
"notes":"The second prerender-manifest ENOENT was validated as a cascade from the aborted frontend build; a successful clean build recreates the manifest in project/frontend/.next."
|
||||
}
|
||||
19
work/artifacts/F-141/reviewer.json
Normal file
19
work/artifacts/F-141/reviewer.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"feature_id": "F-141",
|
||||
"agent": "reviewer",
|
||||
"stage": "review_gate",
|
||||
"verdict": "APPROVED",
|
||||
"reviewed_at": "2026-08-21T20:02:00Z",
|
||||
"summary": "El build del customer frontend queda aislado de las demás aplicaciones Next y deja de depender de que el backend esté accesible durante el prerender. La solución preserva el API real en runtime y no introduce datos simulados.",
|
||||
"checks": [
|
||||
{"item":"Turbopack root is explicit","ok":true,"evidence":"frontend/next.config.ts sets turbopack.root to frontendRoot"},
|
||||
{"item":"Output tracing root is explicit","ok":true,"evidence":"frontend/next.config.ts sets outputFileTracingRoot to frontendRoot"},
|
||||
{"item":"Categories no longer calls API during build","ok":true,"evidence":"categories/page.tsx exports dynamic='force-dynamic'; clean build passes with API URL 192.168.18.93:3000 unavailable"},
|
||||
{"item":"Other uncached catalog routes covered","ok":true,"evidence":"home, brands, products, detail routes and legacy admin products route use runtime rendering"},
|
||||
{"item":"No fallback/mock source introduced","ok":true,"evidence":"Existing fetch functions and API contracts remain unchanged"},
|
||||
{"item":"Existing app builds preserved","ok":true,"evidence":"admin and storefront production builds exit 0; backend build exit 0"},
|
||||
{"item":"Manifest failure explained and resolved","ok":true,"evidence":"frontend/.next/prerender-manifest.json exists after clean build"}
|
||||
],
|
||||
"issues": [],
|
||||
"notes":"Dynamic rendering is intentional for API-dependent catalog pages. Static informational pages remain static where their existing CMS fetch is already guarded with catch()."
|
||||
}
|
||||
17
work/artifacts/F-141/security.json
Normal file
17
work/artifacts/F-141/security.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"feature_id": "F-141",
|
||||
"agent": "security",
|
||||
"stage": "security_gate",
|
||||
"verdict": "APPROVED",
|
||||
"reviewed_at": "2026-08-21T20:02:30Z",
|
||||
"summary": "El cambio solo afecta a la configuración de build y al momento de ejecución de lecturas públicas del catálogo. No amplía permisos, no expone secretos y no modifica endpoints.",
|
||||
"checks": [
|
||||
{"item":"No secrets committed","ok":true,"evidence":"No .env files or credentials modified; NEXT_PUBLIC_API_URL remains environment-provided"},
|
||||
{"item":"No API security bypass","ok":true,"evidence":"Runtime requests continue through existing fetch functions and backend routes"},
|
||||
{"item":"No sensitive data moved to build output","ok":true,"evidence":"Catalog pages are server-rendered on demand instead of serializing failed/build-time API data"},
|
||||
{"item":"Workspace isolation safe","ok":true,"evidence":"Both Turbopack and output tracing roots are constrained to project/frontend"},
|
||||
{"item":"Error behavior remains explicit","ok":true,"evidence":"Required catalog fetches still throw on HTTP/network failures at runtime; no silent empty mock is returned"}
|
||||
],
|
||||
"issues": [],
|
||||
"notes":"Dynamic server rendering may expose runtime availability errors to the normal Next error boundary if the backend is down. This is preferable to silently shipping an empty catalog and is unchanged from the existing runtime semantics."
|
||||
}
|
||||
@@ -1,54 +1,61 @@
|
||||
{
|
||||
"feature_id": "POS-002",
|
||||
"feature_id": "F-141",
|
||||
"stage": "close",
|
||||
"agent": "leader",
|
||||
"action": "Close: POS foundation, store-scoped inventory, order source/check, roles, and admin build fix approved",
|
||||
"action": "Close: Next workspace roots explicit and API-dependent catalog pages runtime rendered",
|
||||
"state": "running",
|
||||
"next_agent": "leader",
|
||||
"waiting_for": "commit_and_promote_POS-003",
|
||||
"updated_at": "2026-08-21T19:55:25Z",
|
||||
"waiting_for": "commit",
|
||||
"updated_at": "2026-08-21T20:01:53Z",
|
||||
"timeline": [
|
||||
{
|
||||
"ts": "2026-08-21T19:33:31Z",
|
||||
"ts": "2026-08-21T19:58:45Z",
|
||||
"agent": "leader",
|
||||
"stage": "intake",
|
||||
"state": "running",
|
||||
"message": "Intake: isolate frontend Next workspace and remove build-time dependency on unavailable API"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-21T19:58:53Z",
|
||||
"agent": "architect",
|
||||
"stage": "design",
|
||||
"state": "running",
|
||||
"message": "Architect: design for migrations 043-046 (POS basics) + OrderState.COMPLETED + Role pos_cashier/pos_manager + requireAnyRole helper + inventory.storeId"
|
||||
"message": "Design: frontend Next config root isolation and runtime data fetching policy"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-21T19:35:18Z",
|
||||
"ts": "2026-08-21T19:59:10Z",
|
||||
"agent": "implementer",
|
||||
"stage": "build",
|
||||
"state": "running",
|
||||
"message": "Implement: 4 migrations (043-046) + OrderState.COMPLETED + Role pos_* + requireAnyRole + inventory.storeId"
|
||||
"message": "Implement: isolate frontend Next roots and make categories API-dependent page runtime-rendered"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-21T19:54:15Z",
|
||||
"ts": "2026-08-21T20:01:08Z",
|
||||
"agent": "reviewer",
|
||||
"stage": "review_gate",
|
||||
"state": "running",
|
||||
"message": "Review: migrations 043-047, store-scoped inventory, COMPLETED state, POS roles, user_id CHECK, Turbopack root fix"
|
||||
"message": "Review: workspace roots, runtime catalog rendering, no API mocks, regression coverage"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-21T19:54:37Z",
|
||||
"ts": "2026-08-21T20:01:21Z",
|
||||
"agent": "security",
|
||||
"stage": "security_gate",
|
||||
"state": "running",
|
||||
"message": "Security: DB invariants, scoped stock, auth roles, migration safety, build config reviewed"
|
||||
"message": "Security: build isolation, runtime fetch policy, no secrets or API contract changes"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-21T19:54:54Z",
|
||||
"ts": "2026-08-21T20:01:33Z",
|
||||
"agent": "qa",
|
||||
"stage": "qa_gate",
|
||||
"state": "running",
|
||||
"message": "QA: migrations, constraints, inventory concurrency, tests, backend/admin builds and verify.sh green"
|
||||
"message": "QA: clean frontend build against unavailable API, manifest, admin/storefront/backend regression"
|
||||
},
|
||||
{
|
||||
"ts": "2026-08-21T19:55:25Z",
|
||||
"ts": "2026-08-21T20:01:53Z",
|
||||
"agent": "leader",
|
||||
"stage": "close",
|
||||
"state": "running",
|
||||
"message": "Close: POS foundation, store-scoped inventory, order source/check, roles, and admin build fix approved"
|
||||
"message": "Close: Next workspace roots explicit and API-dependent catalog pages runtime rendered"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user