Files
mercadodevida/work/artifacts/F-102/implementer.md
2026-08-21 12:02:15 +02:00

2.8 KiB
Raw Permalink Blame History

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_kg y shipping_methods.free_shipping_max_weight_kg (nullable).
  • Dominio Product: campos unitWeightKg y minPurchaseQty con defaults en repositorio (Number(row.unit_weight_kg ?? 1)).
  • catalog.routes.ts: validación zod en create/patch (unitWeightKg 0..1000, minPurchaseQty entero 1..999) y expuestos en serializeProduct.
  • Dominio ShippingMethod: maxWeightKg y freeShippingMaxWeightKg (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.
  • Rutas shipping: create/patch/list exponen ambos límites; POST /shipping/calculate acepta cartWeightKg opcional.
  • Checkout: nuevo dep getCartWeightKg calcula 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 kg y gratis hasta Y kg; formulario de método con ambos inputs (acepta coma decimal).
  • api-client.ts: firmas de shippingApi.createMethod/updateMethod ampliadas (evita la regresión F-085/F-088).

Frontend (storefront customer)

  • Ficha de producto: AddToCartButton recibe minPurchaseQty; añade la cantidad mínima y muestra "Compra mínima: N unidades."
  • Carrito: changeQuantity nunca baja de minPurchaseQty; botón deshabilitado en el mínimo con tooltip explicativo; addItem conserva el máximo de los mínimos al fusionar líneas.
  • types/api.ts: Product.unitWeightKg y minPurchaseQty.

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 --noEmit en apps/admin y frontend OK.
  • npm test: 42 archivos, 135 tests passed | 0 failed.
  • Migración 038 aplicada (db:status).