sinfactura-types 1.10.443 → 1.10.445

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -74,6 +74,7 @@ exports.WEBHOOK_EVENT_TYPES = [
74
74
  'invoice.generated',
75
75
  'customer.created',
76
76
  'customer.imported',
77
+ 'order.financial_status_changed',
77
78
  ];
78
79
  /** Terminal outcomes for a print job. Closed union — a job printed, or it failed. */
79
80
  exports.PRINT_JOB_SETTLED_OUTCOMES = ['printed', 'failed'];
@@ -758,12 +758,15 @@ declare global {
758
758
  * locations is therefore SHORT by everything in transit, and that is correct
759
759
  * rather than a bug.
760
760
  *
761
- * ⚠️ **Do not repair that shortfall by reaching for `Product.stock`.** The two
762
- * are separate books with separate writers and they diverge permanently by
763
- * design — see `Warehouse` — so subtracting one from the other produces a
764
- * number of which in-transit units are only a small and unidentifiable part. A
765
- * consumer that captions that difference "in transit", or "shrinkage", is
766
- * labelling a gap it has not measured.
761
+ * ⚠️ **On a store not cut over, do not repair that shortfall by reaching for
762
+ * `Product.stock`.** With `Store.stockAuthority` absent or `'store'` the two
763
+ * are separate books with separate writers and they diverge by design — see
764
+ * `Warehouse` — so subtracting one from the other produces a number of which
765
+ * in-transit units are only a small and unidentifiable part. A consumer that
766
+ * captions that difference "in transit", or "shrinkage", is labelling a gap it
767
+ * has not measured. On a store cut over (`stockAuthority: 'depositos'`),
768
+ * `Product.stock − Σ onHand` IS the units on open transfers; any remainder is
769
+ * drift the nightly reconciliation already reports as `STOCK_DEPOSITO_DRIFT`.
767
770
  *
768
771
  * ⚠️ **`received` does NOT mean the received quantities equal the sent
769
772
  * quantities.** A short arrival the operator accepts as final closes the
@@ -77,15 +77,15 @@ declare global {
77
77
  * `Record<>`, `Exclude<>` against a tuple, a switch, an array literal whose
78
78
  * length is asserted.
79
79
  *
80
- * ⚠️ **And a new member can be a WIRE change, not merely a wider type.** A
81
- * consumer that validates a seed payload against the key count — the api does,
82
- * with an exact `.length(FEATURE_KEYS.length)` — starts rejecting every
83
- * payload written against the previous length the moment this union grows.
84
- * That is deliberate there (loud at the seeding call, rather than a tier
85
- * quietly missing a feature), but it means the caller must be updated in the
86
- * same change. Adding a key is never only additive downstream.
80
+ * ⚠️ **And a new member is never only additive downstream.** The api holds no
81
+ * seed payload to grow any more — grants are code (`FEATURE_DEFINITIONS` and
82
+ * the total `PLAN_FEATURES` matrix), so a missing grant fails its `typecheck`
83
+ * — but the shared authorizer resolves entitlements from that matrix, so the
84
+ * key reaches a tenant only once `PlatformStack` deploys. A consumer that
85
+ * exhausts this union (a `switch`, a `Record<FeatureKey, …>`) still has to
86
+ * handle the new member in the same change.
87
87
  */
88
- type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxWarehouses' | 'priceListsMax' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'whatsappCommerce' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
88
+ type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxWarehouses' | 'priceListsMax' | 'webhooks' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'whatsappCommerce' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
89
89
  /** Resolved entitlements for a specific tenant (matrix + overrides applied). */
90
90
  type ResolvedEntitlements = Record<FeatureKey, Entitlement>;
91
91
  /**
package/dist/webhook.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * immediately subscribable — and a store that subscribes to an event with no
11
11
  * emit site gets silence it cannot distinguish from "nothing happened".
12
12
  */
13
- export declare const WEBHOOK_EVENT_TYPES: readonly ["print.queued", "print.received", "print.printed", "print.failed", "print.agent.connected", "print.agent.disconnected", "print.printer.online", "print.printer.offline", "print.job.settled", "payment.received", "product.stock_low", "product.stock_out", "order.created", "order.status_changed", "invoice.generated", "customer.created", "customer.imported"];
13
+ export declare const WEBHOOK_EVENT_TYPES: readonly ["print.queued", "print.received", "print.printed", "print.failed", "print.agent.connected", "print.agent.disconnected", "print.printer.online", "print.printer.offline", "print.job.settled", "payment.received", "product.stock_low", "product.stock_out", "order.created", "order.status_changed", "invoice.generated", "customer.created", "customer.imported", "order.financial_status_changed"];
14
14
  export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
15
15
  /** Terminal outcomes for a print job. Closed union — a job printed, or it failed. */
16
16
  export declare const PRINT_JOB_SETTLED_OUTCOMES: readonly ["printed", "failed"];
@@ -347,36 +347,38 @@ export interface OrderCreatedPayload {
347
347
  * those are opposite operational signals. The axis is what disambiguates them,
348
348
  * so it is REQUIRED and is never inferred from the status spelling.
349
349
  *
350
- * ── WHY `axis` HAS EXACTLY ONE MEMBER TODAY ──────────────────────────────
350
+ * ── WHY `axis` HAS EXACTLY ONE MEMBER ────────────────────────────────────
351
351
  *
352
- * Only the FULFILMENT axis is shipped. `'financial'` is deliberately NOT
353
- * declared here, and this is the line most likely to be "helpfully" widened by
354
- * someone who reads the three-axis description above and assumes the union was
355
- * simply left unfinished.
352
+ * This event carries the FULFILMENT axis only. `'financial'` is deliberately
353
+ * NOT declared here, and this is the line most likely to be "helpfully" widened
354
+ * by someone who reads the three-axis description above and assumes the union
355
+ * was simply left unfinished.
356
356
  *
357
357
  * Rule 3 above is the reason: a declared value means "this can occur on a real
358
- * event", never "we intend to emit this later". Declaring `'financial'` before
359
- * a financial seam emits would publish a value no subscriber can ever observe
360
- * — the union equivalent of publishing an event nothing emits, and it would
361
- * read to an integrator as a branch worth writing and testing against.
362
- *
363
- * ⚠️ Adding `'financial'` LATER is a BREAKING change under rule 1 (it widens a
364
- * union an integrator may exhaustively switch on) and it is breaking whether or
365
- * not it was pre-announced here — pre-declaring buys nothing and costs honesty.
366
- * The financial axis ships behind rule 2 instead: either an announced widen, or
367
- * a new `order.status_changed.v2`. That decision belongs to whoever builds the
368
- * financial seam, with the settlement and payment-linking writers in front of
369
- * them; it is not owed in advance.
358
+ * event", never "we intend to emit this later". A financial move never rides
359
+ * this event — it has its own — so `'financial'` here would publish a value no
360
+ * subscriber can ever observe, and it would read to an integrator as a branch
361
+ * worth writing and testing against.
362
+ *
363
+ * ⚠️ Adding `'financial'` is a BREAKING change under rule 1 (it widens a union
364
+ * an integrator may exhaustively switch on), and announcing it in advance would
365
+ * not make it any less breaking — rule 2 allows only a new event string. So the
366
+ * financial axis ships as its OWN event, {@link OrderFinancialStatusChangedPayload}
367
+ * (`order.financial_status_changed`), and this payload's `axis` stays
368
+ * `'fulfilment'` for good. That also spares fulfilment subscribers the
369
+ * migration an `order.status_changed.v2` would force on them.
370
370
  *
371
371
  * ── WHICH MOVES REACH THE WIRE ───────────────────────────────────────────
372
372
  *
373
373
  * REAL moves only, from the two single-order fulfilment seams: the operator
374
- * delivery/un-delivery toggle, and the MercadoLibre shipment sync. Two writers
375
- * touch `fulfilmentStatus` and are deliberately SILENT, because neither is a
374
+ * delivery/un-delivery toggle, and the MercadoLibre shipment sync. Three writers
375
+ * touch `fulfilmentStatus` and are deliberately SILENT, because none is a
376
376
  * transition:
377
377
  *
378
378
  * - the soft-delete hide path, which rewrites the field wholesale and
379
379
  * deliberately writes no `statusHistory` entry either;
380
+ * - the storefront customer's self-cancel, which retires the order the same
381
+ * way the soft-delete does and recomputes the field to match;
380
382
  * - the order-edit heal branch, which writes a status where none was stored.
381
383
  * A back-catalogue row acquiring the field it always implied is a data
382
384
  * repair, and announcing it as a status change would tell an integrator an
@@ -427,6 +429,69 @@ export interface OrderStatusChangedPayload {
427
429
  */
428
430
  occurredAt: number;
429
431
  }
432
+ /**
433
+ * Payload for `order.financial_status_changed` — an existing order's FINANCIAL
434
+ * axis (`financialStatus`: `pending | partial | paid`) moved, and the move is
435
+ * durable.
436
+ *
437
+ * ── WHY A SEPARATE EVENT AND NOT AN `axis` ON `order.status_changed` ─────
438
+ *
439
+ * Widening {@link OrderStatusChangedPayload}'s `axis` is a breaking change
440
+ * under rule 1, and rule 2 answers a breaking change with a new event string.
441
+ * The event NAME carries the axis here, so there is no `axis` field: a
442
+ * subscriber that sees this event knows `'pending'` means UNPAID.
443
+ *
444
+ * ── WHICH MOVES REACH THE WIRE ───────────────────────────────────────────
445
+ *
446
+ * REAL moves only (`from !== to`), announced after the write commits, from
447
+ * every writer that moves the axis on an existing order: the counter tender,
448
+ * both provider settlement webhooks (Stripe, MercadoPago), the payment link
449
+ * (operator, MercadoLibre collection and MercadoPago movements auto-link), the
450
+ * payment unlink and the MercadoPago refund. A redelivered settlement, a top-up
451
+ * that leaves a `partial` order `partial`, and a link that is refused all
452
+ * announce nothing.
453
+ *
454
+ * ⚠️ **Order creation does not emit.** An order created with a tender carries
455
+ * its first `financialStatus`, which is not a transition; `order.created`
456
+ * already announces the row.
457
+ *
458
+ * ⚠️ **Downgrades are events too.** An unlink or a refund can move `paid` back
459
+ * to `partial` or `pending`; a subscriber that only handles `to: 'paid'` misses
460
+ * money given back.
461
+ *
462
+ * ⚠️ **Best-effort, like every business event.** A process can stop between
463
+ * the commit and the emit, and a failed subscription read or enqueue is warned
464
+ * and swallowed — it never fails the money write it follows. A redelivery after
465
+ * the commit is absorbed by the writer's idempotency claim or finds the axis
466
+ * already moved (a self-edge), so a move lost that way is never re-announced.
467
+ *
468
+ * ⚠️ **No `total` and no `currency`**, for the denomination reason set out on
469
+ * {@link OrderCreatedPayload}. The money itself lives on `payment.received`.
470
+ *
471
+ * ⚠️ **Enumerated, never spread from the row.** `getOrderById` does not delete
472
+ * `search`, which embeds the customer's `fullName` (Ley 25.326).
473
+ */
474
+ export interface OrderFinancialStatusChangedPayload {
475
+ orderId: string;
476
+ /**
477
+ * The state moved FROM.
478
+ *
479
+ * ⚠️ **Genuinely optional.** An order written before `financialStatus`
480
+ * existed carries no such field and nothing backfills it, so its first real
481
+ * move has no prior state to name. The key is ABSENT then, never present
482
+ * holding `undefined`. Absent is never the same as equal to {@link to}: a
483
+ * self-edge does not emit at all.
484
+ */
485
+ from?: OrderFinancialStatus;
486
+ /** The state moved TO. Always the value the committed row now carries. */
487
+ to: OrderFinancialStatus;
488
+ /**
489
+ * Server-stamped Unix milliseconds, taken at the post-commit emit — same
490
+ * convention and reason as {@link OrderCreatedPayload.occurredAt}. NOT the
491
+ * `statusHistory` entry's own `timestamp`.
492
+ */
493
+ occurredAt: number;
494
+ }
430
495
  /**
431
496
  * Payload for `invoice.generated` — ARCA authorised a fiscal voucher and its
432
497
  * `INVOICE#{storeId}` row is durable.
package/dist/webhook.js CHANGED
@@ -71,6 +71,7 @@ export const WEBHOOK_EVENT_TYPES = [
71
71
  'invoice.generated',
72
72
  'customer.created',
73
73
  'customer.imported',
74
+ 'order.financial_status_changed',
74
75
  ];
75
76
  /** Terminal outcomes for a print job. Closed union — a job printed, or it failed. */
76
77
  export const PRINT_JOB_SETTLED_OUTCOMES = ['printed', 'failed'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinfactura-types",
3
- "version": "1.10.443",
3
+ "version": "1.10.445",
4
4
  "description": "Shared wire and entity contracts for the SINFACTURA platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",