Files
mercadodevida/work/artifacts/F-125/architect.md
2026-08-21 17:58:15 +02:00

4.0 KiB

F-125 — Permitir transiciones hacia atrás en pedidos + reenviar email SHIPPED↔PROCESSING

Diagnóstico

El operador reporta que al equivocarse marcando un pedido (p. ej., marcar PROCESSING cuando aún no está listo, o SHIPPED antes de tiempo) no puede revertir. El state machine actual solo permite avanzar o cancelar:

// src/modules/orders/domain/order.ts
export const ALLOWED_TRANSITIONS: Readonly<Record<OrderState, ReadonlyArray<OrderState>>> = {
  PENDING: ['AWAITING_PAYMENT', 'CANCELLED'],
  AWAITING_PAYMENT: ['PAID', 'CANCELLED'],
  PAID: ['PROCESSING', 'CANCELLED', 'REFUNDED'],
  PROCESSING: ['SHIPPED', 'CANCELLED', 'REFUNDED'],
  SHIPPED: ['DELIVERED', 'PARTIALLY_REFUNDED'],
  DELIVERED: ['PARTIALLY_REFUNDED'],
  CANCELLED: [],
  REFUNDED: [],
  PARTIALLY_REFUNDED: [],
};

El state machine está duplicado en el frontend (apps/admin/src/app/(dashboard)/orders/[id]/page.tsx:32) — ambos deben actualizarse para mantener consistencia.

El reenvío de email ya ocurre en cada transición admin (orders.routes.ts:271), así que añadir transiciones nuevas implica que el email se envía automáticamente con el estado y courier/tracking actuales del pedido.

Diseño

Transiciones hacia atrás (un paso)

  • PROCESSING → PAID (revertir procesado)
  • SHIPPED → PROCESSING (revertir envío)
  • DELIVERED → SHIPPED (revertir entrega)

Estados terminales (CANCELLED, REFUNDED, PARTIALLY_REFUNDED) se mantienen terminales — un reembolso no se puede deshacer.

export const ALLOWED_TRANSITIONS = {
  PENDING: ['AWAITING_PAYMENT', 'CANCELLED'],
  AWAITING_PAYMENT: ['PAID', 'CANCELLED'],
  PAID: ['PROCESSING', 'CANCELLED', 'REFUNDED'],
  PROCESSING: ['PAID', 'SHIPPED', 'CANCELLED', 'REFUNDED'],     // + PAID
  SHIPPED: ['PROCESSING', 'DELIVERED', 'PARTIALLY_REFUNDED'],   // + PROCESSING
  DELIVERED: ['SHIPPED', 'PARTIALLY_REFUNDED'],                 // + SHIPPED
  CANCELLED: [],
  REFUNDED: [],
  PARTIALLY_REFUNDED: [],
};

Frontend

  • Actualizar la copia local de ALLOWED_TRANSITIONS en apps/admin/src/app/(dashboard)/orders/[id]/page.tsx:32 con las mismas nuevas transiciones.
  • Añadir ACTION_LABELS para los nuevos botones:
    • PAID → PROCESSING: ya existía como "Procesar pedido"
    • PROCESSING → PAID: "Revertir a Pagado" (nuevo)
    • SHIPPED → PROCESSING: "Revertir a En preparación" (nuevo)
    • DELIVERED → SHIPPED: "Revertir a Enviado" (nuevo)

Email

  • El handler admin /orders/:id/transitions/admin ya envía email en cada transición (orders.routes.ts:271). No requiere cambios.
  • Cuando SHIPPED → PROCESSING: el email se reenvía con state='PROCESSING' pero conserva courier y trackingNumber actuales del pedido en el cuerpo (verificado en buildOrderStatusEmail — incluye las líneas solo si están presentes, independientemente del estado).
  • El cliente recibe el email con "Tu pedido #ABC ahora está: En preparación" más el bloque de courier/tracking si aún están guardados.

Tests

Actualizar src/modules/orders/tests/order-state-machine.test.ts:

  • Eliminar o invertir el test "rejects SHIPPED back to PENDING" (sigue válido: SHIPPED → PENDING sigue prohibido, pero SHIPPED → PROCESSING ahora permitido).
  • Añadir test explícito: isTransitionAllowed('SHIPPED', 'PROCESSING') === true, etc.

Riesgos

  • Bajo. El cambio es ampliar el grafo de transiciones. Los terminales siguen terminales.
  • Riesgo operativo: un operador puede hacer ping-pong entre PROCESSING y SHIPPED. Cada cambio envía email al cliente → spam si abusa. Mitigación: no se cambia el límite de frecuencia, es responsabilidad del operador.

Plan

  1. Editar src/modules/orders/domain/order.ts — añadir transiciones hacia atrás.
  2. Editar src/modules/orders/tests/order-state-machine.test.ts — actualizar tests.
  3. Editar apps/admin/src/app/(dashboard)/orders/[id]/page.tsx — añadir transiciones + nuevos labels.
  4. Backend: npm test (vitest) — verificar 100% tests OK.
  5. Admin: npx tsc --noEmit && npm run build.
  6. Cerrar gates.