platform-types 1.10.734 → 1.10.736

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.
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FISCAL_CONDITIONS = void 0;
4
+ /**
5
+ * The ARCA fiscal-condition catalogue — the ONE definition. It maps each legacy
6
+ * `condFiscal` code to its display name, its ARCA `DocTipo` and the `CbteTipo`
7
+ * family that issuance resolves for it.
8
+ *
9
+ * Deliberately NOT `declare global`: it is a runtime value, so import it:
10
+ *
11
+ * ```ts
12
+ * import { FISCAL_CONDITIONS } from 'platform-types';
13
+ * ```
14
+ *
15
+ * The api re-exports this exact binding from `stacks/services/fiscalCatalogs.ts`,
16
+ * which is where invoice issuance reads it.
17
+ *
18
+ * ⚠️ **`condFiscal` is NOT a unique key.** Code `32` has two rows: «Iva Exento»
19
+ * (`DocTipo 80`, a domestic CUIT holder) and «Cliente del Exterior» (`DocTipo 91`).
20
+ * To NAME a code, prefer its `DocTipo 80` row and fall back to its only row, so
21
+ * `32` reads «Iva Exento» and `99`/`96` read Consumidor Final (spelled
22
+ * differently on the two rows):
23
+ *
24
+ * ```ts
25
+ * const rows = FISCAL_CONDITIONS.filter((c) => c.condFiscal === code);
26
+ * const name = (rows.find((c) => c.DocTipo === 80) ?? rows[0])?.name;
27
+ * ```
28
+ *
29
+ * `undefined` means a code this catalogue does not hold — issuance refuses such
30
+ * an order rather than guessing, so render it as unknown, never as a default.
31
+ *
32
+ * Provenance: these are AFIP-standardized catalogues, identical for every
33
+ * tenant, so a reviewed and versioned file is their home rather than a DynamoDB
34
+ * row an operator could silently edit. This is a byte-identical snapshot of the
35
+ * retired `STORE#CONFIG` row, cross-checked against both dev and prod, with ONE
36
+ * deliberate divergence: id 5's `CbteTipo.NC`/`ND` are corrected (see that row).
37
+ *
38
+ * `CbteTipo.NVC` is declared by `FiscalCondition` but was never populated on the
39
+ * live row and nothing reads it: the one hardcoded non-Responsable-Inscripto
40
+ * fallback in issuance sets its own literal `NVC: 16`. The `0` here is an inert
41
+ * placeholder that satisfies the type, not a real AFIP code.
42
+ */
43
+ exports.FISCAL_CONDITIONS = [
44
+ {
45
+ id: 1,
46
+ name: 'Consumidor Final',
47
+ condFiscal: 99,
48
+ DocTipo: 99,
49
+ CbteTipo: { FAC: 6, REC: 9, NC: 8, ND: 7, NVC: 0 },
50
+ },
51
+ {
52
+ id: 2,
53
+ name: 'Responsable Inscripto',
54
+ condFiscal: 30,
55
+ DocTipo: 80,
56
+ CbteTipo: { FAC: 1, REC: 4, NC: 3, ND: 2, NVC: 0 },
57
+ },
58
+ {
59
+ id: 3,
60
+ name: 'Responsable Monotributo',
61
+ condFiscal: 20,
62
+ DocTipo: 80,
63
+ CbteTipo: { FAC: 1, REC: 4, NC: 3, ND: 2, NVC: 0 },
64
+ },
65
+ {
66
+ id: 4,
67
+ name: 'Iva Exento',
68
+ condFiscal: 32,
69
+ DocTipo: 80,
70
+ CbteTipo: { FAC: 6, REC: 9, NC: 8, ND: 7, NVC: 0 },
71
+ },
72
+ {
73
+ id: 5,
74
+ name: 'Cliente del Exterior',
75
+ condFiscal: 32,
76
+ DocTipo: 91,
77
+ // NC/ND corrected from the legacy snapshot, which had them transposed vs
78
+ // RG 5762 (52 = ND, 53 = NC). `.CbteTipo` is unreachable at runtime here:
79
+ // a `condFiscal` 32 lookup resolves to id 4, not id 5.
80
+ CbteTipo: { FAC: 51, REC: 54, NC: 53, ND: 52, NVC: 0 },
81
+ },
82
+ {
83
+ id: 6,
84
+ name: 'CONSUMIDOR FINAL',
85
+ condFiscal: 96,
86
+ DocTipo: 96,
87
+ CbteTipo: { FAC: 6, REC: 9, NC: 8, ND: 7, NVC: 0 },
88
+ },
89
+ ];
package/dist/cjs/index.js CHANGED
@@ -52,6 +52,7 @@ __exportStar(require("./customerTimeline.js"), exports);
52
52
  __exportStar(require("./cycleCount.js"), exports);
53
53
  __exportStar(require("./delivery.js"), exports);
54
54
  __exportStar(require("./favorite.js"), exports);
55
+ __exportStar(require("./fiscalConditions.js"), exports);
55
56
  __exportStar(require("./gating.js"), exports);
56
57
  __exportStar(require("./holdingCost.js"), exports);
57
58
  __exportStar(require("./demo.js"), exports);
package/dist/cjs/store.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.STORE_MODULE_ERROR_CODES = exports.isStoreModuleOn = exports.isStoreModuleKey = exports.STORE_MODULE_KEYS = exports.CUSTOM_DOMAIN_ROW_LIMIT = exports.CUSTOM_DOMAIN_SLOT_LIMIT = void 0;
3
+ exports.STORE_PATCH_ERROR_CODES = exports.STORE_MODULE_ERROR_CODES = exports.isStoreModuleOn = exports.isStoreModuleKey = exports.STORE_MODULE_KEYS = exports.CUSTOM_DOMAIN_ROW_LIMIT = exports.CUSTOM_DOMAIN_SLOT_LIMIT = void 0;
4
4
  /**
5
5
  * How many custom-domain rows a store may hold that count: one active domain
6
6
  * and one spare, so a replacement can be verified and provisioned before the
@@ -52,9 +52,11 @@ exports.isStoreModuleKey = isStoreModuleKey;
52
52
  const isStoreModuleOn = (modules, module) => modules?.[module] !== false;
53
53
  exports.isStoreModuleOn = isStoreModuleOn;
54
54
  /**
55
- * Every refusal code the store-module switches put on the wire, beyond the
56
- * platform-wide set (`VALIDATION_FAILED`, and `STORE_CONFIG_FORBIDDEN` for a
57
- * plain USER writing the privileged leaf):
55
+ * Every refusal code the store-module switches put on the wire. Not listed
56
+ * here: `VALIDATION_FAILED` (a malformed body), and `STORE_CONFIG_FORBIDDEN` —
57
+ * the `PATCH /store` role refusal a plain USER gets for ANY privileged `config`
58
+ * leaf, `modules` included, judged before this module code runs. That one is in
59
+ * {@link STORE_PATCH_ERROR_CODES}:
58
60
  *
59
61
  * - `STORE_MODULE_UNKNOWN` (400) — `PATCH /store` named a key outside
60
62
  * `STORE_MODULE_KEYS`; `data.module` carries it.
@@ -91,3 +93,69 @@ exports.STORE_MODULE_ERROR_CODES = [
91
93
  'STORE_MODULE_SHIFTS_OPEN',
92
94
  'STORE_MODULE_DISABLED',
93
95
  ];
96
+ /**
97
+ * The bare refusal codes `PATCH /store` (`stacks/lambdas/store/_patch.ts`, and
98
+ * the route's own sibling modules it calls) answers itself — `{ error, message }`
99
+ * with no `data`, unless noted:
100
+ *
101
+ * Role refusals, `403` — a plain USER writing a privileged section. Each is
102
+ * judged before any other check on that section:
103
+ * - `STORE_CONFIG_FORBIDDEN` — any privileged `config` leaf (`modules`,
104
+ * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, …).
105
+ * - `STORE_ECOMMERCE_FORBIDDEN` — any `ecommerce` block.
106
+ * - `STORE_FISCAL_FIELD_FORBIDDEN` — a fiscal field of the store.
107
+ * - `STORE_JURISDICTION_FORBIDDEN` — `jurisdiction`.
108
+ * - `AFIP_RELACION_FLAG_FORBIDDEN` — `afip.wscdcEnabled` / `afip.fceEnabled`.
109
+ * - `FX_TABLE_FORBIDDEN` — the `currencies` exchange-rate table.
110
+ * - `FX_AUTO_UPDATE_FORBIDDEN` — `fxAutoUpdate`.
111
+ *
112
+ * Request refusals, `400`:
113
+ * - `FIELD_NOT_WRITABLE_HERE` — a field written through `POST /store`, not
114
+ * this route; a top-level `fields` array names them.
115
+ * - `AFIP_POINT_OF_SALE_NOT_CLEARABLE` — `null` sent for the punto de venta.
116
+ * - `CURRENCY_CATALOG_ID_UNKNOWN` — a `currencies` entry names an unknown or
117
+ * disabled catalog id.
118
+ * - `ARS_RATE_MUST_BE_ONE` — the peso entry's value is not exactly `1`.
119
+ * - `CURRENCY_RATE_ABOVE_CEILING` — a `currencies` value is above its ceiling.
120
+ * - `FX_CATALOG_ID_UNKNOWN` / `FX_SOURCE_ID_UNKNOWN` — an `fxAutoUpdate`
121
+ * binding names an unknown catalog id or rate source.
122
+ * - `DISPLAY_CURRENCY_NOT_INVOICEABLE` / `PRODUCT_CURRENCY_NOT_INVOICEABLE` — a
123
+ * store carrying an AFIP CUIT set `config.displayCurrency` /
124
+ * `config.defaultProductCurrency` to a non-oficial USD variant.
125
+ * - `PRICE_LIST_NOT_FOUND` — an `ecommerce` save names a
126
+ * `newCustomerPriceList` that does not exist (`store/_ecommercePatch.ts`).
127
+ *
128
+ * Maintenance mode (`{ mode: 'maintenance', level }`, `store/_patchMaintenance.ts`):
129
+ * - `FORBIDDEN` (403) — the caller is neither an ADMIN nor a MANAGER.
130
+ * - `STORE_NOT_FOUND` (404) — the store row is gone.
131
+ *
132
+ * ⚠️ Not repeated here because another tuple already publishes them:
133
+ * `VALIDATION_FAILED`, `PRODUCT_CURRENCY_NOT_SUBSCRIBED`
134
+ * (`PRODUCT_POST_ERROR_CODES`), `WAREHOUSE_NOT_FOUND` /
135
+ * `WAREHOUSE_INACTIVE` (a `channelWarehouses` target), and every
136
+ * `STORE_MODULE_*` code ({@link STORE_MODULE_ERROR_CODES}). Codes the route
137
+ * forwards from shared helpers (the write conflict, the CAEA punto-de-venta
138
+ * checks, the display-currency lock, the storefront fence) are not listed
139
+ * either. Cross-cutting refusals: check `GATING_ERROR_CODES` as well.
140
+ */
141
+ exports.STORE_PATCH_ERROR_CODES = [
142
+ 'STORE_CONFIG_FORBIDDEN',
143
+ 'STORE_ECOMMERCE_FORBIDDEN',
144
+ 'STORE_FISCAL_FIELD_FORBIDDEN',
145
+ 'STORE_JURISDICTION_FORBIDDEN',
146
+ 'AFIP_RELACION_FLAG_FORBIDDEN',
147
+ 'FX_TABLE_FORBIDDEN',
148
+ 'FX_AUTO_UPDATE_FORBIDDEN',
149
+ 'FIELD_NOT_WRITABLE_HERE',
150
+ 'AFIP_POINT_OF_SALE_NOT_CLEARABLE',
151
+ 'CURRENCY_CATALOG_ID_UNKNOWN',
152
+ 'ARS_RATE_MUST_BE_ONE',
153
+ 'CURRENCY_RATE_ABOVE_CEILING',
154
+ 'FX_CATALOG_ID_UNKNOWN',
155
+ 'FX_SOURCE_ID_UNKNOWN',
156
+ 'DISPLAY_CURRENCY_NOT_INVOICEABLE',
157
+ 'PRODUCT_CURRENCY_NOT_INVOICEABLE',
158
+ 'PRICE_LIST_NOT_FOUND',
159
+ 'FORBIDDEN',
160
+ 'STORE_NOT_FOUND',
161
+ ];
@@ -30,7 +30,7 @@
30
30
  * enabled:false until they do.
31
31
  */
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.FEATURE_KEYS = exports.isPaidPlanTier = exports.PLAN_CATALOGUE_ERROR_CODES = exports.PLAN_CATALOGUE_TEMPLATE_FIELDS = exports.PAID_PLAN_TIERS = exports.PLAN_TIERS = void 0;
33
+ exports.FEATURE_KEYS = exports.isPaidPlanTier = exports.PLAN_PRICE_SYNC_ERROR_CODES = exports.PLAN_CATALOGUE_ERROR_CODES = exports.PLAN_CATALOGUE_TEMPLATE_FIELDS = exports.PAID_PLAN_TIERS = exports.PLAN_TIERS = void 0;
34
34
  /*
35
35
  * Runtime tuples for the unions above.
36
36
  *
@@ -79,6 +79,10 @@ exports.PLAN_CATALOGUE_ERROR_CODES = exhaustiveTuple()([
79
79
  'PLAN_CATALOGUE_DRAFT_STALE',
80
80
  'IDEMPOTENCY_CONFLICT',
81
81
  ]);
82
+ /** Every `error` code `POST /subscription/checkout` adds for a price the provider has not caught up with. */
83
+ exports.PLAN_PRICE_SYNC_ERROR_CODES = exhaustiveTuple()([
84
+ 'PLAN_STRIPE_PRICE_STALE',
85
+ ]);
82
86
  /** Whether `value` names a paid tier — narrows an untyped read (a stored row, a global) to `PaidPlanTier`. */
83
87
  const isPaidPlanTier = (value) => typeof value === 'string' && exports.PAID_PLAN_TIERS.includes(value);
84
88
  exports.isPaidPlanTier = isPaidPlanTier;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The ARCA fiscal-condition catalogue — the ONE definition. It maps each legacy
3
+ * `condFiscal` code to its display name, its ARCA `DocTipo` and the `CbteTipo`
4
+ * family that issuance resolves for it.
5
+ *
6
+ * Deliberately NOT `declare global`: it is a runtime value, so import it:
7
+ *
8
+ * ```ts
9
+ * import { FISCAL_CONDITIONS } from 'platform-types';
10
+ * ```
11
+ *
12
+ * The api re-exports this exact binding from `stacks/services/fiscalCatalogs.ts`,
13
+ * which is where invoice issuance reads it.
14
+ *
15
+ * ⚠️ **`condFiscal` is NOT a unique key.** Code `32` has two rows: «Iva Exento»
16
+ * (`DocTipo 80`, a domestic CUIT holder) and «Cliente del Exterior» (`DocTipo 91`).
17
+ * To NAME a code, prefer its `DocTipo 80` row and fall back to its only row, so
18
+ * `32` reads «Iva Exento» and `99`/`96` read Consumidor Final (spelled
19
+ * differently on the two rows):
20
+ *
21
+ * ```ts
22
+ * const rows = FISCAL_CONDITIONS.filter((c) => c.condFiscal === code);
23
+ * const name = (rows.find((c) => c.DocTipo === 80) ?? rows[0])?.name;
24
+ * ```
25
+ *
26
+ * `undefined` means a code this catalogue does not hold — issuance refuses such
27
+ * an order rather than guessing, so render it as unknown, never as a default.
28
+ *
29
+ * Provenance: these are AFIP-standardized catalogues, identical for every
30
+ * tenant, so a reviewed and versioned file is their home rather than a DynamoDB
31
+ * row an operator could silently edit. This is a byte-identical snapshot of the
32
+ * retired `STORE#CONFIG` row, cross-checked against both dev and prod, with ONE
33
+ * deliberate divergence: id 5's `CbteTipo.NC`/`ND` are corrected (see that row).
34
+ *
35
+ * `CbteTipo.NVC` is declared by `FiscalCondition` but was never populated on the
36
+ * live row and nothing reads it: the one hardcoded non-Responsable-Inscripto
37
+ * fallback in issuance sets its own literal `NVC: 16`. The `0` here is an inert
38
+ * placeholder that satisfies the type, not a real AFIP code.
39
+ */
40
+ export declare const FISCAL_CONDITIONS: FiscalCondition[];
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The ARCA fiscal-condition catalogue — the ONE definition. It maps each legacy
3
+ * `condFiscal` code to its display name, its ARCA `DocTipo` and the `CbteTipo`
4
+ * family that issuance resolves for it.
5
+ *
6
+ * Deliberately NOT `declare global`: it is a runtime value, so import it:
7
+ *
8
+ * ```ts
9
+ * import { FISCAL_CONDITIONS } from 'platform-types';
10
+ * ```
11
+ *
12
+ * The api re-exports this exact binding from `stacks/services/fiscalCatalogs.ts`,
13
+ * which is where invoice issuance reads it.
14
+ *
15
+ * ⚠️ **`condFiscal` is NOT a unique key.** Code `32` has two rows: «Iva Exento»
16
+ * (`DocTipo 80`, a domestic CUIT holder) and «Cliente del Exterior» (`DocTipo 91`).
17
+ * To NAME a code, prefer its `DocTipo 80` row and fall back to its only row, so
18
+ * `32` reads «Iva Exento» and `99`/`96` read Consumidor Final (spelled
19
+ * differently on the two rows):
20
+ *
21
+ * ```ts
22
+ * const rows = FISCAL_CONDITIONS.filter((c) => c.condFiscal === code);
23
+ * const name = (rows.find((c) => c.DocTipo === 80) ?? rows[0])?.name;
24
+ * ```
25
+ *
26
+ * `undefined` means a code this catalogue does not hold — issuance refuses such
27
+ * an order rather than guessing, so render it as unknown, never as a default.
28
+ *
29
+ * Provenance: these are AFIP-standardized catalogues, identical for every
30
+ * tenant, so a reviewed and versioned file is their home rather than a DynamoDB
31
+ * row an operator could silently edit. This is a byte-identical snapshot of the
32
+ * retired `STORE#CONFIG` row, cross-checked against both dev and prod, with ONE
33
+ * deliberate divergence: id 5's `CbteTipo.NC`/`ND` are corrected (see that row).
34
+ *
35
+ * `CbteTipo.NVC` is declared by `FiscalCondition` but was never populated on the
36
+ * live row and nothing reads it: the one hardcoded non-Responsable-Inscripto
37
+ * fallback in issuance sets its own literal `NVC: 16`. The `0` here is an inert
38
+ * placeholder that satisfies the type, not a real AFIP code.
39
+ */
40
+ export const FISCAL_CONDITIONS = [
41
+ {
42
+ id: 1,
43
+ name: 'Consumidor Final',
44
+ condFiscal: 99,
45
+ DocTipo: 99,
46
+ CbteTipo: { FAC: 6, REC: 9, NC: 8, ND: 7, NVC: 0 },
47
+ },
48
+ {
49
+ id: 2,
50
+ name: 'Responsable Inscripto',
51
+ condFiscal: 30,
52
+ DocTipo: 80,
53
+ CbteTipo: { FAC: 1, REC: 4, NC: 3, ND: 2, NVC: 0 },
54
+ },
55
+ {
56
+ id: 3,
57
+ name: 'Responsable Monotributo',
58
+ condFiscal: 20,
59
+ DocTipo: 80,
60
+ CbteTipo: { FAC: 1, REC: 4, NC: 3, ND: 2, NVC: 0 },
61
+ },
62
+ {
63
+ id: 4,
64
+ name: 'Iva Exento',
65
+ condFiscal: 32,
66
+ DocTipo: 80,
67
+ CbteTipo: { FAC: 6, REC: 9, NC: 8, ND: 7, NVC: 0 },
68
+ },
69
+ {
70
+ id: 5,
71
+ name: 'Cliente del Exterior',
72
+ condFiscal: 32,
73
+ DocTipo: 91,
74
+ // NC/ND corrected from the legacy snapshot, which had them transposed vs
75
+ // RG 5762 (52 = ND, 53 = NC). `.CbteTipo` is unreachable at runtime here:
76
+ // a `condFiscal` 32 lookup resolves to id 4, not id 5.
77
+ CbteTipo: { FAC: 51, REC: 54, NC: 53, ND: 52, NVC: 0 },
78
+ },
79
+ {
80
+ id: 6,
81
+ name: 'CONSUMIDOR FINAL',
82
+ condFiscal: 96,
83
+ DocTipo: 96,
84
+ CbteTipo: { FAC: 6, REC: 9, NC: 8, ND: 7, NVC: 0 },
85
+ },
86
+ ];
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@ export * from './customerTimeline.ts';
28
28
  export * from './cycleCount.ts';
29
29
  export * from './delivery.ts';
30
30
  export * from './favorite.ts';
31
+ export * from './fiscalConditions.ts';
31
32
  export * from './gating.ts';
32
33
  export * from './holdingCost.ts';
33
34
  export * from './demo.ts';
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export * from "./customerTimeline.js";
36
36
  export * from "./cycleCount.js";
37
37
  export * from "./delivery.js";
38
38
  export * from "./favorite.js";
39
+ export * from "./fiscalConditions.js";
39
40
  export * from "./gating.js";
40
41
  export * from "./holdingCost.js";
41
42
  export * from "./demo.js";
package/dist/order.d.ts CHANGED
@@ -953,11 +953,31 @@ declare global {
953
953
  * its own — method ids are per-catalog ordinals.
954
954
  */
955
955
  deliveryMethod?: number;
956
+ /**
957
+ * The receptor the order is invoiced to, as the creating client stated it.
958
+ *
959
+ * ⚠️ **Every key is optional, and readers must tolerate each one absent.**
960
+ * That is what the writers persist, not a loosening: the storefront checkout
961
+ * validates this object with every key optional and writes it verbatim (an
962
+ * empty object included), the presupuesto conversion passes the operator's
963
+ * object through, and only the operator create requires `condFiscal`.
964
+ *
965
+ * - `razonSocial` is routinely absent: a Consumidor Final receptor has no
966
+ * legal name, and none may be invented for one.
967
+ * - `condFiscalName` is display-only and usually absent. Never read it for
968
+ * meaning: derive the name from `condFiscal` with the published
969
+ * `FISCAL_CONDITIONS` catalogue (its docblock gives the lookup, since
970
+ * `condFiscal` alone is not a unique key there).
971
+ * - `condFiscal` and `cuit` absent are a PERSISTABLE state, not an
972
+ * invoiceable one: issuing a factura refuses the order with a coded `400`
973
+ * (`INVOICE_METHOD_COND_FISCAL_INVALID`, `INVOICE_METHOD_CUIT_MISSING`)
974
+ * until the operator completes it.
975
+ */
956
976
  invoiceMethod?: {
957
- condFiscal: number;
958
- condFiscalName: string;
959
- cuit: string;
960
- razonSocial: string;
977
+ condFiscal?: number;
978
+ condFiscalName?: string;
979
+ cuit?: string;
980
+ razonSocial?: string;
961
981
  /**
962
982
  * Explicit per-order ARCA receptor identity, decoupled from condFiscal
963
983
  * (ARCA DocTipo: 80 = CUIT, 96 = DNI, 99 = Consumidor Final — a
package/dist/store.d.ts CHANGED
@@ -918,7 +918,7 @@ declare global {
918
918
  * not finish); `cash` while any cash shift is still open
919
919
  * (`409 STORE_MODULE_SHIFTS_OPEN` — `StoreModuleShiftsOpenData`).
920
920
  *
921
- * Enforced on the server for seven modules only:
921
+ * Enforced on the server for nine modules only:
922
922
  * - `promotions` — applying one is refused `PROMOTION_NOT_ACTIVE` (the
923
923
  * existing promotion refusal, not `STORE_MODULE_DISABLED`), a grant already
924
924
  * frozen on a cart stays on the row but derives no money (its
@@ -945,8 +945,22 @@ declare global {
945
945
  * settled `'skipped'` with reason `MODULE_OFF` at its send worker.
946
946
  * - `cash` — a POS terminal activates WITHOUT a shift (no `shiftId`,
947
947
  * living until its own expiry), and opening a shift (`POST /shifts`) is
948
- * refused `403 STORE_MODULE_DISABLED`; every other `/shifts` endpoint
949
- * (close, movements, reconcile, reads) stays reachable.
948
+ * refused `403 STORE_MODULE_DISABLED` — re-checked at commit, so an
949
+ * open that commits after the turn-off is refused (one committing
950
+ * inside the turn-off's own check-then-write window is accepted — see
951
+ * `docs/STORE.md` § Store module switches); every other `/shifts`
952
+ * endpoint (close, movements, reconcile, reads) stays reachable.
953
+ * - `customerAccounts` — the scheduled payment-reminder cron sends
954
+ * nothing for the store, and a scheduled reminder email already queued
955
+ * is dropped; the operator's per-customer send-reminder stays reachable.
956
+ * - `fiscalInvoicing` — "stop new, finish old": the CAEA request cron
957
+ * requests no new CAEA for the store and `AFIP_CERT_EXPIRY` is muted
958
+ * for it; a price-freezing seña over an order with no live factura
959
+ * freezes with no `invoiceId` (re-checked at commit) instead of being
960
+ * refused `DEPOSIT_FREEZE_REQUIRES_INVOICE`, and while that seña is held
961
+ * a re-price that moves the charged total is `409 ORDER_PRICE_FROZEN`,
962
+ * even after the module is back on. The CAEA inform cron, the return
963
+ * credit-note reconciler and operator-initiated invoicing are unchanged.
950
964
  * Every other module is hide-only: its endpoints stay reachable. Public
951
965
  * reads carry only `appointments`, `surveys`, `promotions` and
952
966
  * `customerAccounts`. `PUT /platform/stores/{storeId}` refuses the leaf.
@@ -2032,9 +2046,11 @@ export declare const isStoreModuleKey: (value: unknown) => value is StoreModuleK
2032
2046
  */
2033
2047
  export declare const isStoreModuleOn: (modules: StoreModules | null | undefined, module: StoreModuleKey) => boolean;
2034
2048
  /**
2035
- * Every refusal code the store-module switches put on the wire, beyond the
2036
- * platform-wide set (`VALIDATION_FAILED`, and `STORE_CONFIG_FORBIDDEN` for a
2037
- * plain USER writing the privileged leaf):
2049
+ * Every refusal code the store-module switches put on the wire. Not listed
2050
+ * here: `VALIDATION_FAILED` (a malformed body), and `STORE_CONFIG_FORBIDDEN` —
2051
+ * the `PATCH /store` role refusal a plain USER gets for ANY privileged `config`
2052
+ * leaf, `modules` included, judged before this module code runs. That one is in
2053
+ * {@link STORE_PATCH_ERROR_CODES}:
2038
2054
  *
2039
2055
  * - `STORE_MODULE_UNKNOWN` (400) — `PATCH /store` named a key outside
2040
2056
  * `STORE_MODULE_KEYS`; `data.module` carries it.
@@ -2147,3 +2163,50 @@ export type StoreModuleRefusalBody = {
2147
2163
  data: StoreModuleRefusalDataByCode[Code];
2148
2164
  };
2149
2165
  }[StoreModuleErrorCode];
2166
+ /**
2167
+ * The bare refusal codes `PATCH /store` (`stacks/lambdas/store/_patch.ts`, and
2168
+ * the route's own sibling modules it calls) answers itself — `{ error, message }`
2169
+ * with no `data`, unless noted:
2170
+ *
2171
+ * Role refusals, `403` — a plain USER writing a privileged section. Each is
2172
+ * judged before any other check on that section:
2173
+ * - `STORE_CONFIG_FORBIDDEN` — any privileged `config` leaf (`modules`,
2174
+ * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, …).
2175
+ * - `STORE_ECOMMERCE_FORBIDDEN` — any `ecommerce` block.
2176
+ * - `STORE_FISCAL_FIELD_FORBIDDEN` — a fiscal field of the store.
2177
+ * - `STORE_JURISDICTION_FORBIDDEN` — `jurisdiction`.
2178
+ * - `AFIP_RELACION_FLAG_FORBIDDEN` — `afip.wscdcEnabled` / `afip.fceEnabled`.
2179
+ * - `FX_TABLE_FORBIDDEN` — the `currencies` exchange-rate table.
2180
+ * - `FX_AUTO_UPDATE_FORBIDDEN` — `fxAutoUpdate`.
2181
+ *
2182
+ * Request refusals, `400`:
2183
+ * - `FIELD_NOT_WRITABLE_HERE` — a field written through `POST /store`, not
2184
+ * this route; a top-level `fields` array names them.
2185
+ * - `AFIP_POINT_OF_SALE_NOT_CLEARABLE` — `null` sent for the punto de venta.
2186
+ * - `CURRENCY_CATALOG_ID_UNKNOWN` — a `currencies` entry names an unknown or
2187
+ * disabled catalog id.
2188
+ * - `ARS_RATE_MUST_BE_ONE` — the peso entry's value is not exactly `1`.
2189
+ * - `CURRENCY_RATE_ABOVE_CEILING` — a `currencies` value is above its ceiling.
2190
+ * - `FX_CATALOG_ID_UNKNOWN` / `FX_SOURCE_ID_UNKNOWN` — an `fxAutoUpdate`
2191
+ * binding names an unknown catalog id or rate source.
2192
+ * - `DISPLAY_CURRENCY_NOT_INVOICEABLE` / `PRODUCT_CURRENCY_NOT_INVOICEABLE` — a
2193
+ * store carrying an AFIP CUIT set `config.displayCurrency` /
2194
+ * `config.defaultProductCurrency` to a non-oficial USD variant.
2195
+ * - `PRICE_LIST_NOT_FOUND` — an `ecommerce` save names a
2196
+ * `newCustomerPriceList` that does not exist (`store/_ecommercePatch.ts`).
2197
+ *
2198
+ * Maintenance mode (`{ mode: 'maintenance', level }`, `store/_patchMaintenance.ts`):
2199
+ * - `FORBIDDEN` (403) — the caller is neither an ADMIN nor a MANAGER.
2200
+ * - `STORE_NOT_FOUND` (404) — the store row is gone.
2201
+ *
2202
+ * ⚠️ Not repeated here because another tuple already publishes them:
2203
+ * `VALIDATION_FAILED`, `PRODUCT_CURRENCY_NOT_SUBSCRIBED`
2204
+ * (`PRODUCT_POST_ERROR_CODES`), `WAREHOUSE_NOT_FOUND` /
2205
+ * `WAREHOUSE_INACTIVE` (a `channelWarehouses` target), and every
2206
+ * `STORE_MODULE_*` code ({@link STORE_MODULE_ERROR_CODES}). Codes the route
2207
+ * forwards from shared helpers (the write conflict, the CAEA punto-de-venta
2208
+ * checks, the display-currency lock, the storefront fence) are not listed
2209
+ * either. Cross-cutting refusals: check `GATING_ERROR_CODES` as well.
2210
+ */
2211
+ export declare const STORE_PATCH_ERROR_CODES: readonly ["STORE_CONFIG_FORBIDDEN", "STORE_ECOMMERCE_FORBIDDEN", "STORE_FISCAL_FIELD_FORBIDDEN", "STORE_JURISDICTION_FORBIDDEN", "AFIP_RELACION_FLAG_FORBIDDEN", "FX_TABLE_FORBIDDEN", "FX_AUTO_UPDATE_FORBIDDEN", "FIELD_NOT_WRITABLE_HERE", "AFIP_POINT_OF_SALE_NOT_CLEARABLE", "CURRENCY_CATALOG_ID_UNKNOWN", "ARS_RATE_MUST_BE_ONE", "CURRENCY_RATE_ABOVE_CEILING", "FX_CATALOG_ID_UNKNOWN", "FX_SOURCE_ID_UNKNOWN", "DISPLAY_CURRENCY_NOT_INVOICEABLE", "PRODUCT_CURRENCY_NOT_INVOICEABLE", "PRICE_LIST_NOT_FOUND", "FORBIDDEN", "STORE_NOT_FOUND"];
2212
+ export type StorePatchErrorCode = (typeof STORE_PATCH_ERROR_CODES)[number];
package/dist/store.js CHANGED
@@ -47,9 +47,11 @@ export const isStoreModuleKey = (value) => typeof value === 'string' && STORE_MO
47
47
  */
48
48
  export const isStoreModuleOn = (modules, module) => modules?.[module] !== false;
49
49
  /**
50
- * Every refusal code the store-module switches put on the wire, beyond the
51
- * platform-wide set (`VALIDATION_FAILED`, and `STORE_CONFIG_FORBIDDEN` for a
52
- * plain USER writing the privileged leaf):
50
+ * Every refusal code the store-module switches put on the wire. Not listed
51
+ * here: `VALIDATION_FAILED` (a malformed body), and `STORE_CONFIG_FORBIDDEN` —
52
+ * the `PATCH /store` role refusal a plain USER gets for ANY privileged `config`
53
+ * leaf, `modules` included, judged before this module code runs. That one is in
54
+ * {@link STORE_PATCH_ERROR_CODES}:
53
55
  *
54
56
  * - `STORE_MODULE_UNKNOWN` (400) — `PATCH /store` named a key outside
55
57
  * `STORE_MODULE_KEYS`; `data.module` carries it.
@@ -86,3 +88,69 @@ export const STORE_MODULE_ERROR_CODES = [
86
88
  'STORE_MODULE_SHIFTS_OPEN',
87
89
  'STORE_MODULE_DISABLED',
88
90
  ];
91
+ /**
92
+ * The bare refusal codes `PATCH /store` (`stacks/lambdas/store/_patch.ts`, and
93
+ * the route's own sibling modules it calls) answers itself — `{ error, message }`
94
+ * with no `data`, unless noted:
95
+ *
96
+ * Role refusals, `403` — a plain USER writing a privileged section. Each is
97
+ * judged before any other check on that section:
98
+ * - `STORE_CONFIG_FORBIDDEN` — any privileged `config` leaf (`modules`,
99
+ * `stock`, `priceDecimals`, `changePrice`, `mandatory2fa`, …).
100
+ * - `STORE_ECOMMERCE_FORBIDDEN` — any `ecommerce` block.
101
+ * - `STORE_FISCAL_FIELD_FORBIDDEN` — a fiscal field of the store.
102
+ * - `STORE_JURISDICTION_FORBIDDEN` — `jurisdiction`.
103
+ * - `AFIP_RELACION_FLAG_FORBIDDEN` — `afip.wscdcEnabled` / `afip.fceEnabled`.
104
+ * - `FX_TABLE_FORBIDDEN` — the `currencies` exchange-rate table.
105
+ * - `FX_AUTO_UPDATE_FORBIDDEN` — `fxAutoUpdate`.
106
+ *
107
+ * Request refusals, `400`:
108
+ * - `FIELD_NOT_WRITABLE_HERE` — a field written through `POST /store`, not
109
+ * this route; a top-level `fields` array names them.
110
+ * - `AFIP_POINT_OF_SALE_NOT_CLEARABLE` — `null` sent for the punto de venta.
111
+ * - `CURRENCY_CATALOG_ID_UNKNOWN` — a `currencies` entry names an unknown or
112
+ * disabled catalog id.
113
+ * - `ARS_RATE_MUST_BE_ONE` — the peso entry's value is not exactly `1`.
114
+ * - `CURRENCY_RATE_ABOVE_CEILING` — a `currencies` value is above its ceiling.
115
+ * - `FX_CATALOG_ID_UNKNOWN` / `FX_SOURCE_ID_UNKNOWN` — an `fxAutoUpdate`
116
+ * binding names an unknown catalog id or rate source.
117
+ * - `DISPLAY_CURRENCY_NOT_INVOICEABLE` / `PRODUCT_CURRENCY_NOT_INVOICEABLE` — a
118
+ * store carrying an AFIP CUIT set `config.displayCurrency` /
119
+ * `config.defaultProductCurrency` to a non-oficial USD variant.
120
+ * - `PRICE_LIST_NOT_FOUND` — an `ecommerce` save names a
121
+ * `newCustomerPriceList` that does not exist (`store/_ecommercePatch.ts`).
122
+ *
123
+ * Maintenance mode (`{ mode: 'maintenance', level }`, `store/_patchMaintenance.ts`):
124
+ * - `FORBIDDEN` (403) — the caller is neither an ADMIN nor a MANAGER.
125
+ * - `STORE_NOT_FOUND` (404) — the store row is gone.
126
+ *
127
+ * ⚠️ Not repeated here because another tuple already publishes them:
128
+ * `VALIDATION_FAILED`, `PRODUCT_CURRENCY_NOT_SUBSCRIBED`
129
+ * (`PRODUCT_POST_ERROR_CODES`), `WAREHOUSE_NOT_FOUND` /
130
+ * `WAREHOUSE_INACTIVE` (a `channelWarehouses` target), and every
131
+ * `STORE_MODULE_*` code ({@link STORE_MODULE_ERROR_CODES}). Codes the route
132
+ * forwards from shared helpers (the write conflict, the CAEA punto-de-venta
133
+ * checks, the display-currency lock, the storefront fence) are not listed
134
+ * either. Cross-cutting refusals: check `GATING_ERROR_CODES` as well.
135
+ */
136
+ export const STORE_PATCH_ERROR_CODES = [
137
+ 'STORE_CONFIG_FORBIDDEN',
138
+ 'STORE_ECOMMERCE_FORBIDDEN',
139
+ 'STORE_FISCAL_FIELD_FORBIDDEN',
140
+ 'STORE_JURISDICTION_FORBIDDEN',
141
+ 'AFIP_RELACION_FLAG_FORBIDDEN',
142
+ 'FX_TABLE_FORBIDDEN',
143
+ 'FX_AUTO_UPDATE_FORBIDDEN',
144
+ 'FIELD_NOT_WRITABLE_HERE',
145
+ 'AFIP_POINT_OF_SALE_NOT_CLEARABLE',
146
+ 'CURRENCY_CATALOG_ID_UNKNOWN',
147
+ 'ARS_RATE_MUST_BE_ONE',
148
+ 'CURRENCY_RATE_ABOVE_CEILING',
149
+ 'FX_CATALOG_ID_UNKNOWN',
150
+ 'FX_SOURCE_ID_UNKNOWN',
151
+ 'DISPLAY_CURRENCY_NOT_INVOICEABLE',
152
+ 'PRODUCT_CURRENCY_NOT_INVOICEABLE',
153
+ 'PRICE_LIST_NOT_FOUND',
154
+ 'FORBIDDEN',
155
+ 'STORE_NOT_FOUND',
156
+ ];
@@ -780,11 +780,20 @@ declare global {
780
780
  * place to echo a catalogue's payment-provider IDs.
781
781
  */
782
782
  missingProviderIds: string[];
783
+ /**
784
+ * The charged billing cycles whose Stripe Price was minted at a different amount or currency
785
+ * than the row now publishes (its `stripePrice*Cents` / `stripePriceCurrency` stamp differs):
786
+ * Stripe checkout refuses them (`409 PLAN_STRIPE_PRICE_STALE`) until `seed-stripe-products`
787
+ * re-mints. A cycle with no stamp (a row seeded before the stamp existed) is never listed.
788
+ */
789
+ staleStripePriceCycles: ('monthly' | 'annual')[];
783
790
  /**
784
791
  * Keys in the template row's `grants` overlay that the api does not
785
792
  * declare (retired or misspelt), sorted. Inert — every reader ignores them
786
793
  * and the code default applies — but stored, so they are drift: a `null`
787
- * for that key on `PATCH /platform/billing/plans/grants` removes them.
794
+ * for that key on `PATCH /platform/billing/plans/grants`, then
795
+ * `POST /platform/billing/plans/publish`, removes them (the PATCH alone
796
+ * writes only the draft).
788
797
  */
789
798
  undeclaredGrantKeys: string[];
790
799
  /**
@@ -1093,10 +1102,24 @@ declare global {
1093
1102
  reconciledStores?: number;
1094
1103
  /**
1095
1104
  * A paid tier whose price, currency or name changed, with a currency set on the draft being
1096
- * published (never `basico`, never when that currency is absent): run the provider seeders
1097
- * to mint its new prices.
1105
+ * published (never `basico`, never when that currency is absent). A live publish mints the
1106
+ * new Stripe Prices itself when Stripe already bills the tier (see `stripePriceMinted`), and
1107
+ * that mint also converges the Stripe Product's name on the published `name`. The MercadoPago
1108
+ * PreApproval plans, and a Product rename after a `name`-only publish (which mints nothing),
1109
+ * stay the seeders'.
1098
1110
  */
1099
1111
  stripeSyncRequired: boolean;
1112
+ /**
1113
+ * Live publish only, and only on a tier whose price or currency moved while Stripe bills it
1114
+ * (its row carries a `stripeProductId`): `true` when the publish minted the new Stripe Prices
1115
+ * and stamped them on the row. `false` means this publish did NOT stamp the row, for one of
1116
+ * two reasons: the mint failed (an operator ERROR row `STRIPE_PLAN_PRICE_MINT_FAILED` is
1117
+ * written, and Stripe checkout refuses the tier with `409 PLAN_STRIPE_PRICE_STALE` until
1118
+ * `seed-stripe-products` re-mints), or a LATER publish moved the price before this mint's
1119
+ * stamp landed — superseded, so no ERROR row: that later publish mints its own. Absent
1120
+ * otherwise.
1121
+ */
1122
+ stripePriceMinted?: boolean;
1100
1123
  }
1101
1124
  /** `data` of `POST /platform/billing/plans/publish`. */
1102
1125
  interface PlanCataloguePublishResult {
@@ -1161,6 +1184,20 @@ declare global {
1161
1184
  * revision moved: send the same request again.
1162
1185
  */
1163
1186
  | 'PLAN_CATALOGUE_BUSY' | 'PLAN_CATALOGUE_NO_CHANGES' | 'PLAN_CATALOGUE_PUBLICATION_NOT_FOUND' | 'PLAN_CATALOGUE_REQUEST_EXPIRED' | 'PLAN_CATALOGUE_INVALID_CURSOR' | 'PLAN_CATALOGUE_DRAFT_STALE' | 'IDEMPOTENCY_CONFLICT';
1187
+ /**
1188
+ * The `error` codes `POST /subscription/checkout` adds when a published price
1189
+ * has not reached the provider that would bill it.
1190
+ */
1191
+ type PlanPriceSyncErrorCode =
1192
+ /**
1193
+ * 409 · the store checks out through Stripe, and the Stripe Price on that
1194
+ * plan cycle was minted at a different amount or currency than the plan
1195
+ * now publishes (its stamp, `stripePrice*Cents` / `stripePriceCurrency`,
1196
+ * differs) — a checkout would bill the old price. Nothing was created; the
1197
+ * operator re-mints (`seed-stripe-products`). Carries `planTier` and
1198
+ * `billingCycle` like `PLAN_NOT_PURCHASABLE`.
1199
+ */
1200
+ 'PLAN_STRIPE_PRICE_STALE';
1164
1201
  }
1165
1202
  export type BillingProviderName = SubscriptionProvider;
1166
1203
  export type InvoiceSummary = globalThis.InvoiceSummary;
@@ -1176,6 +1213,8 @@ export declare const PAID_PLAN_TIERS: readonly ["emprendedor", "profesional", "a
1176
1213
  export declare const PLAN_CATALOGUE_TEMPLATE_FIELDS: readonly ["name", "description", "displayOrder", "isActive", "isPopular", "color", "priceMonthlyCents", "priceAnnualCents", "currency"];
1177
1214
  /** Every `error` code the plan catalogue draft / publish routes add — the coverage test holds the handlers to it. */
1178
1215
  export declare const PLAN_CATALOGUE_ERROR_CODES: readonly ["PLAN_CATALOGUE_REVISION_CONFLICT", "PLAN_CATALOGUE_BUSY", "PLAN_CATALOGUE_NO_CHANGES", "PLAN_CATALOGUE_PUBLICATION_NOT_FOUND", "PLAN_CATALOGUE_REQUEST_EXPIRED", "PLAN_CATALOGUE_INVALID_CURSOR", "PLAN_CATALOGUE_DRAFT_STALE", "IDEMPOTENCY_CONFLICT"];
1216
+ /** Every `error` code `POST /subscription/checkout` adds for a price the provider has not caught up with. */
1217
+ export declare const PLAN_PRICE_SYNC_ERROR_CODES: readonly ["PLAN_STRIPE_PRICE_STALE"];
1179
1218
  /** Whether `value` names a paid tier — narrows an untyped read (a stored row, a global) to `PaidPlanTier`. */
1180
1219
  export declare const isPaidPlanTier: (value: unknown) => value is PaidPlanTier;
1181
1220
  /**
@@ -76,6 +76,10 @@ export const PLAN_CATALOGUE_ERROR_CODES = exhaustiveTuple()([
76
76
  'PLAN_CATALOGUE_DRAFT_STALE',
77
77
  'IDEMPOTENCY_CONFLICT',
78
78
  ]);
79
+ /** Every `error` code `POST /subscription/checkout` adds for a price the provider has not caught up with. */
80
+ export const PLAN_PRICE_SYNC_ERROR_CODES = exhaustiveTuple()([
81
+ 'PLAN_STRIPE_PRICE_STALE',
82
+ ]);
79
83
  /** Whether `value` names a paid tier — narrows an untyped read (a stored row, a global) to `PaidPlanTier`. */
80
84
  export const isPaidPlanTier = (value) => typeof value === 'string' && PAID_PLAN_TIERS.includes(value);
81
85
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.734",
3
+ "version": "1.10.736",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",