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

2.8 KiB

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.