# F-144 — Diseño técnico

## Enfoque
Una única migrations `048_reporting_store_shipping_snapshots.js` (node-pg-migrate,
estilo `043_pos_basics`/`047_*`) idempotente y reversible. **No hay migración de
datos costosa**: `store_id` usa column `DEFAULT` del default store (sembrado por
043), por lo que filas existentes e inserts sin store_id heredan el default sin
table rewrite ni toque en el app-layer.

## Columnas nuevas

| Tabla | Columna | Tipo | Nullable | Default | Restricción |
|---|---|---|---|---|---|
| orders_orders | store_id | uuid | NOT NULL | `'00000000-0000-0000-0000-000000000001'::uuid` | FK → pos_stores(id) |
| orders_orders | shipping_cents | integer | NOT NULL | 0 | CHECK (>=0) |
| orders_items | cost_at_sale_cents | bigint | YES (snapshot) | — | — |
| orders_items | vat_rate | text | YES (snapshot) | — | — |

Índice: `orders_orders_store_id_created_at_idx ON orders_orders(store_id, created_at)`
(por el patrón de reporting §7: consultas por tienda + rango de fechas).

## Idempotencia + reversibilidad
- Toda la DDL: `ADD COLUMN IF NOT EXISTS` / `DO $$ IF NOT EXISTS` sobre
  `pg_constraint`. Re-ejecutar es no-op.
- `down()`: `DROP INDEX`, `DROP CONSTRAINT`, `DROP COLUMN` por cada nueva
  columna (orden inverso de dependencias).

## Por qué DEFAULT (no NOT NULL sin default + UPDATE)
- `store_id` es una columna nueva: no hay histórico que "reescribir". Un
  `DEFAULT` constante backfilla silenciosamente y evita un `UPDATE` table-scan
  sobre tabla potencialmente grande — alineado con §11 "no añadir índices
  duplicados indiscriminadamente" y con no-rewrite-history.
- El app **inserta pedidos** vía `PgOrderRepository` (raw SQL `INSERT INTO
  orders_orders (...)`). Con column `DEFAULT`, ese INSERT sigue funcionando
  (la column no aparece en la lista) → cero regresión en checkout/orders.itest.
  La resolución explícita de tienda (writes app-layered) se deja a F-146.

## Test
- itest `reporting-snapshots.itest.ts` (mirror `inventory.itest.ts`):
  `recreateDatabase(url)` + `runMigrations(url,'up')` (aplica 048 contra
  `mercadodevida_test`) + `createPool`, con `describe.skipIf(!hasDb)`.
- Asocia columnas/nullabilidad/default/FK/índice vía `information_schema` /
  `pg_constraint` / `pg_indexes`, e inserta `INSERT INTO orders_orders DEFAULT
  VALUES RETURNING store_id, shipping_cents` → `store_id = DEFAULT_STORE_ID`,
  `shipping_cents = 0`.
- Harness: `TEST_DATABASE_URL=postgres://mdv:mdv_dev_only@localhost:5432/mercadodevida_test`
  (verificado: `orders.itest.ts` recrea+migra+tests en ~450ms con el
  `mdv` superuser).

## Verificación esperada
- `npx tsc --noEmit` → 0 errores.
- `TEST_DATABASE_URL=... npx vitest run src/app/tests/reporting-snapshots.itest.ts` → 3/3.
- `TEST_DATABASE_URL=... npx vitest run` → 1 archivo itest nuevo + regresión
  (orders/catalog/checkout/sales itests) verde, 0 fallos.
- `node scripts/check-module-boundaries.mjs src` → 0 violaciones nuevas
  (F-144 añade 1 migration .js + 1 .ts en tests; sin imports inter-módulo).
