# F-193 — Implementer Evidence ## Feature Show product expiry and shipping weight on storefront + authoritative variant weight in shipping. ## Changes ### 1. `project/migrations/057_product_variant_weight_and_expiry.js` (new) Adds `weight_grams` column to `catalog_product_variants`: - `integer`, nullable, no default - CHECK: `weight_grams IS NULL OR weight_grams > 0` - After `ean` column ### 2. `project/src/modules/catalog/domain/variant.ts` Added `weightGrams` to `ProductVariant`, `NewProductVariant`, and `ProductVariantPatch`. ### 3. `project/src/modules/catalog/infrastructure/pg-variant-repository.ts` - Added `weight_grams` to `VariantRow` interface - Added `['weightGrams', 'weight_grams']` to `UPDATABLE` array - Updated `create()` to INSERT `weight_grams` - Updated `toVariant()` to map `weight_grams → weightGrams` ### 4. `project/src/modules/catalog/api/catalog.routes.ts` - Added `weightGrams` to `newVariantSchema` (zod, optional, integer >= 1) - Added `weightGrams` to `serializeVariant()` output ### 5. `project/src/modules/checkout/application/checkout-service.ts` - Extended `getCartWeightKg` parameter type to include `variantId` - Updated call site to pass full `cart.items` (no mapping needed) ### 6. `project/src/modules/checkout/api/checkout.routes.ts` - Rewrote `getCartWeightKg` to: - Fetch variant-level `weight_grams` from `catalog_product_variants` - Fetch product-level `unit_weight_kg` as fallback - Prefer variant weight (g → kg) when available - Fall back to product weight (kg) when variant weight is null ### 7. `project/frontend/src/lib/api.ts` - Added `weightGrams: number | null` to `ProductVariant` interface ### 8. `project/frontend/src/types/api.ts` - Added `expirationDate?: string` to `Product` interface ### 9. `project/frontend/src/app/products/[slug]/page.tsx` Added expiry date and weight display in the price box: - `Caduca: {date}` — when `product.expirationDate` is set - `Peso: {X.Y kg}` or `Peso: {X g}` — from `primaryVariant.weightGrams` ### Existing (no changes needed) - `catalog_products.expiration_date` — already exists from migration 038 - `catalog_products.unit_weight_kg` — already exists as fallback - `ShippingService` — already takes `cartWeightKg` and filters by `maxWeightKg` - `serializeProduct()` — already serializes `expirationDate` ## Verification | Check | Result | |-------|--------| | `npx tsc --noEmit` (backend) | 0 errors | | `npx tsc --noEmit` (frontend) | 0 errors | | `npm test` (backend) | 269 passed, 96 skipped | | `./scripts/verify.sh` | OK |