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

39 lines
2.8 KiB
Markdown
Raw 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-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`).