platform-types 1.10.700 → 1.10.702

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.
@@ -8,8 +8,11 @@
8
8
  *
9
9
  * Notes:
10
10
  * - Tier names are the 4 locked Spanish tiers (per SUBSCRIPTION_TIERS_BEST_PRACTICES §0):
11
- * BÁSICO, EMPRENDEDOR, PROFESIONAL, AVANZADO. The launch policy
12
- * (ADR-0010) gives every new paid subscription a 30-day Stripe trial; courtesy
11
+ * BÁSICO, EMPRENDEDOR, PROFESIONAL, AVANZADO. A new store starts trialing on
12
+ * the `trialTier` platform global, with `trialEndsAt` set `trialDays` days out
13
+ * (see `PlanCatalogue.trial`); that date is not yet enforced for a store
14
+ * without a payment provider. A Stripe checkout adds its own fixed 30-day
15
+ * trial, which does not follow those globals (ADR-0010). Courtesy
13
16
  * gifts (formerly the Founders cohort) are now a one-off ops action via
14
17
  * `gift-subscription` that sets `freeUntil` on the SUBSCRIPTION row.
15
18
  * - `freeUntil` lives on every Subscription independent of status. It is the
@@ -24,3 +27,81 @@
24
27
  * enabled:false until they do.
25
28
  */
26
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.FEATURE_KEYS = exports.isPaidPlanTier = exports.PAID_PLAN_TIERS = exports.PLAN_TIERS = void 0;
31
+ /*
32
+ * Runtime tuples for the unions above.
33
+ *
34
+ * A union alone cannot be iterated, and any consumer that needs to enumerate
35
+ * every tier or key — zeroing a breakdown, fanning a query across tiers,
36
+ * rendering a pricing table's rows — otherwise hand-rolls its own copy that
37
+ * compiles fine while silently omitting a member. A missing member is not a
38
+ * visible break: it is a bucket that reads zero forever, or a row that never
39
+ * renders.
40
+ *
41
+ * ⚠️ `satisfies readonly PlanTier[]` alone does NOT catch that — it checks each
42
+ * element IS a tier, never that every tier is present. Hence `exhaustiveTuple`,
43
+ * whose second type parameter fails to resolve unless the tuple covers the
44
+ * union, making an omission a compile error at the tuple itself. Module-private
45
+ * on purpose: it is a type-level trick, not part of the published surface.
46
+ */
47
+ const exhaustiveTuple = () => (tuple) => tuple;
48
+ /** Every plan tier, in rank order (lowest first). */
49
+ exports.PLAN_TIERS = exhaustiveTuple()(['basico', 'emprendedor', 'profesional', 'avanzado']);
50
+ /**
51
+ * Every paid tier, in rank order (lowest first) — `PLAN_TIERS` without the free
52
+ * `basico`. The one list of "tiers that bill": the trial-tier vocabulary, the
53
+ * grace sweep's tier fan-out and the provider plan seeders all read it.
54
+ */
55
+ exports.PAID_PLAN_TIERS = exhaustiveTuple()(['emprendedor', 'profesional', 'avanzado']);
56
+ /** Whether `value` names a paid tier — narrows an untyped read (a stored row, a global) to `PaidPlanTier`. */
57
+ const isPaidPlanTier = (value) => typeof value === 'string' && exports.PAID_PLAN_TIERS.includes(value);
58
+ exports.isPaidPlanTier = isPaidPlanTier;
59
+ /**
60
+ * Every gated feature key.
61
+ *
62
+ * ⚠️ Adding a member here is not the whole change, and `typecheck` is what
63
+ * stops it shipping half-done. In the api, a new key needs a grant in **every
64
+ * tier** of `PLAN_FEATURES` (`stacks/services/planFeatureMatrix.ts`): that
65
+ * constant is typed total over every `PlanTier` × `FeatureKey` cell, so a tier
66
+ * missing the new key's grant, or a grant of the wrong shape for the key's
67
+ * declared `type`, fails `typecheck` at the `PLAN_FEATURES` declaration
68
+ * itself. It also widens the api's per-store override key schema, so a new key
69
+ * immediately becomes a valid override target.
70
+ *
71
+ * ⚠️ **A new member also obliges an api PLATFORM-STACK deploy.** Entitlements
72
+ * resolve inside the Lambda authorizer, built in `PlatformStack`. Deploying only
73
+ * the handler stacks leaves a STALE authorizer running the previous matrix: the
74
+ * key is absent from the resolved map, where a boolean gate refuses but a
75
+ * metered / lifetime cap PASSES THROUGH — a new cap silently unbounded.
76
+ * Checklist: `docs/PLANS_AND_ENTITLEMENTS.md` in the api repo.
77
+ */
78
+ exports.FEATURE_KEYS = [
79
+ 'afipInvoicing',
80
+ 'suppliers',
81
+ 'advancedReports',
82
+ 'cashManagement',
83
+ 'importExport',
84
+ 'domesticPayments',
85
+ 'stripePayments',
86
+ 'whatsappCommerce',
87
+ 'aiFeatures',
88
+ 'mobileApp',
89
+ 'customBranding',
90
+ 'customDomain',
91
+ 'prioritySupport',
92
+ 'advancedPricing',
93
+ 'marketplaceChannels',
94
+ 'storefront',
95
+ 'loyalty',
96
+ 'marketing',
97
+ 'mandatory2fa',
98
+ 'maxUsers',
99
+ 'maxProducts',
100
+ 'maxCustomers',
101
+ 'maxWarehouses',
102
+ 'priceListsMax',
103
+ 'webhooks',
104
+ 'maxOrdersMonth',
105
+ 'maxInvoicesMonth',
106
+ 'whatsappMessages',
107
+ ];
@@ -7,8 +7,11 @@
7
7
  *
8
8
  * Notes:
9
9
  * - Tier names are the 4 locked Spanish tiers (per SUBSCRIPTION_TIERS_BEST_PRACTICES §0):
10
- * BÁSICO, EMPRENDEDOR, PROFESIONAL, AVANZADO. The launch policy
11
- * (ADR-0010) gives every new paid subscription a 30-day Stripe trial; courtesy
10
+ * BÁSICO, EMPRENDEDOR, PROFESIONAL, AVANZADO. A new store starts trialing on
11
+ * the `trialTier` platform global, with `trialEndsAt` set `trialDays` days out
12
+ * (see `PlanCatalogue.trial`); that date is not yet enforced for a store
13
+ * without a payment provider. A Stripe checkout adds its own fixed 30-day
14
+ * trial, which does not follow those globals (ADR-0010). Courtesy
12
15
  * gifts (formerly the Founders cohort) are now a one-off ops action via
13
16
  * `gift-subscription` that sets `freeUntil` on the SUBSCRIPTION row.
14
17
  * - `freeUntil` lives on every Subscription independent of status. It is the
@@ -24,10 +27,15 @@
24
27
  */
25
28
  declare global {
26
29
  type PlanTier = 'basico' | 'emprendedor' | 'profesional' | 'avanzado';
30
+ /** A tier that bills — every `PlanTier` but the free `basico`. Enumerated by `PAID_PLAN_TIERS`. */
31
+ type PaidPlanTier = Exclude<PlanTier, 'basico'>;
27
32
  /**
28
33
  * Lifecycle status of a tenant's subscription.
29
34
  *
30
- * - `trialing` — new paid-tier signup in their 30-day Stripe trial.
35
+ * - `trialing` — a trial on a paid tier: a new store trials on the
36
+ * `trialTier` platform global with `trialEndsAt` set `trialDays` days out
37
+ * (`PlanCatalogue.trial`) — a date not yet enforced for a store without a
38
+ * payment provider — and a Stripe checkout adds its own fixed 30-day trial.
31
39
  * - `active` — paid subscription, period current.
32
40
  * - `past_due` — payment failed, in the 7-day grace window.
33
41
  * - `readonly` — grace elapsed, writes blocked, tenant can still read.
@@ -62,9 +70,10 @@ declare global {
62
70
  source?: 'plan' | 'override' | 'trial';
63
71
  }
64
72
  /**
65
- * All gated features. Add new keys here when a new feature becomes gateable;
66
- * every existing plan in `FEATURE_MATRIX` must then declare the new key
67
- * (TypeScript enforces this via `Record<FeatureKey, Entitlement>`).
73
+ * All gated features. Add new keys here when a new feature becomes gateable,
74
+ * and to the `FEATURE_KEYS` tuple below (`UncoveredFeatureKeys` fails
75
+ * `typecheck` naming the key until you do); the api's `PLAN_FEATURES` matrix
76
+ * must then grant it on every tier (typed total over `PlanTier` × `FeatureKey`).
68
77
  *
69
78
  * ⚠️ **`Record<FeatureKey, …>` is NOT the only construction a new member
70
79
  * breaks, and searching for it alone will tell you a consumer is unaffected
@@ -148,28 +157,84 @@ declare global {
148
157
  * booleans.
149
158
  */
150
159
  type PlanFeatureStatus = 'live' | 'planned' | 'future' | 'service';
160
+ /** Singular/plural es-AR noun for a numeric/metered feature's count (e.g. `{ one: 'usuario', other: 'usuarios' }`). */
161
+ type PlanFeatureUnit = {
162
+ one: string;
163
+ other: string;
164
+ };
165
+ /**
166
+ * The window a limit counts over: `month` — a metered counter that resets
167
+ * monthly (e.g. orders); `active` — a numeric cap on rows that exist right
168
+ * now (e.g. products, users, depósitos — disabling one frees a slot).
169
+ */
170
+ type PlanFeaturePeriod = 'month' | 'active';
151
171
  /**
152
- * A single feature row in a Plan's `features[]` array. Matches the
153
- * BE wire format from `GET /subscription/plans` exactly — boolean
154
- * features carry `enabled`, numeric/metered carry `limit` (-1 =
155
- * unlimited).
172
+ * Section a feature row renders under on a pricing table. Each key's label
173
+ * and sort position travel as a {@link PlanFeatureGroup} in
174
+ * `PlanCatalogue.groups`, so a consumer never hard-codes either.
156
175
  */
157
- interface PlanFeature {
176
+ type PlanFeatureGroupKey = 'limits' | 'billing' | 'channels' | 'operations' | 'growth' | 'enterprise';
177
+ interface PlanFeatureGroup {
178
+ key: PlanFeatureGroupKey;
179
+ /** User-facing Spanish heading. */
180
+ label: string;
181
+ /** Sort position among groups (ascending). */
182
+ order: number;
183
+ }
184
+ /** Fields every feature row carries, whatever its `type`. */
185
+ interface PlanFeatureBase {
158
186
  key: FeatureKey;
159
- type: EntitlementType;
160
187
  status: PlanFeatureStatus;
188
+ /** Short user-facing Spanish name (the row's title on a pricing table). */
189
+ label: string;
161
190
  /** User-facing Spanish description. */
162
191
  description: string;
163
- /** Set on `boolean` features; `null` on numeric/metered. */
164
- enabled: boolean | null;
165
- /** Set on `numeric`/`metered` features; `null` on boolean. -1 = unlimited. */
166
- limit: number | null;
192
+ group: PlanFeatureGroupKey;
193
+ /** Sort position within `group` (ascending). `features[]` arrives already sorted by group order, then this. */
194
+ order: number;
195
+ /** `null` on boolean keys. */
196
+ unit: PlanFeatureUnit | null;
197
+ /** `null` on boolean keys. */
198
+ period: PlanFeaturePeriod | null;
199
+ /**
200
+ * The grant rendered server-side as an es-AR noun phrase, ready to print:
201
+ * `Ilimitado` for `-1`, `1 usuario`, `Hasta 2.000 pedidos por mes`,
202
+ * `Incluido` / `No incluido` for a boolean. Display copy only — compare
203
+ * grants through `enabled` / `limit`, never by parsing this.
204
+ */
205
+ valueText: string;
167
206
  }
168
207
  /**
169
- * A sellable plan in the catalog. Aligned with the BE wire format from
170
- * `GET /subscription/plans`. Source of truth lives in
171
- * DynamoDB (`PLAN#{tier}` partition), administered via `POST /sa/plans`
172
- * + `PATCH /sa/plans/{tier}`.
208
+ * A single feature row in a Plan's `features[]` array, discriminated on
209
+ * `type`: a boolean row carries `enabled` and no `limit` key; a
210
+ * numeric/metered row carries `limit` and no `enabled` key. `limit: -1` =
211
+ * unlimited.
212
+ */
213
+ type PlanFeature = (PlanFeatureBase & {
214
+ type: 'boolean';
215
+ enabled: boolean;
216
+ }) | (PlanFeatureBase & {
217
+ type: 'numeric' | 'metered';
218
+ limit: number;
219
+ });
220
+ /**
221
+ * How a billing cycle of a plan can be bought:
222
+ *
223
+ * - `self` — self-serve checkout: the cycle has a charged price and the
224
+ * plan has a `currency`.
225
+ * - `sales` — the plan charges some cycle, but not this one self-serve
226
+ * (no price on it, or no `currency`); route to "Contactar ventas".
227
+ * - `none` — neither cycle is charged (the free tier); nothing to buy.
228
+ */
229
+ type PlanCheckoutMode = 'self' | 'sales' | 'none';
230
+ /**
231
+ * A sellable plan in the catalog, exactly as `GET /subscription/plans` and
232
+ * `GET /public/plans` emit it — both build it with the same catalogue
233
+ * builder, so the two endpoints return identical plans. The template fields
234
+ * (copy, prices, flags) live in DynamoDB (`PLAN#{tier}` / `SK: PLAN`),
235
+ * administered via `POST /platform/billing/plans` +
236
+ * `PATCH /platform/billing/plans/{tier}`; `features` are code, rendered at
237
+ * response time.
173
238
  *
174
239
  * Prices are integers in the `currency` smallest unit (centavos for ARS,
175
240
  * cents for USD). `null` = "Contactar ventas" (AVANZADO annual at launch
@@ -228,20 +293,62 @@ declare global {
228
293
  */
229
294
  color: string | null;
230
295
  /**
231
- * Per-feature configuration. Every `FeatureKey` appears exactly once.
232
- * Boolean features carry `enabled`; numeric/metered carry `limit`.
296
+ * Per-feature configuration. Every `FeatureKey` appears exactly once,
297
+ * whatever its `status` — a `planned` / `future` key is sent too, so
298
+ * filter on `status` to decide what to advertise. Sorted by group order,
299
+ * then `order`. Boolean features carry `enabled`; numeric/metered carry
300
+ * `limit`.
233
301
  */
234
302
  features: PlanFeature[];
235
303
  /**
236
304
  * Marketing-curated short bullets for the pricing card. Display order =
237
305
  * array order; up to 6 entries, 80 chars each. Always present — empty array
238
306
  * when unset (FE synthesizes a list from `features[]`). Lets marketing tweak
239
- * copy via `/sa/plans` without a redeploy; replaces the FE-only `BULLET_LIST_BY_TIER`.
307
+ * copy via `PATCH /platform/billing/plans/{tier}` without a redeploy;
308
+ * replaces the FE-only `BULLET_LIST_BY_TIER`.
240
309
  */
241
310
  bullets: string[];
311
+ /**
312
+ * Keys worth calling out on this tier's card: highlight-eligible keys with
313
+ * status `live` or `service` whose grant improves on the next lower
314
+ * ACTIVE tier (by `displayOrder`; an `isActive: false` tier is skipped,
315
+ * as pricing pages hide it). The lowest active tier lists its own
316
+ * eligible keys. A `planned` / `future` key never appears. Each is a
317
+ * `features[]` key.
318
+ */
319
+ highlights: FeatureKey[];
320
+ /** How each billing cycle can be bought — see {@link PlanCheckoutMode}. */
321
+ checkout: {
322
+ monthly: PlanCheckoutMode;
323
+ annual: PlanCheckoutMode;
324
+ };
325
+ /** Whether a subscription on this tier can be cancelled: `true` exactly when at least one cycle is charged. */
326
+ cancellable: boolean;
242
327
  }
243
328
  /**
244
- * One audit row per SUPER_ADMIN-driven plan mutation, returned by
329
+ * `data` of `GET /public/plans` and `GET /subscription/plans` — both return
330
+ * the same `{ plans, groups, trial }`: the plans, the feature groups their
331
+ * rows render under, and the trial a new store starts on.
332
+ */
333
+ interface PlanCatalogue {
334
+ plans: Plan[];
335
+ /** Every group a `features[].group` can name, with its label and sort position. */
336
+ groups: PlanFeatureGroup[];
337
+ /**
338
+ * What a store registered now starts on: `tier` — a paid tier — in status
339
+ * `trialing`, with `trialEndsAt` set `days` days out. That date is not yet
340
+ * enforced for a store without a payment provider. Resolved from the platform globals
341
+ * `trialTier` / `trialDays` — `profesional` / `30` when unset or invalid —
342
+ * so it is always present.
343
+ */
344
+ trial: {
345
+ tier: PaidPlanTier;
346
+ days: number;
347
+ };
348
+ }
349
+ /**
350
+ * One audit row per MANAGER-driven plan mutation (`POST` / `PATCH
351
+ * /platform/billing/plans`, `asManager`), returned by
245
352
  * `GET /platform/billing/plans/{tier}/audit`. The store-subscription audit
246
353
  * shares this storage shape but is read as `SubscriptionAuditEntry`.
247
354
  * `before`/`after` carry only the fields that changed (diff slice).
@@ -334,17 +441,35 @@ declare global {
334
441
  /** A single usage entry as returned by GET /subscription. */
335
442
  interface SubscriptionUsageEntry {
336
443
  key: FeatureKey;
337
- /** Period in YYYYMM format; `null` for lifetime caps (maxProducts/maxCustomers/maxUsers). */
444
+ /**
445
+ * Period in YYYYMM format for a monthly counter; `null` for a cap that never
446
+ * resets — the lifetime caps (maxProducts/maxCustomers/maxUsers) and the
447
+ * row-counted ones (webhooks, maxWarehouses: active rows, as their caps count).
448
+ */
338
449
  period: string | null;
339
450
  current: number;
340
- /** `null` for unlimited tiers (no cap). */
451
+ /**
452
+ * The resolved ceiling: `-1` is the unlimited sentinel, and `null` means
453
+ * the plan declares no ceiling for this key.
454
+ */
341
455
  limit: number | null;
342
- /** `null` when `limit` is unlimited/uncapped. */
456
+ /** `limit - current`, floored at zero; `null` whenever `limit` is `-1` or `null`. */
343
457
  remaining: number | null;
344
458
  }
345
459
  /**
346
- * Full subscription snapshot pushed to the frontend on subscription/entitlement
347
- * changes. Flat shape — matches GET /subscription wire format directly.
460
+ * Full subscription snapshot. Three producers emit it:
461
+ *
462
+ * - `GET /subscription` — the full `usage[]`, including the row-counted
463
+ * `webhooks` / `maxWarehouses` meters. The two are present together or
464
+ * absent together: both are absent when the row count failed in this pass,
465
+ * and absent is not zero;
466
+ * - the `GET /store` subscription embed — WITHOUT those two meters (monthly
467
+ * and lifetime only) and without `provider`;
468
+ * - the reply of the MANAGER `PUT /platform/stores/{storeId}/subscription` —
469
+ * built by the same assembly as `GET /subscription`, meters included.
470
+ *
471
+ * The WebSocket only nudges a refetch on subscription/entitlement changes; it
472
+ * never carries this payload.
348
473
  */
349
474
  interface SubscriptionSyncPayload {
350
475
  planTier: PlanTier;
@@ -648,3 +773,36 @@ declare global {
648
773
  }
649
774
  export type BillingProviderName = SubscriptionProvider;
650
775
  export type InvoiceSummary = globalThis.InvoiceSummary;
776
+ /** Every plan tier, in rank order (lowest first). */
777
+ export declare const PLAN_TIERS: readonly ["basico", "emprendedor", "profesional", "avanzado"];
778
+ /**
779
+ * Every paid tier, in rank order (lowest first) — `PLAN_TIERS` without the free
780
+ * `basico`. The one list of "tiers that bill": the trial-tier vocabulary, the
781
+ * grace sweep's tier fan-out and the provider plan seeders all read it.
782
+ */
783
+ export declare const PAID_PLAN_TIERS: readonly ["emprendedor", "profesional", "avanzado"];
784
+ /** Whether `value` names a paid tier — narrows an untyped read (a stored row, a global) to `PaidPlanTier`. */
785
+ export declare const isPaidPlanTier: (value: unknown) => value is PaidPlanTier;
786
+ /**
787
+ * Every gated feature key.
788
+ *
789
+ * ⚠️ Adding a member here is not the whole change, and `typecheck` is what
790
+ * stops it shipping half-done. In the api, a new key needs a grant in **every
791
+ * tier** of `PLAN_FEATURES` (`stacks/services/planFeatureMatrix.ts`): that
792
+ * constant is typed total over every `PlanTier` × `FeatureKey` cell, so a tier
793
+ * missing the new key's grant, or a grant of the wrong shape for the key's
794
+ * declared `type`, fails `typecheck` at the `PLAN_FEATURES` declaration
795
+ * itself. It also widens the api's per-store override key schema, so a new key
796
+ * immediately becomes a valid override target.
797
+ *
798
+ * ⚠️ **A new member also obliges an api PLATFORM-STACK deploy.** Entitlements
799
+ * resolve inside the Lambda authorizer, built in `PlatformStack`. Deploying only
800
+ * the handler stacks leaves a STALE authorizer running the previous matrix: the
801
+ * key is absent from the resolved map, where a boolean gate refuses but a
802
+ * metered / lifetime cap PASSES THROUGH — a new cap silently unbounded.
803
+ * Checklist: `docs/PLANS_AND_ENTITLEMENTS.md` in the api repo.
804
+ */
805
+ export declare const FEATURE_KEYS: readonly ["afipInvoicing", "suppliers", "advancedReports", "cashManagement", "importExport", "domesticPayments", "stripePayments", "whatsappCommerce", "aiFeatures", "mobileApp", "customBranding", "customDomain", "prioritySupport", "advancedPricing", "marketplaceChannels", "storefront", "loyalty", "marketing", "mandatory2fa", "maxUsers", "maxProducts", "maxCustomers", "maxWarehouses", "priceListsMax", "webhooks", "maxOrdersMonth", "maxInvoicesMonth", "whatsappMessages"];
806
+ type AssertNoUncoveredFeatureKey<T extends never> = T;
807
+ export type UncoveredFeatureKeys = AssertNoUncoveredFeatureKey<Exclude<FeatureKey, (typeof FEATURE_KEYS)[number]>>;
808
+ export {};
@@ -7,8 +7,11 @@
7
7
  *
8
8
  * Notes:
9
9
  * - Tier names are the 4 locked Spanish tiers (per SUBSCRIPTION_TIERS_BEST_PRACTICES §0):
10
- * BÁSICO, EMPRENDEDOR, PROFESIONAL, AVANZADO. The launch policy
11
- * (ADR-0010) gives every new paid subscription a 30-day Stripe trial; courtesy
10
+ * BÁSICO, EMPRENDEDOR, PROFESIONAL, AVANZADO. A new store starts trialing on
11
+ * the `trialTier` platform global, with `trialEndsAt` set `trialDays` days out
12
+ * (see `PlanCatalogue.trial`); that date is not yet enforced for a store
13
+ * without a payment provider. A Stripe checkout adds its own fixed 30-day
14
+ * trial, which does not follow those globals (ADR-0010). Courtesy
12
15
  * gifts (formerly the Founders cohort) are now a one-off ops action via
13
16
  * `gift-subscription` that sets `freeUntil` on the SUBSCRIPTION row.
14
17
  * - `freeUntil` lives on every Subscription independent of status. It is the
@@ -22,4 +25,79 @@
22
25
  * declared here even when their epics haven't shipped — the matrix can set
23
26
  * enabled:false until they do.
24
27
  */
25
- export {};
28
+ /*
29
+ * Runtime tuples for the unions above.
30
+ *
31
+ * A union alone cannot be iterated, and any consumer that needs to enumerate
32
+ * every tier or key — zeroing a breakdown, fanning a query across tiers,
33
+ * rendering a pricing table's rows — otherwise hand-rolls its own copy that
34
+ * compiles fine while silently omitting a member. A missing member is not a
35
+ * visible break: it is a bucket that reads zero forever, or a row that never
36
+ * renders.
37
+ *
38
+ * ⚠️ `satisfies readonly PlanTier[]` alone does NOT catch that — it checks each
39
+ * element IS a tier, never that every tier is present. Hence `exhaustiveTuple`,
40
+ * whose second type parameter fails to resolve unless the tuple covers the
41
+ * union, making an omission a compile error at the tuple itself. Module-private
42
+ * on purpose: it is a type-level trick, not part of the published surface.
43
+ */
44
+ const exhaustiveTuple = () => (tuple) => tuple;
45
+ /** Every plan tier, in rank order (lowest first). */
46
+ export const PLAN_TIERS = exhaustiveTuple()(['basico', 'emprendedor', 'profesional', 'avanzado']);
47
+ /**
48
+ * Every paid tier, in rank order (lowest first) — `PLAN_TIERS` without the free
49
+ * `basico`. The one list of "tiers that bill": the trial-tier vocabulary, the
50
+ * grace sweep's tier fan-out and the provider plan seeders all read it.
51
+ */
52
+ export const PAID_PLAN_TIERS = exhaustiveTuple()(['emprendedor', 'profesional', 'avanzado']);
53
+ /** Whether `value` names a paid tier — narrows an untyped read (a stored row, a global) to `PaidPlanTier`. */
54
+ export const isPaidPlanTier = (value) => typeof value === 'string' && PAID_PLAN_TIERS.includes(value);
55
+ /**
56
+ * Every gated feature key.
57
+ *
58
+ * ⚠️ Adding a member here is not the whole change, and `typecheck` is what
59
+ * stops it shipping half-done. In the api, a new key needs a grant in **every
60
+ * tier** of `PLAN_FEATURES` (`stacks/services/planFeatureMatrix.ts`): that
61
+ * constant is typed total over every `PlanTier` × `FeatureKey` cell, so a tier
62
+ * missing the new key's grant, or a grant of the wrong shape for the key's
63
+ * declared `type`, fails `typecheck` at the `PLAN_FEATURES` declaration
64
+ * itself. It also widens the api's per-store override key schema, so a new key
65
+ * immediately becomes a valid override target.
66
+ *
67
+ * ⚠️ **A new member also obliges an api PLATFORM-STACK deploy.** Entitlements
68
+ * resolve inside the Lambda authorizer, built in `PlatformStack`. Deploying only
69
+ * the handler stacks leaves a STALE authorizer running the previous matrix: the
70
+ * key is absent from the resolved map, where a boolean gate refuses but a
71
+ * metered / lifetime cap PASSES THROUGH — a new cap silently unbounded.
72
+ * Checklist: `docs/PLANS_AND_ENTITLEMENTS.md` in the api repo.
73
+ */
74
+ export const FEATURE_KEYS = [
75
+ 'afipInvoicing',
76
+ 'suppliers',
77
+ 'advancedReports',
78
+ 'cashManagement',
79
+ 'importExport',
80
+ 'domesticPayments',
81
+ 'stripePayments',
82
+ 'whatsappCommerce',
83
+ 'aiFeatures',
84
+ 'mobileApp',
85
+ 'customBranding',
86
+ 'customDomain',
87
+ 'prioritySupport',
88
+ 'advancedPricing',
89
+ 'marketplaceChannels',
90
+ 'storefront',
91
+ 'loyalty',
92
+ 'marketing',
93
+ 'mandatory2fa',
94
+ 'maxUsers',
95
+ 'maxProducts',
96
+ 'maxCustomers',
97
+ 'maxWarehouses',
98
+ 'priceListsMax',
99
+ 'webhooks',
100
+ 'maxOrdersMonth',
101
+ 'maxInvoicesMonth',
102
+ 'whatsappMessages',
103
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platform-types",
3
- "version": "1.10.700",
3
+ "version": "1.10.702",
4
4
  "description": "Shared wire and entity contracts for the platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",