Files
2026-08-21 21:03:28 +02:00

72 lines
4.1 KiB
JSON

{
"feature_id": "F-137",
"agent": "qa",
"stage": "qa_gate",
"verdict": "APPROVED",
"reviewed_at": "2026-08-21T19:04:00Z",
"summary": "Acceptance criteria trazados contra evidencia de diff + comandos. Todos los AC pasan a nivel código. AC4 (flujo create) y AC5 (failure surface) requieren smoke manual en navegador con monolith reiniciado.",
"acceptance_traceability": [
{
"criterion": "AC-1: 'Guardar precio' button is gone from the UI",
"evidence": "grep -c 'Guardar precio' project/apps/admin/src/features/products/components/sections/PriceStockSection.tsx → 0",
"ok": true
},
{
"criterion": "AC-2: Stock, EAN, peso, compra mínima inputs do NOT auto-save on blur or Enter",
"evidence": "grep -nE 'onBlur=\\{save' PriceStockSection.tsx → 0 matches; grep onKeyDown saveStock/saveEan/saveProductMeta → 0 matches",
"ok": true
},
{
"criterion": "AC-3: Clicking 'Guardar cambios' persists price + stock + EAN + meta in a single logical action",
"evidence": "ProductEditor.tsx:188 llama priceStockRef.current?.saveAll() tras productsApi.create/update. saveAll hace Promise.all([savePrice, saveStock, saveEan, saveProductMeta]).",
"ok": true
},
{
"criterion": "AC-4: Create flow persists variant price/stock/EAN/meta after 'Crear producto'",
"evidence": "saveAll invoca ensureVariant() que llama productsApi.getVariants(productId) si la sección aún no tiene variant cargado. Cubre el caso create donde el useEffect de carga aún no corrió.",
"ok": true,
"requires_manual_smoke": true
},
{
"criterion": "AC-5: Per-group failure surfacing (which group(s) failed)",
"evidence": "ProductEditor.tsx:191-202 mapea Object.entries(result).filter(not ok) a etiquetas (precio, stock, EAN, peso/compra mínima) y construye mensaje 'Revisa X'.",
"ok": true,
"requires_manual_smoke": true
},
{
"criterion": "AC-6: Build green, lint green, typecheck green",
"evidence": "npx tsc --noEmit → exit 0; npx eslint (ambos archivos) → 0 errors / 7 warnings pre-existentes; npm run build → 'Compiled successfully' con BUILD_ID regenerado",
"ok": true
},
{
"criterion": "AC-7: Existing dirty indicator still works for product payload",
"evidence": "getSnap() en ProductEditor.tsx no se tocó; sigue cubriendo name/slug/desc/brandId/categoryIds/channels/featured/attributes/state/seoTitle/seoDesc/expirationDate. snapRef.current = getSnap() se setea después del saveAll con éxito.",
"ok": true
}
],
"checks": [
{
"item": "Diff size y scope razonables",
"ok": true,
"evidence": "git diff --stat: ProductEditor.tsx +34 lines, PriceStockSection.tsx +110/-86 lines. Sin churn excesivo."
},
{
"item": "Patrón forwardRef + useImperativeHandle consistente con el resto del codebase",
"ok": true,
"evidence": "LexicalEditor (mismo dir features/cms) ya usa forwardRef. Patrón familiar."
},
{
"item": "Sin console.log ni debugger statements",
"ok": true,
"evidence": "grep -E 'console\\.(log|debug)|debugger' en archivos modificados → 0"
},
{
"item": "Mensajes de error visibles al usuario sin filtrar stack traces",
"ok": true,
"evidence": "Todos los catch hacen `error instanceof Error ? error.message : 'Error genérico'` antes de pasarlo al state. Sin stack traces en UI."
}
],
"issues": [],
"notes": "Smoke test manual requerido:\n1. cd project && ./scripts/monolith.sh prod restart\n2. Login admin → /products/new → rellenar nombre, PVP, stock, EAN, peso, compra mínima → Crear producto → verificar que tras recarga los valores persisten.\n3. /products/<id> → modificar PVP → Guardar cambios → verificar que se persiste (sin parpadeo, sin doble save).\n4. Probar escenario de fallo: introducir un EAN que ya exista en otro producto → Guardar cambios → verificar mensaje 'Revisa EAN'.\n\nSin e2e automatizado disponible para esta vista (sin playwright instalado en este repo). El comando smoke mínimo es el restart del monolito + recarga manual."
}