3.3 KiB
3.3 KiB
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): persistecourierconCOALESCE($4, courier)y lo mapea entoOrder. - Servicio (
order-service.ts):transitionAdminreenvía courier aupdateState. - Mailer (
order-status-mailer.ts):sendOrderStatusEmailahora aceptacourier.- Extraído
buildOrderStatusEmail(input)puro y reutilizable; tanto el email de texto como el HTML incluyen la líneaTransportista: Xjunto aNúmero de seguimiento: Ycuando proceda.
- Rutas admin (
orders.routes.ts):- Esquema
/orders/:id/transitions/adminahora aceptacourier.state === 'SHIPPED'exige tracking y courier (422COURIER_REQUIRED). - El courier y el tracking se pasan al mailer; la entrada del historial incluye
Transportistacuando aplica. - La ruta
/orders/:id/shippingahora aceptacourieropcional para corregirlo tras enviar. serializeOrderexponecourier(defaultnull).
- Esquema
- Ajustes (
store-settings/api/settings.routes.ts):couriers: string[](zod: array de 1..60, max 30). Guardado enstore_settings.shipping_courierscomo JSON.DEFAULT_COURIERS = ['Correos','SEUR','MRW','GLS','DHL','UPS'];parseCouriersvalida JSON, filtra no-strings, recorta espacios, limita a 30 y cae al default si falla.
- Bug fijado:
apps/adminapuntaba 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/admincon 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
transitionAdminy aupdateShipping.
- Carga couriers desde
- 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 entransitionAdmin.
Evidencia
npm run typecheck(backend) OK.npm test: 145 passed | 0 failed (de 135 previos; +10 nuevos).apps/admin:npx tsc --noEmitOK.- ESLint sobre
src/modules/orders,src/modules/store-settingsy la migración 039: OK. - Migración 039 aplicada (
db:status).