2.8 KiB
2.8 KiB
F-102 — Peso unitario, compra mínima y límites de envío por peso (redefinida por el operador)
Redefinición de intake (work/current.md, 2026-08-21): el "pack" es en realidad un selector de compra mínima por producto (el frontend bloquea la compra por debajo). El peso del pedido es cantidad × peso unitario. Además, nuevos límites por método de envío: max weight y límite de envío gratuito por rango de peso.
Backend
- Migración 038:
catalog_products.unit_weight_kg numeric(8,3) NOT NULL DEFAULT 1,catalog_products.min_purchase_qty integer NOT NULL DEFAULT 1,shipping_methods.max_weight_kgyshipping_methods.free_shipping_max_weight_kg(nullable). - Dominio
Product: camposunitWeightKgyminPurchaseQtycon defaults en repositorio (Number(row.unit_weight_kg ?? 1)). catalog.routes.ts: validación zod en create/patch (unitWeightKg0..1000,minPurchaseQtyentero 1..999) y expuestos enserializeProduct.- Dominio
ShippingMethod:maxWeightKgyfreeShippingMaxWeightKg(null = sin límite). ShippingService.calculate(cartTotalCents, address, cartWeightKg = 0):- excluye métodos cuyo
maxWeightKg< peso del carrito; - el envío gratis por umbral solo aplica si el peso está dentro del rango gratuito del método.
- excluye métodos cuyo
- Rutas shipping: create/patch/list exponen ambos límites;
POST /shipping/calculateaceptacartWeightKgopcional. - Checkout: nuevo dep
getCartWeightKgcalcula peso del carrito (cantidad × unit_weight_kg con fallback 1) y lo pasa a shipping; fallo de cálculo degrada a 0 (no bloquea checkout).
Admin
- Editor de producto (Prices & Stock): campos "Peso unitario (kg)" y "Compra mínima (uds.)" con guardado por blur/Enter, validación local y mensaje ✓/Error.
- Gestión de envíos: columna "Peso" con
Máx X kgygratis hasta Y kg; formulario de método con ambos inputs (acepta coma decimal). api-client.ts: firmas deshippingApi.createMethod/updateMethodampliadas (evita la regresión F-085/F-088).
Frontend (storefront customer)
- Ficha de producto:
AddToCartButtonrecibeminPurchaseQty; añade la cantidad mínima y muestra "Compra mínima: N unidades." - Carrito:
changeQuantitynunca baja deminPurchaseQty; botón − deshabilitado en el mínimo con tooltip explicativo;addItemconserva el máximo de los mínimos al fusionar líneas. types/api.ts:Product.unitWeightKgyminPurchaseQty.
Tests
shipping-service.test.ts: método excluido cuando el peso supera su max; envío gratis denegado cuando el peso supera el rango gratuito.- Fixtures de catalog actualizados con los nuevos campos obligatorios.
Evidencia
npm run typecheck(backend) OK;npx tsc --noEmiten apps/admin y frontend OK.npm test: 42 archivos, 135 tests passed | 0 failed.- Migración 038 aplicada (
db:status).