sinfactura-types 1.10.261 → 1.10.263

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.
package/dist/auth.d.ts CHANGED
@@ -280,8 +280,16 @@ declare global {
280
280
  * - `wrong_totp` — the step-up code was wrong.
281
281
  * - `totp_replay` — an already-used code was presented again and rejected.
282
282
  * - `totp_locked` — brute-force lockout active on the TOTP leg.
283
+ * - `enrollment_required` — the credentials were correct and the store's
284
+ * mandatory-2FA grace window has closed on a user who never enrolled. Like
285
+ * `requires_2fa` this is NOT a completed login, but it is the opposite
286
+ * diagnosis: `requires_2fa` means somebody stalled at a factor they HAVE,
287
+ * this means they have none to present. ⚠️ Emitted on OPERATOR logins only
288
+ * — the mandate is scoped to operator surfaces, and there is no customer
289
+ * enrolment endpoint for a web mandate to be satisfiable against, so a
290
+ * `kind: 'CUSTOMER'` row can never carry it.
283
291
  */
284
- type LoginOutcome = 'success' | 'wrong_password' | 'disabled' | 'blocked' | 'requires_2fa' | 'wrong_totp' | 'totp_replay' | 'totp_locked';
292
+ type LoginOutcome = 'success' | 'wrong_password' | 'disabled' | 'blocked' | 'requires_2fa' | 'wrong_totp' | 'totp_replay' | 'totp_locked' | 'enrollment_required';
285
293
  /**
286
294
  * One row of `GET /auth?mode=login-history[&days=N]` — a storefront
287
295
  * customer's own login audit trail. The stored row reaches the wire
@@ -191,6 +191,56 @@ declare global {
191
191
  lastReminderAt?: number;
192
192
  marketing?: CustomerMarketing;
193
193
  minBuy?: number;
194
+ /**
195
+ * How many appointments this customer has been marked `NO_SHOW` for, so a
196
+ * booking-time policy can be evaluated SERVER-SIDE against a durable number
197
+ * instead of against whatever date window a browser happened to read. Two
198
+ * operators with different date filters derive different counts for the same
199
+ * customer; this field is the one both of them share.
200
+ *
201
+ * ⚠️ **NOT A HISTORICAL TOTAL — it counts from the day it shipped.** This
202
+ * repo is forward-only and never backfills, so a customer with years of
203
+ * prior no-shows starts ABSENT and reads exactly like one who has never
204
+ * missed an appointment. A policy that auto-flags at 4+ will treat a
205
+ * long-standing offender as clean until they accumulate four MORE. That is a
206
+ * known consequence, not an oversight: what to do about it (a grace window,
207
+ * an operator override, showing the client-derived range count beside this
208
+ * one) is the app's decision — but it must not be a surprise.
209
+ *
210
+ * ⚠️ **Absence means "nothing recorded since this shipped"**, never "never
211
+ * no-showed". Coerce with `noShowCount ?? 0` for arithmetic, but do not
212
+ * render that fallback as a measured zero.
213
+ *
214
+ * ⚠️ **A LOWER BOUND.** The bump is issued after the appointment's own
215
+ * status write has already committed, and is deliberately best-effort: an
216
+ * appointment must still be markable `NO_SHOW` when the customer row has
217
+ * since been deleted. A lost bump undercounts by one and can never
218
+ * overcount — so a threshold that has been crossed was really crossed.
219
+ *
220
+ * ⚠️ **MONOTONIC, because `NO_SHOW` is TERMINAL — there is no decrement
221
+ * path.** `stacks/services/appointments.ts`'s transition table gives
222
+ * `NO_SHOW` an empty successor list, and the two writers that could move a
223
+ * terminal row (`cancelAppointment`, `rescheduleAppointment`) both condition
224
+ * on the status still being one of the four non-terminal ones. A booking
225
+ * marked `NO_SHOW` in error is corrected by creating a new appointment, not
226
+ * by reverting the old one. If a revert is ever added it owes a
227
+ * floor-guarded decrement in the SAME change, and the app must stop
228
+ * treating this as monotonic on the same day.
229
+ *
230
+ * ⚠️ **Written by an atomic `ADD noShowCount :one`**, like the `ADD balance`
231
+ * deltas on this same row. Never read-modify-write it: two operators
232
+ * clearing the same morning's board would both read the same value and one
233
+ * of the two events would vanish silently.
234
+ *
235
+ * ⚠️ Deliberately absent from `CUSTOMER_LIST_DISPLAY_FIELDS`, and therefore
236
+ * from `CUSTOMER_OPERATOR_BROADCAST_FIELDS`, which is DEFINED as that list
237
+ * plus frame mechanics. Behavioural data about a named person (Ley 25.326)
238
+ * does not belong on a store-wide list served to every staff-tier token, and
239
+ * a customer must not receive their own tally on a socket. It is served by
240
+ * the capability-gated `?customerId=` point read — the surface a booking
241
+ * screen already uses.
242
+ */
243
+ noShowCount?: number;
194
244
  /**
195
245
  * Unsettled debits denormalised onto this row so the accounts report can
196
246
  * bucket them by age without a per-customer query — the same
package/dist/order.d.ts CHANGED
@@ -97,8 +97,58 @@ declare global {
97
97
  * drift. The APPOINTMENT is authoritative: it is the row the scheduler
98
98
  * writes under a conditional update, and a stale pointer here must be
99
99
  * treated as absent rather than followed.
100
+ *
101
+ * ⚠️ **This field, {@link Order.appointmentDate} and
102
+ * {@link Order.appointmentTime} are ONE stamp.** Every writer sets all
103
+ * three in a single `UpdateExpression` on this row (`SET` when a booking
104
+ * is made or moved, `REMOVE` when it is cancelled), so they cannot drift
105
+ * apart from ONE ANOTHER: all three present, or all three absent — the
106
+ * one exception being a row written before the date/time existed, which
107
+ * carries this pointer alone and is never backfilled. What can still lag
108
+ * is the stamp AS A WHOLE, because the write follows an already-committed
109
+ * appointment and is best-effort; that is the drift the paragraph above
110
+ * is about, and it is why the appointment stays authoritative.
100
111
  */
101
112
  appointmentId?: string;
113
+ /**
114
+ * The booked slot's LOCAL date, `YYYYMMDD` in the store's (Buenos Aires)
115
+ * time — a copy of `Appointment.date`, denormalised so a page of orders
116
+ * can render, sort and filter a «Turno» column without reading the
117
+ * appointments table at all. The three alternatives it replaces are each
118
+ * wrong in a different way: a range read over appointments misses any
119
+ * booking outside the window, and renders "sin turno" on exactly the
120
+ * orders an operator is hunting for; a per-row lookup is N+1 with no
121
+ * `orderId` filter to collapse it; and neither can sort a grid by a key
122
+ * that is not on the row.
123
+ *
124
+ * ⚠️ **NOT a Unix timestamp.** `20260315` is the fifteenth of March, not
125
+ * an instant. It copies the AUTHORITATIVE local pair the appointment row
126
+ * is keyed by (`${date}#${startTime}#${appointmentId}`) rather than the
127
+ * derived `Appointment.startTimestamp`, so this row and that key can
128
+ * never denote different slots.
129
+ *
130
+ * ⚠️ **Absent means "nothing to render", and that includes an order
131
+ * written before this field existed.** The platform is forward-only and
132
+ * nothing backfills, so a row may carry {@link Order.appointmentId} with
133
+ * no date or time: render no turno rather than inventing one, and follow
134
+ * the pointer only from a detail view that can afford the read. The next
135
+ * booking or reschedule on such an order stamps all three.
136
+ *
137
+ * ⚠️ A COPY, for display and ordering. The APPOINTMENT stays
138
+ * authoritative for whether the slot is still live — a cancel clears the
139
+ * stamp, but a clear that failed leaves it stale, so nothing that charges
140
+ * money or promises a customer a time may read this without the
141
+ * appointment.
142
+ */
143
+ appointmentDate?: number;
144
+ /**
145
+ * The booked slot's LOCAL start time, `HHmm` — a copy of
146
+ * `Appointment.startTime`, written and cleared by the same single update
147
+ * as {@link Order.appointmentDate} and under exactly the same rules:
148
+ * absent means nothing to render, and the appointment remains
149
+ * authoritative for whether the booking still stands.
150
+ */
151
+ appointmentTime?: string;
102
152
  /**
103
153
  * OPTIONAL. The eleven readers that forced this to be required have all
104
154
  * been closed, and the widening was gated on a measured zero-error
package/dist/store.d.ts CHANGED
@@ -499,6 +499,55 @@ declare global {
499
499
  */
500
500
  nextSequence?: number;
501
501
  };
502
+ /**
503
+ * Per-store mandatory-2FA obligation for OPERATOR logins.
504
+ *
505
+ * ⚠️ **It lives on the store row and NOT in the plan entitlement map,
506
+ * and that placement is the whole design.** The `mandatory2fa` FeatureKey
507
+ * gates which TIERS may impose the obligation; this leaf records whether a
508
+ * store actually has. Every other FeatureKey GRANTS a capability, so losing
509
+ * it withdraws something. This one OBLIGES — so resolving it from the
510
+ * entitlement map would mean `graceSweep` silently UN-mandating 2FA when it
511
+ * writes `planTier: 'basico'` on a delinquent store at day 14. A security
512
+ * posture does not lapse because an invoice did.
513
+ *
514
+ * Verified rather than assumed: `graceSweep` writes only `status`,
515
+ * `planTier`, `canceledFromTier`, `canceledAt` and `updatedAt`, and writes
516
+ * them to the SUBSCRIPTION row — it touches neither `totp` nor store
517
+ * config. So an obligation kept here survives the downgrade **by
518
+ * construction**, with no special case for anyone to remember and nothing
519
+ * to re-check when the sweep changes.
520
+ *
521
+ * Forward-only: absent means no obligation, and nothing backfills it.
522
+ */
523
+ mandatory2fa?: {
524
+ /** Whether operator logins for this store require an enrolled second factor. */
525
+ enabled: boolean;
526
+ /**
527
+ * ms-epoch the obligation was last turned ON. The grace window runs from
528
+ * here.
529
+ *
530
+ * ⚠️ BE-stamped, never client-writable. A client that could set this
531
+ * could set it far enough in the past to close the window on every
532
+ * operator at once — which is a lockout, not a configuration change.
533
+ */
534
+ since: number;
535
+ /**
536
+ * Days after `since` during which an unenrolled operator still receives a
537
+ * full session — flagged `mustEnroll2fa` — rather than being refused.
538
+ *
539
+ * ⚠️ **The window is what makes the mandate satisfiable at all, not a
540
+ * courtesy.** `/auth/2fa/enroll` and `/auth/2fa/verify-enrollment` both sit
541
+ * behind the `userToken` authorizer, and the shipped step-up challenge is
542
+ * stateless — it mints no partial token, the client just re-submits the
543
+ * credential. So refusing to mint a token leaves an unenrolled operator
544
+ * with no way to reach the endpoint that would let them comply: the
545
+ * mandate would brick its own remedy. Enrolment happens inside the window;
546
+ * once it closes the refusal is safe, because anyone who missed it is
547
+ * recovered by an ADMIN toggling `enabled` back off.
548
+ */
549
+ graceDays: number;
550
+ };
502
551
  };
503
552
  ecommerce?: Ecommerce;
504
553
  photoURL: string;
@@ -83,7 +83,7 @@ declare global {
83
83
  * quietly missing a feature), but it means the caller must be updated in the
84
84
  * same change. Adding a key is never only additive downstream.
85
85
  */
86
- type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxStores' | 'priceListsMax' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'multiStore' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'whatsappCommerce' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing';
86
+ type FeatureKey = 'maxOrdersMonth' | 'maxInvoicesMonth' | 'maxProducts' | 'maxUsers' | 'maxCustomers' | 'maxStores' | 'priceListsMax' | 'afipInvoicing' | 'suppliers' | 'domesticPayments' | 'stripePayments' | 'cashManagement' | 'multiStore' | 'importExport' | 'advancedReports' | 'customBranding' | 'prioritySupport' | 'whatsappCommerce' | 'aiFeatures' | 'mobileApp' | 'customDomain' | 'advancedPricing' | 'marketplaceChannels' | 'storefront' | 'loyalty' | 'marketing' | 'mandatory2fa';
87
87
  /** Resolved entitlements for a specific tenant (matrix + overrides applied). */
88
88
  type ResolvedEntitlements = Record<FeatureKey, Entitlement>;
89
89
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinfactura-types",
3
- "version": "1.10.261",
3
+ "version": "1.10.263",
4
4
  "description": "Shared wire and entity contracts for the SINFACTURA platform.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",