5.4 KiB
5.4 KiB
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 nuevoPriceStockHandleexpone un único métodosaveAll(): Promise<{price, stock, ean, meta}>. - Eliminados los
useStatesavingPrice,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/saveProductMetaahora devuelvenPromise<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 tienevarianten su state local (caso típico del flujo de creación, dondeproductIdse setea pero eluseEffectde carga aún no corrió), hace unproductsApi.getVariants(productId)y guarda el primer item. Idempotente. useImperativeHandlecon 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 conPromise.ally 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 392–399 del original).
- Eliminados
onBlur={saveX}yonKeyDown={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-600para✓). - Sin cambios en la lógica de cálculo IVA/PVP/Neto (las funciones
onGrossChange,onNetChange,onVatChangesiguen iguales). - Sin cambios en la carga inicial de datos (el
useEffectque llamaproductsApi.get/getVariants/pricingApi.getVariantPrice/inventoryApi.getAvailabilitysigue 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
saveAllreporta fallos parciales (algún grupook: false), se calcula la lista de grupos fallidos y se muestra un mensaje tipoProducto 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
saveAlllanza una excepción (caso muy improbable, sería bug), se seteaerror. - Refactor menor: el
setSuccessya 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 delsaveAll). El mensaje final siempre refleja el estado combinado.
- Después del bloque de generación SEO, llama
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.tsxproject/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,
saveAlldebe ejecutarse después de queproductIdesté seteado (porquePriceStockSectionnecesita un productId para resolver la variante). ComohandleSavehacesetProductId(saved.id)justo después deproductsApi.create, ysaveAllse invoca en el siguiente tick de microtask, el ref está disponible. El helperensureVariantcubre el caso race donde eluseEffectde 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.