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

5.4 KiB
Raw Blame History

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.