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

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): 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).