124 lines
5.0 KiB
Markdown
124 lines
5.0 KiB
Markdown
# F-069 — Implementer evidence
|
|
|
|
## Scope delivered
|
|
|
|
Shipping method names ("Estandar", "Express 24h") and prices lived in
|
|
`shipping_methods`, but the descriptive text the storefront checkout
|
|
shows under each method was hardcoded in `CheckoutClient.tsx`. Admin
|
|
edits to the name and price did update the catalog, but the marketing
|
|
description ("Entrega 3-5 días laborables") could not be changed
|
|
without a code deploy. This fix makes the description a first-class
|
|
field on `shipping_methods` and edits it from the admin.
|
|
|
|
## Changes
|
|
|
|
### Migration
|
|
|
|
`project/migrations/029_shipping_method_description.js` (new)
|
|
|
|
```js
|
|
ALTER TABLE shipping_methods
|
|
ADD COLUMN IF NOT EXISTS description text;
|
|
```
|
|
|
|
Applied via `node-pg-migrate up`.
|
|
|
|
### Backend
|
|
|
|
`project/src/modules/shipping/api/shipping.routes.ts`
|
|
|
|
- `methodBodySchema` accepts an optional `description` string (max 500 chars).
|
|
- `POST /shipping/methods` writes the new column.
|
|
- `GET /admin/shipping/methods` returns `description` per row.
|
|
- `PATCH /admin/shipping/methods/:id` accepts a `description` patch.
|
|
- New public `GET /shipping/methods?country=…&postalCode=…` joins
|
|
`shipping_zones` (active, country + postal prefix match) and
|
|
`shipping_methods` (active) and returns the catalogue the storefront
|
|
checkout needs. The response includes `description` for each method.
|
|
|
|
### Admin UI
|
|
|
|
`project/apps/admin/src/lib/api-client.ts`
|
|
|
|
- `ShippingMethod` type gains `description: string | null`.
|
|
- `createMethod` / `updateMethod` payloads accept `description`.
|
|
|
|
`project/apps/admin/src/app/(dashboard)/shipping/page.tsx`
|
|
|
|
- `MethodRow` shows the truncated description under the method name.
|
|
- `MethodForm` gains a description input below the name; both create
|
|
and update paths persist it.
|
|
|
|
### Storefront proxy
|
|
|
|
`project/frontend/src/app/api/shipping/methods/route.ts` (new)
|
|
|
|
Thin GET proxy that forwards query params to the backend
|
|
`/shipping/methods`.
|
|
|
|
### Checkout
|
|
|
|
`project/frontend/src/components/checkout/CheckoutClient.tsx`
|
|
|
|
- New `ShippingMethod` interface.
|
|
- New `useEffect` that fetches `/api/shipping/methods` on mount and
|
|
defaults the selection to the first method.
|
|
- The "Método de envío" radio list now renders one card per real
|
|
method from the API: name, optional description, optional
|
|
"Envío gratis aplicado" badge when `freeShippingThresholdCents` is
|
|
met, and the formatted price. The hardcoded
|
|
`[{id:'standard',…},{id:'express',…}]` array is gone.
|
|
- The summary panel and the order payload both source the cost from
|
|
the selected method (`selectedShippingMethod?.baseCostCents ?? 0`)
|
|
instead of a hardcoded `899 / 499`.
|
|
|
|
## Acceptance traceability
|
|
|
|
| Acceptance criterion | How it is met |
|
|
| -------------------- | ------------- |
|
|
| Shipping methods table has a description column | Migration 029 added `shipping_methods.description text`. |
|
|
| Admin can edit description from `/shipping` | `MethodForm` has a description input; `shippingApi.createMethod` / `updateMethod` payloads include `description`; admin GET returns the field; PATCH writes it. |
|
|
| Storefront `/checkout` fetches methods and shows the description under each one | `CheckoutClient` calls `fetch('/api/shipping/methods')` on mount; each radio card renders `opt.description`. |
|
|
| Public endpoint returns methods with description | `GET /shipping/methods?country=ES` returns two methods, each with the description we just wrote. Verified end-to-end with curl. |
|
|
| `verify.sh` is green | Exit 0. |
|
|
|
|
## Manual verification
|
|
|
|
```
|
|
# Public catalogue after the admin edits the descriptions
|
|
$ curl 'http://192.168.18.93:3000/shipping/methods?country=ES'
|
|
{
|
|
"items": [
|
|
{ "id": "c57cf1bc-…", "name": "Estandar", "baseCostCents": 599,
|
|
"description": "Entrega 3-5 días laborables",
|
|
"freeShippingThresholdCents": 5900 },
|
|
{ "id": "1d3979fb-…", "name": "Express 24h", "baseCostCents": 999,
|
|
"description": "Entrega al día siguiente" }
|
|
]
|
|
}
|
|
|
|
# Admin list reflects the same
|
|
$ curl 'http://192.168.18.93:3004/api/admin/shipping/methods' -b /tmp/admin_cookies.txt
|
|
[ same shape, both with description populated ]
|
|
```
|
|
|
|
## Build verification
|
|
|
|
- `npm run typecheck` (backend) — exit 0
|
|
- `npm run build` (backend) — exit 0
|
|
- `npm test` (backend) — 124 passed, 56 skipped
|
|
- `npx tsc --noEmit` (frontend / admin) — exit 0
|
|
- Migration `node-pg-migrate up` — applied
|
|
- `monolith.sh prod restart backend admin frontend` → 200 on all
|
|
- `./scripts/verify.sh` — exit 0
|
|
|
|
## Files touched
|
|
|
|
```
|
|
project/migrations/029_shipping_method_description.js (new)
|
|
project/src/modules/shipping/api/shipping.routes.ts (description field + public GET)
|
|
project/apps/admin/src/lib/api-client.ts (ShippingMethod.description)
|
|
project/apps/admin/src/app/(dashboard)/shipping/page.tsx (MethodRow + MethodForm description)
|
|
project/frontend/src/app/api/shipping/methods/route.ts (new proxy)
|
|
project/frontend/src/components/checkout/CheckoutClient.tsx (fetch + render real methods)
|
|
``` |