# 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:

```ts
// 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.

```ts
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.