72 lines
4.1 KiB
JSON
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."
|
|
}
|