Files
mercadodevida/work/artifacts/F-137/implementer.md
2026-08-21 21:03:28 +02:00

70 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# F-137 — Implementer notes: Unify price/stock/EAN/meta save into "Guardar cambios"
## Cambios
### `apps/admin/src/features/products/components/sections/PriceStockSection.tsx`
- Convertido de function declaration a `forwardRef<PriceStockHandle, { productId?: string }>`. El nuevo `PriceStockHandle` expone un único método `saveAll(): Promise<{price, stock, ean, meta}>`.
- Eliminados los `useState` `savingPrice`, `savingStock`, `savingEan`, `savingProductMeta`. Los mensajes (`priceMsg`, `stockMsg`, `eanMsg`, `metaMsg`) ahora solo se setean en caso de error (sin flashes de éxito tipo `✓ Guardado`).
- Las funciones `savePrice` / `saveStock` / `saveEan` / `saveProductMeta` ahora devuelven `Promise<boolean>` y propagan el mensaje de error en el state msg correspondiente. Reutilizan los mismos endpoints HTTP que antes — sin cambios de backend.
- Nuevo helper `ensureVariant()`: si la sección aún no tiene `variant` en su state local (caso típico del flujo de creación, donde `productId` se setea pero el `useEffect` de carga aún no corrió), hace un `productsApi.getVariants(productId)` y guarda el primer item. Idempotente.
- `useImperativeHandle` con deps array = state values (gross, net, cost, offer, vatRate, stock, ean, unitWeightGr, minPurchaseQty, taxRates). Esto garantiza que el handle siempre vea los valores más recientes del formulario.
- `saveAll()` ejecuta los 4 saves con `Promise.all` y devuelve el mapa de éxito. No lanza excepción: cada grupo se maneja con su propio try/catch interno.
- **Eliminado el botón "Guardar precio"** (líneas que iban a ser 392399 del original).
- **Eliminados `onBlur={saveX}` y `onKeyDown={Enter→saveX}`** en los inputs de stock, EAN, peso unitario y compra mínima. Los inputs ya no persisten automáticamente al perder el foco.
- **Eliminados `disabled={savingX}`** en los inputs correspondientes (ya no hay estado de guardado).
- Cambiado el render de los mensajes para que solo muestren texto rojo (sin el `text-green-600` para `✓`).
- Sin cambios en la lógica de cálculo IVA/PVP/Neto (las funciones `onGrossChange`, `onNetChange`, `onVatChange` siguen iguales).
- Sin cambios en la carga inicial de datos (el `useEffect` que llama `productsApi.get` / `getVariants` / `pricingApi.getVariantPrice` / `inventoryApi.getAvailability` sigue intacto).
### `apps/admin/src/features/products/components/ProductEditor.tsx`
- Añadido import del tipo: `import { PriceStockSection, type PriceStockHandle } from './sections/PriceStockSection';`.
- Añadido `priceStockRef = useRef<PriceStockHandle>(null)` junto al state de marcas/categorías.
- `<PriceStockSection ref={priceStockRef} productId={productId} />` en la pestaña General.
- `handleSave`:
- Después del bloque de generación SEO, llama `priceStockRef.current?.saveAll()` dentro de su propio try/catch.
- Si `saveAll` reporta fallos parciales (algún grupo `ok: false`), se calcula la lista de grupos fallidos y se muestra un mensaje tipo `Producto guardado. Revisa precio, EAN.` mapeando cada clave a su etiqueta en español.
- Si todo va bien, mensaje estándar (`¡Producto creado!` / `Cambios guardados`).
- Si `saveAll` lanza una excepción (caso muy improbable, sería bug), se setea `error`.
- Refactor menor: el `setSuccess` ya no se setea en la rama de generación de IA si el producto tenía contenido incompleto (porque ahora se sobreescribe con el resultado del `saveAll`). El mensaje final siempre refleja el estado combinado.
## UX
Antes:
- Precio → botón "Guardar precio"
- Stock, EAN, peso, compra mínima → auto-save en blur o Enter (con ✓ verde durante 3s)
Ahora:
- Todos los campos persisten al pulsar "Guardar cambios" en la cabecera del editor.
- Los inputs son "tontos": solo mantienen estado local; no hacen red al perder foco.
- Si el guardado central falla parcialmente, se muestra qué grupo falló (`precio`, `stock`, `EAN`, `peso/compra mínima`) en el banner verde de éxito (con coletilla "Revisa X").
- Si todo va bien, banner verde estándar.
## Archivos modificados
- `project/apps/admin/src/features/products/components/sections/PriceStockSection.tsx`
- `project/apps/admin/src/features/products/components/ProductEditor.tsx`
## Evidencia
```
$ cd project/apps/admin && npx tsc --noEmit
(exit 0)
$ npx eslint src/features/products/components/sections/PriceStockSection.tsx \
src/features/products/components/ProductEditor.tsx
✖ 7 problems (0 errors, 7 warnings)
- 6 warnings pre-existentes sobre `as any` en ProductEditor.tsx
- 1 warning pre-existente sobre unused eslint-disable
$ npm run build
✓ Compiled successfully
```
## Notas
- En el flujo de creación, `saveAll` debe ejecutarse después de que `productId` esté seteado (porque `PriceStockSection` necesita un productId para resolver la variante). Como `handleSave` hace `setProductId(saved.id)` justo después de `productsApi.create`, y `saveAll` se invoca en el siguiente tick de microtask, el ref está disponible. El helper `ensureVariant` cubre el caso race donde el `useEffect` de carga aún no haya disparado.
- No hay tests automatizados para esta vista. La verificación end-to-end requiere navegador: crear un producto nuevo con PVP + stock + EAN + peso, pulsar "Crear producto", recargar `/products/:id`, comprobar que los valores se mantienen.
- El build regenerado queda en `apps/admin/.next/`. El operador debe reiniciar el monolito (`./scripts/monolith.sh prod restart`) para que el bundle actualizado se sirva en `:3004`.