Files
mercadodevida/spec/tech.md
2026-08-22 07:41:08 +02:00

50 lines
2.8 KiB
Markdown

# F-153 — Tech Spec
## Principles
- Associate the customer email to the order **read model** (not a per-request hack):
`OrderView.email` is resolved once by the orders repository via a `LEFT JOIN identity_users`.
- Reuse the existing SQL pattern: `orders/infrastructure/pg-order-repository.ts` `search`
already does `LEFT JOIN identity_users u ON u.id = o.user_id` — F-153 extends that to every
order read so the email is always available on the view.
- Boundaries: `identity_users` is referenced only as a **SQL table name** (pre-existing in
`search`); no TypeScript import crosses the identity/orders boundary. `identity` does not
import orders; orders references `identity_users` table name (string) at infrastructure.
- NoUncheckedIndexedAccess is ON → index access returns `T | undefined`; use `!` or `?? null`
when mapping rows.
- Backward compatible: `email` is an additive field on the serialized output; no state
transition, no new migration, no endpoint change.
## Changes
### 1. Domain — OrderView carries email
- `orders/domain/order.ts`: add `email: string | null;` to `OrderView` (`Order` itself unchanged —
email is a denormalized read-model attribute, not a core domain field).
### 2. Infrastructure — resolve email in the repo
- `orders/infrastructure/pg-order-repository.ts`:
- Add `email: string | null` to `OrderRow`.
- Add helper `toOrderView(row, items): OrderView = { ...toOrder(row), email: row.email, items }`.
- `SELECT o.*``SELECT o.*, u.email` with `LEFT JOIN identity_users u ON u.id = o.user_id`
in `findById`, `findByIdAndUserId`, `findAll`, `search`.
- Build every returned `OrderView` via `toOrderView` (so `email` is always set; `null` when the
order has no linked identity_user).
### 3. API — expose + consume email
- `orders/api/orders.routes.ts`:
- `serializeOrder`: add `email: string | null` to the param type and to the output
(`email: order.email`).
- Admin force-transition (`POST /orders/:id/transitions/admin`): replace the inline
`SELECT email FROM identity_users WHERE id = order.userId` with `const to = order.email;`
(single source of truth; the LEFT JOIN already resolved it). Keep the try/catch + warn
and the "El cliente no tiene email asociado" fallback (now only when `order.email` is null).
## Testing
- New `orders/infrastructure/pg-order-repository.test.ts`: mock `pg.Pool`, assert `findById`
returns `email` from the JOIN when the linked identity_user has one, and `null` when there is
no linked user.
- Update existing `OrderView` literals in tests (`order-service`, `payments-service`,
`checkout-service`) to include `email: null` (additive field).
- `order-status-mailer.test.ts` (existing) remains green (no change to email senders).
- tsc --noEmit clean; prettier + eslint clean; lint:boundaries no new violations;
vitest run full suite green; verify.sh green.