Files
mercadodevida/work/artifacts/F-113/implementer.md
2026-08-21 12:27:13 +02:00

45 lines
3.3 KiB
Markdown

# F-113 — Email al cliente en procesando/enviado con tracking y courier editable
## Backend
- **Migración 039** (`039_order_courier.js`): `orders_orders.courier varchar(120) NULL`. Aplicada (`db:status`).
- **Dominio/ports**: `Order.courier?: string | null`; `updateState(id, state, trackingNumber?, courier?)`; `transitionAdmin(id, next, trackingNumber?, courier?)`.
- **Repositorio** (`pg-order-repository.ts`): persiste `courier` con `COALESCE($4, courier)` y lo mapea en `toOrder`.
- **Servicio** (`order-service.ts`): `transitionAdmin` reenvía courier a `updateState`.
- **Mailer** (`order-status-mailer.ts`):
- `sendOrderStatusEmail` ahora acepta `courier`.
- Extraído `buildOrderStatusEmail(input)` puro y reutilizable; tanto el email de texto como el HTML incluyen la línea `Transportista: X` junto a `Número de seguimiento: Y` cuando proceda.
- **Rutas admin** (`orders.routes.ts`):
- Esquema `/orders/:id/transitions/admin` ahora acepta `courier`. `state === 'SHIPPED'` exige tracking **y** courier (422 `COURIER_REQUIRED`).
- El courier y el tracking se pasan al mailer; la entrada del historial incluye `Transportista` cuando aplica.
- La ruta `/orders/:id/shipping` ahora acepta `courier` opcional para corregirlo tras enviar.
- `serializeOrder` expone `courier` (default `null`).
- **Ajustes** (`store-settings/api/settings.routes.ts`):
- `couriers: string[]` (zod: array de 1..60, max 30). Guardado en `store_settings.shipping_couriers` como JSON.
- `DEFAULT_COURIERS = ['Correos','SEUR','MRW','GLS','DHL','UPS']`; `parseCouriers` valida JSON, filtra no-strings, recorta espacios, limita a 30 y cae al default si falla.
- **Bug fijado**: `apps/admin` apuntaba a `/orders/:id/transitions` (ruta de cliente, sin email ni tracking). Ahora apunta a `/orders/:id/transitions/admin`, que es el endpoint que realmente envía el email.
## Admin UI (apps/admin)
- `lib/api-client.ts`:
- `ordersApi.transition(id, state, trackingNumber?, courier?)``POST /api/orders/{id}/transitions/admin` con courier si está.
- `ordersApi.updateShipping(id, trackingNumber, note?, courier?)`.
- `StoreSettings.couriers?: string[]`.
- `types/index.ts`: `Order.courier?: string | null`.
- **Página de pedido** (`orders/[id]/page.tsx`):
- Carga couriers desde `settingsApi.get()`.
- Modal de confirmación SHIPPED: select de transportista (con aviso si no hay configurados) + tracking; ambos obligatorios.
- Sección "Envío": select de transportista arriba del tracking.
- El courier seleccionado se envía a `transitionAdmin` y a `updateShipping`.
- **Ajustes** (`settings/page.tsx`): nueva pestaña "Transportistas" con textarea de uno por línea; al guardar, se envía el array a la API.
## Tests
- `order-status-mailer.test.ts` (4): tracking+courier en SHIPPED, ausencia en PROCESSING, omisión con courier en blanco, escape HTML en courier/tracking.
- `settings-couriers.test.ts` (5): parseCouriers con defaults, JSON válido, entradas no-string, JSON inválido, límite 30.
- `order-service.test.ts` (+1): el repositorio recibe courier y trackingNumber en `transitionAdmin`.
## Evidencia
- `npm run typecheck` (backend) OK.
- `npm test`: 145 passed | 0 failed (de 135 previos; +10 nuevos).
- `apps/admin`: `npx tsc --noEmit` OK.
- ESLint sobre `src/modules/orders`, `src/modules/store-settings` y la migración 039: OK.
- Migración 039 aplicada (`db:status`).