@fiado/type-kit 3.638.0 → 3.640.0

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.
Files changed (43) hide show
  1. package/_test_/unit/transferNote/transferNoteConfigAndPrivate.test.ts +53 -0
  2. package/_test_/unit/transferNote/transferNoteEventsAndFeeds.test.ts +32 -0
  3. package/_test_/unit/transferNote/transferNoteRequests.test.ts +20 -1
  4. package/bin/retailCommission/dtos/CommissionAccrual.d.ts +2 -0
  5. package/bin/retailWizard/dtos/Sale.d.ts +4 -0
  6. package/bin/retailWizard/events/RetailCommissionInboundMessage.d.ts +5 -0
  7. package/bin/retailWizard/events/RetailCommissionInboundMessage.js +5 -0
  8. package/bin/transferNote/constants/transferNoteErrorHttpStatus.js +4 -0
  9. package/bin/transferNote/dtos/TransferNote.d.ts +10 -0
  10. package/bin/transferNote/dtos/TransferNoteConfigRecords.d.ts +14 -0
  11. package/bin/transferNote/dtos/requests/TransferNoteActionRequests.d.ts +3 -1
  12. package/bin/transferNote/dtos/requests/TransferNoteActionRequests.js +8 -2
  13. package/bin/transferNote/dtos/requests/TransferNoteConfigRequests.d.ts +11 -1
  14. package/bin/transferNote/dtos/requests/TransferNoteConfigRequests.js +20 -2
  15. package/bin/transferNote/dtos/responses/TransferNoteConfigResponses.d.ts +1 -0
  16. package/bin/transferNote/enums/TransferNoteConfigChangeDecisionEnum.d.ts +5 -0
  17. package/bin/transferNote/enums/TransferNoteConfigChangeDecisionEnum.js +9 -0
  18. package/bin/transferNote/enums/TransferNoteErrorCodeEnum.d.ts +9 -1
  19. package/bin/transferNote/enums/TransferNoteErrorCodeEnum.js +8 -0
  20. package/bin/transferNote/events/TransferNoteLifecycleEventDetails.d.ts +38 -0
  21. package/bin/transferNote/events/TransferNoteLifecycleEventDetails.js +28 -0
  22. package/bin/transferNote/events/TransferNoteReconciledEventDetail.d.ts +6 -1
  23. package/bin/transferNote/events/TransferNoteReconciledEventDetail.js +5 -0
  24. package/bin/transferNote/index.d.ts +3 -0
  25. package/bin/transferNote/index.js +3 -0
  26. package/bin/transferNote/validators/IsTransferNoteClabeConstraint.d.ts +8 -0
  27. package/bin/transferNote/validators/IsTransferNoteClabeConstraint.js +34 -0
  28. package/package.json +1 -1
  29. package/src/retailCommission/dtos/CommissionAccrual.ts +2 -0
  30. package/src/retailWizard/dtos/Sale.ts +4 -0
  31. package/src/retailWizard/events/RetailCommissionInboundMessage.ts +8 -0
  32. package/src/transferNote/constants/transferNoteErrorHttpStatus.ts +4 -0
  33. package/src/transferNote/dtos/TransferNote.ts +11 -0
  34. package/src/transferNote/dtos/TransferNoteConfigRecords.ts +15 -0
  35. package/src/transferNote/dtos/requests/TransferNoteActionRequests.ts +8 -2
  36. package/src/transferNote/dtos/requests/TransferNoteConfigRequests.ts +17 -1
  37. package/src/transferNote/dtos/responses/TransferNoteConfigResponses.ts +2 -0
  38. package/src/transferNote/enums/TransferNoteConfigChangeDecisionEnum.ts +5 -0
  39. package/src/transferNote/enums/TransferNoteErrorCodeEnum.ts +8 -0
  40. package/src/transferNote/events/TransferNoteLifecycleEventDetails.ts +51 -0
  41. package/src/transferNote/events/TransferNoteReconciledEventDetail.ts +5 -0
  42. package/src/transferNote/index.ts +3 -0
  43. package/src/transferNote/validators/IsTransferNoteClabeConstraint.ts +24 -0
@@ -113,8 +113,12 @@ describe('espejo privado', () => {
113
113
  trackingKey: 'K1',
114
114
  paidAt: '2026-10-29T15:00:00.000Z',
115
115
  amountCents: 100,
116
+ paidToClabe: '002010077777777771',
116
117
  }),
117
118
  ).toEqual([]);
119
+ expect(
120
+ errorsOf(PrivateCaptureTransferNoteSpeiRequest, { ...caller, trackingKey: 'K1', paidAt: '2026-10-29T15:00:00.000Z', amountCents: 100 }),
121
+ ).toContain('paidToClabe');
118
122
  });
119
123
 
120
124
  it('las lecturas privadas también exigen author y actorId', () => {
@@ -215,3 +219,52 @@ describe('catálogos', () => {
215
219
  expect(TRANSFER_NOTE_ERROR_HTTP_STATUS[TransferNoteErrorCodeEnum.TRANSFER_NOTE_ITEM_NOT_FOUND]).toBe(404);
216
220
  });
217
221
  });
222
+
223
+ describe('cambio de configuración con doble control', () => {
224
+ it('la decisión acepta APPROVE o REJECT y nada más', () => {
225
+ const { DecideTransferNoteConfigChangeRequest, TransferNoteConfigChangeDecisionEnum } = TransferNote;
226
+ const proposedAt = '2026-11-04T18:00:00.000Z';
227
+ expect(errorsOf(DecideTransferNoteConfigChangeRequest, { decision: TransferNoteConfigChangeDecisionEnum.APPROVE, proposedAt })).toEqual([]);
228
+ expect(errorsOf(DecideTransferNoteConfigChangeRequest, { decision: 'MAYBE', proposedAt })).toContain('decision');
229
+ expect(errorsOf(DecideTransferNoteConfigChangeRequest, { decision: TransferNoteConfigChangeDecisionEnum.APPROVE })).toContain('proposedAt');
230
+ expect(errorsOf(DecideTransferNoteConfigChangeRequest, { decision: TransferNoteConfigChangeDecisionEnum.REJECT, proposedAt: 'ayer' })).toContain('proposedAt');
231
+ });
232
+
233
+ it('los codes nuevos tienen su status', () => {
234
+ expect(TRANSFER_NOTE_ERROR_HTTP_STATUS[TransferNoteErrorCodeEnum.SAME_ACTOR_NOT_ALLOWED]).toBe(422);
235
+ expect(TRANSFER_NOTE_ERROR_HTTP_STATUS[TransferNoteErrorCodeEnum.CONFIG_CHANGE_NOT_PENDING]).toBe(409);
236
+ expect(TRANSFER_NOTE_ERROR_HTTP_STATUS[TransferNoteErrorCodeEnum.NOTE_DOCUMENT_NOT_READY]).toBe(409);
237
+ expect(TRANSFER_NOTE_ERROR_HTTP_STATUS[TransferNoteErrorCodeEnum.PARTY_ROUTING_CONFLICT]).toBe(409);
238
+ });
239
+
240
+ it('el registro expone el cambio propuesto aparte del vigente', () => {
241
+ const record: TransferNote.TransferNoteConfigRecord = {
242
+ recordKey: 'PARTY#luga',
243
+ recordType: TransferNoteConfigRecordTypeEnum.PARTY,
244
+ party: { partyId: 'luga', partyType: TransferNotePartyTypeEnum.RETAILER, displayName: 'Luga', retailerId: 'r-1', clabe: '002010077777777771' },
245
+ pendingChange: {
246
+ party: { partyId: 'luga', partyType: TransferNotePartyTypeEnum.RETAILER, displayName: 'Luga', retailerId: 'r-1', clabe: '012180001234567897' },
247
+ proposedBy: 'ana@sk.mx',
248
+ proposedAt: '2026-11-04T10:00:00.000Z',
249
+ },
250
+ updatedAt: '2026-11-04T10:00:00.000Z',
251
+ updatedBy: 'ana@sk.mx',
252
+ };
253
+ expect(record.party?.clabe).toBe('002010077777777771');
254
+ });
255
+
256
+ it('un cambio de pagador, receptor o signo de un concepto también queda propuesto', () => {
257
+ const pendingChange: TransferNote.TransferNoteConfigPendingChange = {
258
+ concept: {
259
+ concept: TransferNoteConceptEnum.DEVICE_PAYMENT,
260
+ payerPartyType: TransferNotePartyTypeEnum.SOFOM,
261
+ payeePartyType: TransferNotePartyTypeEnum.RETAILER,
262
+ sign: TransferNoteConceptSignEnum.NEGATIVE,
263
+ activeFrom: '2026-11-04T10:00:00.000Z',
264
+ },
265
+ proposedBy: 'ana@sk.mx',
266
+ proposedAt: '2026-11-04T10:00:00.000Z',
267
+ };
268
+ expect(pendingChange.concept?.sign).toBe(TransferNoteConceptSignEnum.NEGATIVE);
269
+ });
270
+ });
@@ -36,6 +36,38 @@ describe('eventos de transferNote', () => {
36
36
  });
37
37
  });
38
38
 
39
+ describe('los 7 eventos de la nota', () => {
40
+ it('cada detailType vive bajo transferNote.*', () => {
41
+ expect(Object.values(TransferNote.TransferNoteEventDetailTypeEnum).sort()).toEqual([
42
+ 'transferNote.TransferNoteCancelled',
43
+ 'transferNote.TransferNoteDisputeResolved',
44
+ 'transferNote.TransferNoteDisputed',
45
+ 'transferNote.TransferNoteIssued',
46
+ 'transferNote.TransferNoteReconciled',
47
+ 'transferNote.TransferNoteSpeiCaptured',
48
+ 'transferNote.TransferNoteSpeiReverted',
49
+ ]);
50
+ });
51
+
52
+ it('la captura lleva la CLABE pagada y solo ids y montos', () => {
53
+ const detail: TransferNote.TransferNoteSpeiCapturedEventDetail = {
54
+ eventId: 'n-1#SPEI_CAPTURED#1762250400000',
55
+ tenantId: 'surekeep',
56
+ noteId: 'n-1',
57
+ pairKey: 'fsa#sk#-',
58
+ isoWeek: '2026-W44',
59
+ netAmountCents: 125000,
60
+ currency: CurrencyId.MXN,
61
+ trackingKey: 'BNET01002610290000001',
62
+ paidAt: '2026-11-04T10:00:00.000Z',
63
+ paidAmountCents: 125000,
64
+ paidToClabe: '002010077777777771',
65
+ capturedAt: '2026-11-04T10:00:00.000Z',
66
+ };
67
+ expect(detail.paidToClabe).toHaveLength(18);
68
+ });
69
+ });
70
+
39
71
  describe('registros de feed de notas con la forma base', () => {
40
72
  it('un item de nota se lee con el cliente genérico y su reversa va en refs', () => {
41
73
  const reversal: TransferNote.TransferNoteItemFeedRecord = {
@@ -72,7 +72,7 @@ describe('PreviewTransferNoteRequest', () => {
72
72
  });
73
73
 
74
74
  describe('captura y reversa de SPEI', () => {
75
- const spei = { trackingKey: 'BNET01002610290000001', paidAt: '2026-10-29T15:00:00.000Z', amountCents: 125000 };
75
+ const spei = { trackingKey: 'BNET01002610290000001', paidAt: '2026-10-29T15:00:00.000Z', amountCents: 125000, paidToClabe: '002010077777777771' };
76
76
 
77
77
  it('acepta clave, fecha ISO y monto entero positivo', () => {
78
78
  expect(errorsOf(CaptureTransferNoteSpeiRequest, spei)).toEqual([]);
@@ -90,6 +90,25 @@ describe('captura y reversa de SPEI', () => {
90
90
  expect(errorsOf(RevertTransferNoteSpeiRequest, { reason: ' ' })).toContain('reason');
91
91
  expect(errorsOf(CancelTransferNoteRequest, {})).toContain('reason');
92
92
  });
93
+
94
+ it('la cancelación pide razón de 20+ como emitir u objetar', () => {
95
+ expect(errorsOf(CancelTransferNoteRequest, { reason: 'corta' })).toContain('reason');
96
+ expect(errorsOf(CancelTransferNoteRequest, { reason: longReason })).toEqual([]);
97
+ });
98
+
99
+ it('paidToClabe es obligatoria en toda captura', () => {
100
+ const { paidToClabe: _omit, ...withoutClabe } = spei;
101
+ expect(errorsOf(CaptureTransferNoteSpeiRequest, withoutClabe)).toContain('paidToClabe');
102
+ expect(errorsOf(CaptureTransferNoteSpeiRequest, { ...spei, paidToClabe: null })).toContain('paidToClabe');
103
+ });
104
+
105
+ it('paidToClabe exige dígito verificador válido', () => {
106
+ expect(errorsOf(CaptureTransferNoteSpeiRequest, { ...spei, paidToClabe: '002010077777777771' })).toEqual([]);
107
+ expect(errorsOf(CaptureTransferNoteSpeiRequest, { ...spei, paidToClabe: '002010077777777772' })).toContain('paidToClabe');
108
+ expect(errorsOf(CaptureTransferNoteSpeiRequest, { ...spei, paidToClabe: '12345' })).toContain('paidToClabe');
109
+ expect(TransferNote.isValidTransferNoteClabe('002010077777777771')).toBe(true);
110
+ expect(TransferNote.isValidTransferNoteClabe(null)).toBe(false);
111
+ });
93
112
  });
94
113
 
95
114
  describe('objeción', () => {
@@ -85,6 +85,8 @@ export interface CommissionSaleInput {
85
85
  /** `CASH` | `CREDIT`, para `PERCENT_BY_PAYMENT`. */
86
86
  paymentType: string;
87
87
  soldAt: string;
88
+ /** Cuándo se ganó la comisión: ubica la venta en su corte. Los devengos viejos no lo traen o lo traen en `null` (usar `soldAt`). */
89
+ earnedAt?: string | null;
88
90
  /** Las líneas de la venta. Las ventas que se calculan con reglas siempre las traen. */
89
91
  lines?: CommissionSaleLine[];
90
92
  }
@@ -97,6 +97,10 @@ export interface Sale {
97
97
  cancelledAt: string | null;
98
98
  /** Reason OBLIGATORIO en cancelación (AuditTrail) — flujo C. */
99
99
  cancelReason: string | null;
100
+ /** La comisión espera a que el cliente active su app: contraseña, contrato y número secreto. Ausente si no espera. */
101
+ commissionAwaitingApp?: boolean;
102
+ /** ISO; cuándo se ganó la comisión, si el cliente activó su app después de cerrar. Ausente: al cerrar o en espera. */
103
+ commissionEarnedAt?: string;
100
104
  /** Si la venta se puede cancelar hoy y, si no, por qué; solo lo trae el detalle del backoffice. */
101
105
  cancellation?: SaleCancellation;
102
106
  /**
@@ -46,6 +46,11 @@ export declare class RetailCommissionInboundMessage {
46
46
  soldAt: string;
47
47
  /** soldAt en la venta; cancelledAt en la cancelación. */
48
48
  occurredAt: string;
49
+ /**
50
+ * Cuándo se ganó la comisión, si el cliente activó su app después de cerrar la venta. Ubica la venta en
51
+ * su corte. Ausente: se ganó al cerrar (`soldAt`).
52
+ */
53
+ earnedAt?: string;
49
54
  /** Motivo de la cancelación; puede venir vacío. */
50
55
  cancelReason?: string;
51
56
  lines: RetailCommissionInboundLine[];
@@ -153,6 +153,11 @@ __decorate([
153
153
  (0, class_validator_1.IsISO8601)(),
154
154
  __metadata("design:type", String)
155
155
  ], RetailCommissionInboundMessage.prototype, "occurredAt", void 0);
156
+ __decorate([
157
+ (0, class_validator_1.IsOptional)(),
158
+ (0, class_validator_1.IsISO8601)(),
159
+ __metadata("design:type", String)
160
+ ], RetailCommissionInboundMessage.prototype, "earnedAt", void 0);
156
161
  __decorate([
157
162
  (0, class_validator_1.IsOptional)(),
158
163
  (0, class_validator_1.IsString)(),
@@ -34,4 +34,8 @@ exports.TRANSFER_NOTE_ERROR_HTTP_STATUS = {
34
34
  [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.CUTOFF_DISPATCH_FAILED]: 502,
35
35
  [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.CONCURRENT_WRITE_CONFLICT]: 409,
36
36
  [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.AUDIT_QUEUE_PUBLISH_FAILED]: 502,
37
+ [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.SAME_ACTOR_NOT_ALLOWED]: 422,
38
+ [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.CONFIG_CHANGE_NOT_PENDING]: 409,
39
+ [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.NOTE_DOCUMENT_NOT_READY]: 409,
40
+ [TransferNoteErrorCodeEnum_1.TransferNoteErrorCodeEnum.PARTY_ROUTING_CONFLICT]: 409,
37
41
  };
@@ -25,6 +25,8 @@ export interface TransferNoteSpeiPayment {
25
25
  capturedBy: string;
26
26
  capturedAt: string;
27
27
  receiptUploaded: boolean;
28
+ /** CLABE a la que se mandó el SPEI, si se capturó. */
29
+ paidToClabe?: string;
28
30
  }
29
31
  export interface TransferNoteConfirmation {
30
32
  type: TransferNoteConfirmationTypeEnum;
@@ -40,6 +42,12 @@ export interface TransferNoteDispute {
40
42
  /** Estado al que vuelve la nota si se rechaza la objeción. */
41
43
  previousStatus: TransferNoteStatusEnum;
42
44
  }
45
+ /** Cancelación de la nota: quién, cuándo y por qué. */
46
+ export interface TransferNoteCancellation {
47
+ reason: string;
48
+ cancelledBy: string;
49
+ cancelledAt: string;
50
+ }
43
51
  /** Fila de los listados de notas. */
44
52
  export interface TransferNoteSummary {
45
53
  noteId: string;
@@ -67,6 +75,8 @@ export interface TransferNote extends TransferNoteSummary {
67
75
  issueReason?: string;
68
76
  speiPayment?: TransferNoteSpeiPayment;
69
77
  dispute?: TransferNoteDispute;
78
+ /** Solo en notas canceladas después de emitirse. */
79
+ cancellation?: TransferNoteCancellation;
70
80
  /** Nota cancelada de la que nace esta, o la regenerada que la reemplaza. */
71
81
  regeneratedFromNoteId?: string;
72
82
  regeneratedNoteId?: string;
@@ -37,6 +37,18 @@ export interface TransferNotePairSettings {
37
37
  /** Se llena al 4.º corte seguido sin emitir. */
38
38
  discrepancyOpenedAt?: string;
39
39
  }
40
+ /**
41
+ * Cambio que altera a quién se le paga y espera la aprobación de otra persona: CLABE, tipo o tienda de una parte,
42
+ * pagador/receptor de un par, o tipos de pagador/receptor o signo de un concepto.
43
+ * Mientras no se apruebe rige el valor vigente del registro.
44
+ */
45
+ export interface TransferNoteConfigPendingChange {
46
+ party?: TransferNotePartySettings;
47
+ concept?: TransferNoteConceptSettings;
48
+ pair?: TransferNotePairSettings;
49
+ proposedBy: string;
50
+ proposedAt: string;
51
+ }
40
52
  /** Un registro de configuración; trae solo el sub-shape de su `recordType`. */
41
53
  export interface TransferNoteConfigRecord {
42
54
  recordKey: string;
@@ -45,6 +57,8 @@ export interface TransferNoteConfigRecord {
45
57
  party?: TransferNotePartySettings;
46
58
  concept?: TransferNoteConceptSettings;
47
59
  pair?: TransferNotePairSettings;
60
+ /** Cambio propuesto que todavía no rige. */
61
+ pendingChange?: TransferNoteConfigPendingChange;
48
62
  updatedAt: string;
49
63
  updatedBy: string;
50
64
  }
@@ -17,12 +17,14 @@ export declare class CaptureTransferNoteSpeiRequest {
17
17
  trackingKey: string;
18
18
  paidAt: string;
19
19
  amountCents: number;
20
+ /** CLABE a la que se mandó el SPEI, obligatoria; viaja en el feed de liquidación para compararla con la registrada del receptor. */
21
+ paidToClabe: string;
20
22
  }
21
23
  /** Body de publicRevertTransferNoteSpei. */
22
24
  export declare class RevertTransferNoteSpeiRequest {
23
25
  reason: string;
24
26
  }
25
- /** Body de publicCancelTransferNote. */
27
+ /** Body de publicCancelTransferNote; la cancelación es irreversible y exige la misma razón larga que emitir u objetar. */
26
28
  export declare class CancelTransferNoteRequest {
27
29
  reason: string;
28
30
  }
@@ -13,6 +13,7 @@ exports.ResolveTransferNoteDisputeRequest = exports.CancelTransferNoteRequest =
13
13
  const class_transformer_1 = require("class-transformer");
14
14
  const class_validator_1 = require("class-validator");
15
15
  const TransferNoteDisputeDecisionEnum_1 = require("../../enums/TransferNoteDisputeDecisionEnum");
16
+ const IsTransferNoteClabeConstraint_1 = require("../../validators/IsTransferNoteClabeConstraint");
16
17
  const IsTransferNoteSelectionConstraint_1 = require("../../validators/IsTransferNoteSelectionConstraint");
17
18
  const transferNoteRequestDecorators_1 = require("./transferNoteRequestDecorators");
18
19
  /** Body de publicPreviewTransferNote: items de un par por selección o por semana. */
@@ -73,6 +74,11 @@ __decorate([
73
74
  (0, class_validator_1.IsPositive)(),
74
75
  __metadata("design:type", Number)
75
76
  ], CaptureTransferNoteSpeiRequest.prototype, "amountCents", void 0);
77
+ __decorate([
78
+ (0, class_transformer_1.Expose)(),
79
+ (0, class_validator_1.Validate)(IsTransferNoteClabeConstraint_1.IsTransferNoteClabeConstraint),
80
+ __metadata("design:type", String)
81
+ ], CaptureTransferNoteSpeiRequest.prototype, "paidToClabe", void 0);
76
82
  /** Body de publicRevertTransferNoteSpei. */
77
83
  class RevertTransferNoteSpeiRequest {
78
84
  }
@@ -81,12 +87,12 @@ __decorate([
81
87
  (0, transferNoteRequestDecorators_1.RequiredReason)(),
82
88
  __metadata("design:type", String)
83
89
  ], RevertTransferNoteSpeiRequest.prototype, "reason", void 0);
84
- /** Body de publicCancelTransferNote. */
90
+ /** Body de publicCancelTransferNote; la cancelación es irreversible y exige la misma razón larga que emitir u objetar. */
85
91
  class CancelTransferNoteRequest {
86
92
  }
87
93
  exports.CancelTransferNoteRequest = CancelTransferNoteRequest;
88
94
  __decorate([
89
- (0, transferNoteRequestDecorators_1.RequiredReason)(),
95
+ (0, transferNoteRequestDecorators_1.LongReason)(),
90
96
  __metadata("design:type", String)
91
97
  ], CancelTransferNoteRequest.prototype, "reason", void 0);
92
98
  /** Body de publicResolveTransferNoteDispute. */
@@ -1,5 +1,6 @@
1
1
  import { CurrencyId } from '../../../currency/enums/CurrencyId';
2
2
  import { TransferNoteConceptSignEnum } from '../../enums/TransferNoteConceptSignEnum';
3
+ import { TransferNoteConfigChangeDecisionEnum } from '../../enums/TransferNoteConfigChangeDecisionEnum';
3
4
  import { TransferNoteConfigRecordTypeEnum as RecordType } from '../../enums/TransferNoteConfigRecordTypeEnum';
4
5
  import { TransferNotePartyTypeEnum } from '../../enums/TransferNotePartyTypeEnum';
5
6
  export declare class TransferNoteTenantSettingsInput {
@@ -28,7 +29,10 @@ export declare class TransferNotePairSettingsInput {
28
29
  payeePartyId: string;
29
30
  activeFrom: string;
30
31
  }
31
- /** Body de publicUpdateTransferNoteConfig; da de alta el registro si no existe. */
32
+ /**
33
+ * Body de publicUpdateTransferNoteConfig; da de alta el registro si no existe.
34
+ * Un cambio de CLABE o de pagador/receptor de un par queda propuesto hasta que otra persona lo apruebe.
35
+ */
32
36
  export declare class UpdateTransferNoteConfigRequest {
33
37
  recordType: RecordType;
34
38
  tenant?: TransferNoteTenantSettingsInput;
@@ -36,3 +40,9 @@ export declare class UpdateTransferNoteConfigRequest {
36
40
  concept?: TransferNoteConceptSettingsInput;
37
41
  pair?: TransferNotePairSettingsInput;
38
42
  }
43
+ /** Body de publicDecideTransferNoteConfigChange: aprueba o descarta el cambio propuesto; quien lo propuso no puede aprobarlo. */
44
+ export declare class DecideTransferNoteConfigChangeRequest {
45
+ decision: TransferNoteConfigChangeDecisionEnum;
46
+ /** `pendingChange.proposedAt` que vio quien decide; si la propuesta se reemplazó después, la decisión se rechaza. */
47
+ proposedAt: string;
48
+ }
@@ -9,12 +9,13 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.UpdateTransferNoteConfigRequest = exports.TransferNotePairSettingsInput = exports.TransferNoteConceptSettingsInput = exports.TransferNotePartySettingsInput = exports.TransferNoteTenantSettingsInput = void 0;
12
+ exports.DecideTransferNoteConfigChangeRequest = exports.UpdateTransferNoteConfigRequest = exports.TransferNotePairSettingsInput = exports.TransferNoteConceptSettingsInput = exports.TransferNotePartySettingsInput = exports.TransferNoteTenantSettingsInput = void 0;
13
13
  const class_transformer_1 = require("class-transformer");
14
14
  const class_validator_1 = require("class-validator");
15
15
  const CurrencyId_1 = require("../../../currency/enums/CurrencyId");
16
16
  const transferNoteValidation_1 = require("../../constants/transferNoteValidation");
17
17
  const TransferNoteConceptSignEnum_1 = require("../../enums/TransferNoteConceptSignEnum");
18
+ const TransferNoteConfigChangeDecisionEnum_1 = require("../../enums/TransferNoteConfigChangeDecisionEnum");
18
19
  const TransferNoteConfigRecordTypeEnum_1 = require("../../enums/TransferNoteConfigRecordTypeEnum");
19
20
  const TransferNotePartyTypeEnum_1 = require("../../enums/TransferNotePartyTypeEnum");
20
21
  const transferNoteRequestDecorators_1 = require("./transferNoteRequestDecorators");
@@ -109,7 +110,10 @@ __decorate([
109
110
  (0, class_validator_1.IsISO8601)(),
110
111
  __metadata("design:type", String)
111
112
  ], TransferNotePairSettingsInput.prototype, "activeFrom", void 0);
112
- /** Body de publicUpdateTransferNoteConfig; da de alta el registro si no existe. */
113
+ /**
114
+ * Body de publicUpdateTransferNoteConfig; da de alta el registro si no existe.
115
+ * Un cambio de CLABE o de pagador/receptor de un par queda propuesto hasta que otra persona lo apruebe.
116
+ */
113
117
  class UpdateTransferNoteConfigRequest {
114
118
  }
115
119
  exports.UpdateTransferNoteConfigRequest = UpdateTransferNoteConfigRequest;
@@ -150,3 +154,17 @@ __decorate([
150
154
  (0, class_transformer_1.Type)(() => TransferNotePairSettingsInput),
151
155
  __metadata("design:type", TransferNotePairSettingsInput)
152
156
  ], UpdateTransferNoteConfigRequest.prototype, "pair", void 0);
157
+ /** Body de publicDecideTransferNoteConfigChange: aprueba o descarta el cambio propuesto; quien lo propuso no puede aprobarlo. */
158
+ class DecideTransferNoteConfigChangeRequest {
159
+ }
160
+ exports.DecideTransferNoteConfigChangeRequest = DecideTransferNoteConfigChangeRequest;
161
+ __decorate([
162
+ (0, class_transformer_1.Expose)(),
163
+ (0, class_validator_1.IsEnum)(TransferNoteConfigChangeDecisionEnum_1.TransferNoteConfigChangeDecisionEnum),
164
+ __metadata("design:type", String)
165
+ ], DecideTransferNoteConfigChangeRequest.prototype, "decision", void 0);
166
+ __decorate([
167
+ (0, class_transformer_1.Expose)(),
168
+ (0, class_validator_1.IsISO8601)(),
169
+ __metadata("design:type", String)
170
+ ], DecideTransferNoteConfigChangeRequest.prototype, "proposedAt", void 0);
@@ -4,3 +4,4 @@ export interface GetTransferNoteConfigResponse {
4
4
  records: TransferNoteConfigRecord[];
5
5
  }
6
6
  export type UpdateTransferNoteConfigResponse = TransferNoteConfigRecord;
7
+ export type DecideTransferNoteConfigChangeResponse = TransferNoteConfigRecord;
@@ -0,0 +1,5 @@
1
+ /** APPROVE aplica el cambio propuesto de CLABE o de par; REJECT lo descarta y sigue el valor vigente. */
2
+ export declare enum TransferNoteConfigChangeDecisionEnum {
3
+ APPROVE = "APPROVE",
4
+ REJECT = "REJECT"
5
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransferNoteConfigChangeDecisionEnum = void 0;
4
+ /** APPROVE aplica el cambio propuesto de CLABE o de par; REJECT lo descarta y sigue el valor vigente. */
5
+ var TransferNoteConfigChangeDecisionEnum;
6
+ (function (TransferNoteConfigChangeDecisionEnum) {
7
+ TransferNoteConfigChangeDecisionEnum["APPROVE"] = "APPROVE";
8
+ TransferNoteConfigChangeDecisionEnum["REJECT"] = "REJECT";
9
+ })(TransferNoteConfigChangeDecisionEnum || (exports.TransferNoteConfigChangeDecisionEnum = TransferNoteConfigChangeDecisionEnum = {}));
@@ -38,5 +38,13 @@ export declare enum TransferNoteErrorCodeEnum {
38
38
  CUTOFF_DISPATCH_FAILED = "CUTOFF_DISPATCH_FAILED",
39
39
  /** Otra escritura cambió el registro entre la lectura y el guardado. */
40
40
  CONCURRENT_WRITE_CONFLICT = "CONCURRENT_WRITE_CONFLICT",
41
- AUDIT_QUEUE_PUBLISH_FAILED = "AUDIT_QUEUE_PUBLISH_FAILED"
41
+ AUDIT_QUEUE_PUBLISH_FAILED = "AUDIT_QUEUE_PUBLISH_FAILED",
42
+ /** La misma persona no puede hacer las dos mitades de un control (capturar y confirmar, proponer y aprobar). */
43
+ SAME_ACTOR_NOT_ALLOWED = "SAME_ACTOR_NOT_ALLOWED",
44
+ /** El registro de configuración no tiene un cambio propuesto que decidir. */
45
+ CONFIG_CHANGE_NOT_PENDING = "CONFIG_CHANGE_NOT_PENDING",
46
+ /** El documento de la nota todavía no tiene todos sus items; se reintenta en unos segundos. */
47
+ NOTE_DOCUMENT_NOT_READY = "NOTE_DOCUMENT_NOT_READY",
48
+ /** La parte nueva repite la tienda de otra parte o un tipo que solo puede tener una parte en el tenant. */
49
+ PARTY_ROUTING_CONFLICT = "PARTY_ROUTING_CONFLICT"
42
50
  }
@@ -43,4 +43,12 @@ var TransferNoteErrorCodeEnum;
43
43
  /** Otra escritura cambió el registro entre la lectura y el guardado. */
44
44
  TransferNoteErrorCodeEnum["CONCURRENT_WRITE_CONFLICT"] = "CONCURRENT_WRITE_CONFLICT";
45
45
  TransferNoteErrorCodeEnum["AUDIT_QUEUE_PUBLISH_FAILED"] = "AUDIT_QUEUE_PUBLISH_FAILED";
46
+ /** La misma persona no puede hacer las dos mitades de un control (capturar y confirmar, proponer y aprobar). */
47
+ TransferNoteErrorCodeEnum["SAME_ACTOR_NOT_ALLOWED"] = "SAME_ACTOR_NOT_ALLOWED";
48
+ /** El registro de configuración no tiene un cambio propuesto que decidir. */
49
+ TransferNoteErrorCodeEnum["CONFIG_CHANGE_NOT_PENDING"] = "CONFIG_CHANGE_NOT_PENDING";
50
+ /** El documento de la nota todavía no tiene todos sus items; se reintenta en unos segundos. */
51
+ TransferNoteErrorCodeEnum["NOTE_DOCUMENT_NOT_READY"] = "NOTE_DOCUMENT_NOT_READY";
52
+ /** La parte nueva repite la tienda de otra parte o un tipo que solo puede tener una parte en el tenant. */
53
+ TransferNoteErrorCodeEnum["PARTY_ROUTING_CONFLICT"] = "PARTY_ROUTING_CONFLICT";
46
54
  })(TransferNoteErrorCodeEnum || (exports.TransferNoteErrorCodeEnum = TransferNoteErrorCodeEnum = {}));
@@ -0,0 +1,38 @@
1
+ import { CurrencyId } from '../../currency/enums/CurrencyId';
2
+ import { TransferNoteDisputeCategoryEnum } from '../enums/TransferNoteDisputeCategoryEnum';
3
+ import { TransferNoteDisputeDecisionEnum } from '../enums/TransferNoteDisputeDecisionEnum';
4
+ export declare class TransferNoteLifecycleEventBase {
5
+ /** Fijo por ocurrencia (`{noteId}#{evento}#{instante}`): un reenvío llega con el mismo id. */
6
+ eventId: string;
7
+ tenantId: string;
8
+ noteId: string;
9
+ pairKey: string;
10
+ isoWeek: string;
11
+ netAmountCents: number;
12
+ currency: CurrencyId;
13
+ }
14
+ export declare class TransferNoteSpeiCapturedEventDetail extends TransferNoteLifecycleEventBase {
15
+ trackingKey: string;
16
+ /** ISO 8601 del pago declarado. */
17
+ paidAt: string;
18
+ paidAmountCents: number;
19
+ /** CLABE a la que se mandó el SPEI, si se capturó. */
20
+ paidToClabe?: string;
21
+ capturedAt: string;
22
+ }
23
+ export declare class TransferNoteSpeiRevertedEventDetail extends TransferNoteLifecycleEventBase {
24
+ /** Clave de rastreo del SPEI anulado. */
25
+ trackingKey: string;
26
+ revertedAt: string;
27
+ }
28
+ export declare class TransferNoteCancelledEventDetail extends TransferNoteLifecycleEventBase {
29
+ cancelledAt: string;
30
+ }
31
+ export declare class TransferNoteDisputedEventDetail extends TransferNoteLifecycleEventBase {
32
+ category: TransferNoteDisputeCategoryEnum;
33
+ disputedAt: string;
34
+ }
35
+ export declare class TransferNoteDisputeResolvedEventDetail extends TransferNoteLifecycleEventBase {
36
+ decision: TransferNoteDisputeDecisionEnum;
37
+ resolvedAt: string;
38
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransferNoteDisputeResolvedEventDetail = exports.TransferNoteDisputedEventDetail = exports.TransferNoteCancelledEventDetail = exports.TransferNoteSpeiRevertedEventDetail = exports.TransferNoteSpeiCapturedEventDetail = exports.TransferNoteLifecycleEventBase = void 0;
4
+ // Campos que comparten los eventos del ciclo de vida de la nota; solo ids y montos, sin razones en texto libre.
5
+ class TransferNoteLifecycleEventBase {
6
+ }
7
+ exports.TransferNoteLifecycleEventBase = TransferNoteLifecycleEventBase;
8
+ // `detail` de TransferNoteSpeiCaptured: el pagador capturó el SPEI y la nota pasó a SENT.
9
+ class TransferNoteSpeiCapturedEventDetail extends TransferNoteLifecycleEventBase {
10
+ }
11
+ exports.TransferNoteSpeiCapturedEventDetail = TransferNoteSpeiCapturedEventDetail;
12
+ // `detail` de TransferNoteSpeiReverted: el SPEI se anuló y la nota volvió a ISSUED sin confirmaciones.
13
+ class TransferNoteSpeiRevertedEventDetail extends TransferNoteLifecycleEventBase {
14
+ }
15
+ exports.TransferNoteSpeiRevertedEventDetail = TransferNoteSpeiRevertedEventDetail;
16
+ // `detail` de TransferNoteCancelled: la nota se canceló y sus items volvieron al pool.
17
+ class TransferNoteCancelledEventDetail extends TransferNoteLifecycleEventBase {
18
+ }
19
+ exports.TransferNoteCancelledEventDetail = TransferNoteCancelledEventDetail;
20
+ // `detail` de TransferNoteDisputed: el retailer objetó la nota.
21
+ class TransferNoteDisputedEventDetail extends TransferNoteLifecycleEventBase {
22
+ }
23
+ exports.TransferNoteDisputedEventDetail = TransferNoteDisputedEventDetail;
24
+ // `detail` de TransferNoteDisputeResolved: quien controla la nota aceptó o rechazó la objeción. La nota que
25
+ // reemplaza a una aceptada sale después en su propio TransferNoteIssued.
26
+ class TransferNoteDisputeResolvedEventDetail extends TransferNoteLifecycleEventBase {
27
+ }
28
+ exports.TransferNoteDisputeResolvedEventDetail = TransferNoteDisputeResolvedEventDetail;
@@ -2,7 +2,12 @@ import { CurrencyId } from '../../currency/enums/CurrencyId';
2
2
  export declare const TRANSFER_NOTE_EVENTS_SOURCE = "fiado.transferNote";
3
3
  export declare enum TransferNoteEventDetailTypeEnum {
4
4
  TRANSFER_NOTE_RECONCILED = "transferNote.TransferNoteReconciled",
5
- TRANSFER_NOTE_ISSUED = "transferNote.TransferNoteIssued"
5
+ TRANSFER_NOTE_ISSUED = "transferNote.TransferNoteIssued",
6
+ TRANSFER_NOTE_SPEI_CAPTURED = "transferNote.TransferNoteSpeiCaptured",
7
+ TRANSFER_NOTE_SPEI_REVERTED = "transferNote.TransferNoteSpeiReverted",
8
+ TRANSFER_NOTE_CANCELLED = "transferNote.TransferNoteCancelled",
9
+ TRANSFER_NOTE_DISPUTED = "transferNote.TransferNoteDisputed",
10
+ TRANSFER_NOTE_DISPUTE_RESOLVED = "transferNote.TransferNoteDisputeResolved"
6
11
  }
7
12
  export declare class TransferNoteReconciledEventDetail {
8
13
  /** Fijo por nota: un reenvío llega con el mismo id y el consumidor lo descarta. */
@@ -7,6 +7,11 @@ var TransferNoteEventDetailTypeEnum;
7
7
  (function (TransferNoteEventDetailTypeEnum) {
8
8
  TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_RECONCILED"] = "transferNote.TransferNoteReconciled";
9
9
  TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_ISSUED"] = "transferNote.TransferNoteIssued";
10
+ TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_SPEI_CAPTURED"] = "transferNote.TransferNoteSpeiCaptured";
11
+ TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_SPEI_REVERTED"] = "transferNote.TransferNoteSpeiReverted";
12
+ TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_CANCELLED"] = "transferNote.TransferNoteCancelled";
13
+ TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_DISPUTED"] = "transferNote.TransferNoteDisputed";
14
+ TransferNoteEventDetailTypeEnum["TRANSFER_NOTE_DISPUTE_RESOLVED"] = "transferNote.TransferNoteDisputeResolved";
10
15
  })(TransferNoteEventDetailTypeEnum || (exports.TransferNoteEventDetailTypeEnum = TransferNoteEventDetailTypeEnum = {}));
11
16
  // `detail` de TransferNoteReconciled; el motor recalcula con él el agregado de la semana del par.
12
17
  class TransferNoteReconciledEventDetail {
@@ -12,10 +12,12 @@ export * from './enums/TransferNoteConfirmationSourceEnum';
12
12
  export * from './enums/TransferNoteHistoryActionEnum';
13
13
  export * from './enums/TransferNoteFeedConceptEnum';
14
14
  export * from './enums/TransferNoteErrorCodeEnum';
15
+ export * from './enums/TransferNoteConfigChangeDecisionEnum';
15
16
  export * from './constants/transferNoteErrorHttpStatus';
16
17
  export * from './constants/transferNoteValidation';
17
18
  export * from './constants/transferNoteDefaultFees';
18
19
  export * from './validators/IsTransferNoteSelectionConstraint';
20
+ export * from './validators/IsTransferNoteClabeConstraint';
19
21
  export * from './dtos/TransferNote';
20
22
  export * from './dtos/TransferNoteItem';
21
23
  export * from './dtos/TransferNoteHistoryEntry';
@@ -33,3 +35,4 @@ export * from './dtos/responses/TransferNoteConfigResponses';
33
35
  export * from './dtos/responses/PrivateTransferNoteResponses';
34
36
  export * from './events/TransferNoteReconciledEventDetail';
35
37
  export * from './events/TransferNoteIssuedEventDetail';
38
+ export * from './events/TransferNoteLifecycleEventDetails';
@@ -29,11 +29,13 @@ __exportStar(require("./enums/TransferNoteConfirmationSourceEnum"), exports);
29
29
  __exportStar(require("./enums/TransferNoteHistoryActionEnum"), exports);
30
30
  __exportStar(require("./enums/TransferNoteFeedConceptEnum"), exports);
31
31
  __exportStar(require("./enums/TransferNoteErrorCodeEnum"), exports);
32
+ __exportStar(require("./enums/TransferNoteConfigChangeDecisionEnum"), exports);
32
33
  // Constantes y validadores
33
34
  __exportStar(require("./constants/transferNoteErrorHttpStatus"), exports);
34
35
  __exportStar(require("./constants/transferNoteValidation"), exports);
35
36
  __exportStar(require("./constants/transferNoteDefaultFees"), exports);
36
37
  __exportStar(require("./validators/IsTransferNoteSelectionConstraint"), exports);
38
+ __exportStar(require("./validators/IsTransferNoteClabeConstraint"), exports);
37
39
  // Entidades del dominio
38
40
  __exportStar(require("./dtos/TransferNote"), exports);
39
41
  __exportStar(require("./dtos/TransferNoteItem"), exports);
@@ -55,3 +57,4 @@ __exportStar(require("./dtos/responses/PrivateTransferNoteResponses"), exports);
55
57
  // Eventos
56
58
  __exportStar(require("./events/TransferNoteReconciledEventDetail"), exports);
57
59
  __exportStar(require("./events/TransferNoteIssuedEventDetail"), exports);
60
+ __exportStar(require("./events/TransferNoteLifecycleEventDetails"), exports);
@@ -0,0 +1,8 @@
1
+ import { type ValidatorConstraintInterface } from 'class-validator';
2
+ /** CLABE de 18 dígitos cuyo último dígito es el verificador correcto de los 17 anteriores. */
3
+ export declare function isValidTransferNoteClabe(clabe: unknown): boolean;
4
+ /** CLABE con formato y dígito verificador válidos. */
5
+ export declare class IsTransferNoteClabeConstraint implements ValidatorConstraintInterface {
6
+ validate(value: unknown): boolean;
7
+ defaultMessage(): string;
8
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.IsTransferNoteClabeConstraint = void 0;
10
+ exports.isValidTransferNoteClabe = isValidTransferNoteClabe;
11
+ const class_validator_1 = require("class-validator");
12
+ const transferNoteValidation_1 = require("../constants/transferNoteValidation");
13
+ /** Pesos 3, 7, 1 del dígito verificador de la CLABE (Banxico). */
14
+ const CLABE_CHECK_WEIGHTS = [3, 7, 1];
15
+ /** CLABE de 18 dígitos cuyo último dígito es el verificador correcto de los 17 anteriores. */
16
+ function isValidTransferNoteClabe(clabe) {
17
+ if (typeof clabe !== 'string' || !transferNoteValidation_1.TRANSFER_NOTE_CLABE_PATTERN.test(clabe))
18
+ return false;
19
+ const sum = [...clabe.slice(0, 17)].reduce((acc, digit, index) => acc + ((Number(digit) * CLABE_CHECK_WEIGHTS[index % 3]) % 10), 0);
20
+ return (10 - (sum % 10)) % 10 === Number(clabe[17]);
21
+ }
22
+ /** CLABE con formato y dígito verificador válidos. */
23
+ let IsTransferNoteClabeConstraint = class IsTransferNoteClabeConstraint {
24
+ validate(value) {
25
+ return isValidTransferNoteClabe(value);
26
+ }
27
+ defaultMessage() {
28
+ return 'CLABE de 18 dígitos con dígito verificador válido';
29
+ }
30
+ };
31
+ exports.IsTransferNoteClabeConstraint = IsTransferNoteClabeConstraint;
32
+ exports.IsTransferNoteClabeConstraint = IsTransferNoteClabeConstraint = __decorate([
33
+ (0, class_validator_1.ValidatorConstraint)({ name: 'isTransferNoteClabe', async: false })
34
+ ], IsTransferNoteClabeConstraint);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.638.0",
3
+ "version": "3.640.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -88,6 +88,8 @@ export interface CommissionSaleInput {
88
88
  /** `CASH` | `CREDIT`, para `PERCENT_BY_PAYMENT`. */
89
89
  paymentType: string;
90
90
  soldAt: string;
91
+ /** Cuándo se ganó la comisión: ubica la venta en su corte. Los devengos viejos no lo traen o lo traen en `null` (usar `soldAt`). */
92
+ earnedAt?: string | null;
91
93
  /** Las líneas de la venta. Las ventas que se calculan con reglas siempre las traen. */
92
94
  lines?: CommissionSaleLine[];
93
95
  }
@@ -98,6 +98,10 @@ export interface Sale {
98
98
  cancelledAt: string | null;
99
99
  /** Reason OBLIGATORIO en cancelación (AuditTrail) — flujo C. */
100
100
  cancelReason: string | null;
101
+ /** La comisión espera a que el cliente active su app: contraseña, contrato y número secreto. Ausente si no espera. */
102
+ commissionAwaitingApp?: boolean;
103
+ /** ISO; cuándo se ganó la comisión, si el cliente activó su app después de cerrar. Ausente: al cerrar o en espera. */
104
+ commissionEarnedAt?: string;
101
105
  /** Si la venta se puede cancelar hoy y, si no, por qué; solo lo trae el detalle del backoffice. */
102
106
  cancellation?: SaleCancellation;
103
107
  /**
@@ -131,6 +131,14 @@ export class RetailCommissionInboundMessage {
131
131
  @IsISO8601()
132
132
  occurredAt!: string;
133
133
 
134
+ /**
135
+ * Cuándo se ganó la comisión, si el cliente activó su app después de cerrar la venta. Ubica la venta en
136
+ * su corte. Ausente: se ganó al cerrar (`soldAt`).
137
+ */
138
+ @IsOptional()
139
+ @IsISO8601()
140
+ earnedAt?: string;
141
+
134
142
  /** Motivo de la cancelación; puede venir vacío. */
135
143
  @IsOptional()
136
144
  @IsString()
@@ -32,4 +32,8 @@ export const TRANSFER_NOTE_ERROR_HTTP_STATUS: Readonly<Record<C, 400 | 404 | 409
32
32
  [C.CUTOFF_DISPATCH_FAILED]: 502,
33
33
  [C.CONCURRENT_WRITE_CONFLICT]: 409,
34
34
  [C.AUDIT_QUEUE_PUBLISH_FAILED]: 502,
35
+ [C.SAME_ACTOR_NOT_ALLOWED]: 422,
36
+ [C.CONFIG_CHANGE_NOT_PENDING]: 409,
37
+ [C.NOTE_DOCUMENT_NOT_READY]: 409,
38
+ [C.PARTY_ROUTING_CONFLICT]: 409,
35
39
  };
@@ -28,6 +28,8 @@ export interface TransferNoteSpeiPayment {
28
28
  capturedBy: string;
29
29
  capturedAt: string;
30
30
  receiptUploaded: boolean;
31
+ /** CLABE a la que se mandó el SPEI, si se capturó. */
32
+ paidToClabe?: string;
31
33
  }
32
34
 
33
35
  export interface TransferNoteConfirmation {
@@ -46,6 +48,13 @@ export interface TransferNoteDispute {
46
48
  previousStatus: TransferNoteStatusEnum;
47
49
  }
48
50
 
51
+ /** Cancelación de la nota: quién, cuándo y por qué. */
52
+ export interface TransferNoteCancellation {
53
+ reason: string;
54
+ cancelledBy: string;
55
+ cancelledAt: string;
56
+ }
57
+
49
58
  /** Fila de los listados de notas. */
50
59
  export interface TransferNoteSummary {
51
60
  noteId: string;
@@ -74,6 +83,8 @@ export interface TransferNote extends TransferNoteSummary {
74
83
  issueReason?: string;
75
84
  speiPayment?: TransferNoteSpeiPayment;
76
85
  dispute?: TransferNoteDispute;
86
+ /** Solo en notas canceladas después de emitirse. */
87
+ cancellation?: TransferNoteCancellation;
77
88
  /** Nota cancelada de la que nace esta, o la regenerada que la reemplaza. */
78
89
  regeneratedFromNoteId?: string;
79
90
  regeneratedNoteId?: string;
@@ -42,6 +42,19 @@ export interface TransferNotePairSettings {
42
42
  discrepancyOpenedAt?: string;
43
43
  }
44
44
 
45
+ /**
46
+ * Cambio que altera a quién se le paga y espera la aprobación de otra persona: CLABE, tipo o tienda de una parte,
47
+ * pagador/receptor de un par, o tipos de pagador/receptor o signo de un concepto.
48
+ * Mientras no se apruebe rige el valor vigente del registro.
49
+ */
50
+ export interface TransferNoteConfigPendingChange {
51
+ party?: TransferNotePartySettings;
52
+ concept?: TransferNoteConceptSettings;
53
+ pair?: TransferNotePairSettings;
54
+ proposedBy: string;
55
+ proposedAt: string;
56
+ }
57
+
45
58
  /** Un registro de configuración; trae solo el sub-shape de su `recordType`. */
46
59
  export interface TransferNoteConfigRecord {
47
60
  recordKey: string;
@@ -50,6 +63,8 @@ export interface TransferNoteConfigRecord {
50
63
  party?: TransferNotePartySettings;
51
64
  concept?: TransferNoteConceptSettings;
52
65
  pair?: TransferNotePairSettings;
66
+ /** Cambio propuesto que todavía no rige. */
67
+ pendingChange?: TransferNoteConfigPendingChange;
53
68
  updatedAt: string;
54
69
  updatedBy: string;
55
70
  }
@@ -1,6 +1,7 @@
1
1
  import { Expose, Type } from 'class-transformer';
2
2
  import { IsEnum, IsInt, IsISO8601, IsPositive, Validate } from 'class-validator';
3
3
  import { TransferNoteDisputeDecisionEnum } from '../../enums/TransferNoteDisputeDecisionEnum';
4
+ import { IsTransferNoteClabeConstraint } from '../../validators/IsTransferNoteClabeConstraint';
4
5
  import { IsTransferNoteSelectionConstraint } from '../../validators/IsTransferNoteSelectionConstraint';
5
6
  import { LongReason, OptionalIsoWeek, RequiredId, RequiredReason } from './transferNoteRequestDecorators';
6
7
 
@@ -47,6 +48,11 @@ export class CaptureTransferNoteSpeiRequest {
47
48
  @IsInt()
48
49
  @IsPositive()
49
50
  amountCents!: number;
51
+
52
+ /** CLABE a la que se mandó el SPEI, obligatoria; viaja en el feed de liquidación para compararla con la registrada del receptor. */
53
+ @Expose()
54
+ @Validate(IsTransferNoteClabeConstraint)
55
+ paidToClabe!: string;
50
56
  }
51
57
 
52
58
  /** Body de publicRevertTransferNoteSpei. */
@@ -55,9 +61,9 @@ export class RevertTransferNoteSpeiRequest {
55
61
  reason!: string;
56
62
  }
57
63
 
58
- /** Body de publicCancelTransferNote. */
64
+ /** Body de publicCancelTransferNote; la cancelación es irreversible y exige la misma razón larga que emitir u objetar. */
59
65
  export class CancelTransferNoteRequest {
60
- @RequiredReason()
66
+ @LongReason()
61
67
  reason!: string;
62
68
  }
63
69
 
@@ -3,6 +3,7 @@ import { IsDefined, IsEmail, IsEnum, IsInt, IsISO8601, IsOptional, Matches, Min,
3
3
  import { CurrencyId } from '../../../currency/enums/CurrencyId';
4
4
  import { TRANSFER_NOTE_CLABE_PATTERN } from '../../constants/transferNoteValidation';
5
5
  import { TransferNoteConceptSignEnum } from '../../enums/TransferNoteConceptSignEnum';
6
+ import { TransferNoteConfigChangeDecisionEnum } from '../../enums/TransferNoteConfigChangeDecisionEnum';
6
7
  import { TransferNoteConfigRecordTypeEnum as RecordType } from '../../enums/TransferNoteConfigRecordTypeEnum';
7
8
  import { TransferNotePartyTypeEnum } from '../../enums/TransferNotePartyTypeEnum';
8
9
  import { RequiredId } from './transferNoteRequestDecorators';
@@ -79,7 +80,10 @@ export class TransferNotePairSettingsInput {
79
80
  activeFrom!: string;
80
81
  }
81
82
 
82
- /** Body de publicUpdateTransferNoteConfig; da de alta el registro si no existe. */
83
+ /**
84
+ * Body de publicUpdateTransferNoteConfig; da de alta el registro si no existe.
85
+ * Un cambio de CLABE o de pagador/receptor de un par queda propuesto hasta que otra persona lo apruebe.
86
+ */
83
87
  export class UpdateTransferNoteConfigRequest {
84
88
  @Expose()
85
89
  @IsEnum(RecordType)
@@ -113,3 +117,15 @@ export class UpdateTransferNoteConfigRequest {
113
117
  @Type(() => TransferNotePairSettingsInput)
114
118
  pair?: TransferNotePairSettingsInput;
115
119
  }
120
+
121
+ /** Body de publicDecideTransferNoteConfigChange: aprueba o descarta el cambio propuesto; quien lo propuso no puede aprobarlo. */
122
+ export class DecideTransferNoteConfigChangeRequest {
123
+ @Expose()
124
+ @IsEnum(TransferNoteConfigChangeDecisionEnum)
125
+ decision!: TransferNoteConfigChangeDecisionEnum;
126
+
127
+ /** `pendingChange.proposedAt` que vio quien decide; si la propuesta se reemplazó después, la decisión se rechaza. */
128
+ @Expose()
129
+ @IsISO8601()
130
+ proposedAt!: string;
131
+ }
@@ -6,3 +6,5 @@ export interface GetTransferNoteConfigResponse {
6
6
  }
7
7
 
8
8
  export type UpdateTransferNoteConfigResponse = TransferNoteConfigRecord;
9
+
10
+ export type DecideTransferNoteConfigChangeResponse = TransferNoteConfigRecord;
@@ -0,0 +1,5 @@
1
+ /** APPROVE aplica el cambio propuesto de CLABE o de par; REJECT lo descarta y sigue el valor vigente. */
2
+ export enum TransferNoteConfigChangeDecisionEnum {
3
+ APPROVE = 'APPROVE',
4
+ REJECT = 'REJECT',
5
+ }
@@ -41,4 +41,12 @@ export enum TransferNoteErrorCodeEnum {
41
41
  /** Otra escritura cambió el registro entre la lectura y el guardado. */
42
42
  CONCURRENT_WRITE_CONFLICT = 'CONCURRENT_WRITE_CONFLICT',
43
43
  AUDIT_QUEUE_PUBLISH_FAILED = 'AUDIT_QUEUE_PUBLISH_FAILED',
44
+ /** La misma persona no puede hacer las dos mitades de un control (capturar y confirmar, proponer y aprobar). */
45
+ SAME_ACTOR_NOT_ALLOWED = 'SAME_ACTOR_NOT_ALLOWED',
46
+ /** El registro de configuración no tiene un cambio propuesto que decidir. */
47
+ CONFIG_CHANGE_NOT_PENDING = 'CONFIG_CHANGE_NOT_PENDING',
48
+ /** El documento de la nota todavía no tiene todos sus items; se reintenta en unos segundos. */
49
+ NOTE_DOCUMENT_NOT_READY = 'NOTE_DOCUMENT_NOT_READY',
50
+ /** La parte nueva repite la tienda de otra parte o un tipo que solo puede tener una parte en el tenant. */
51
+ PARTY_ROUTING_CONFLICT = 'PARTY_ROUTING_CONFLICT',
44
52
  }
@@ -0,0 +1,51 @@
1
+ import { CurrencyId } from '../../currency/enums/CurrencyId';
2
+ import { TransferNoteDisputeCategoryEnum } from '../enums/TransferNoteDisputeCategoryEnum';
3
+ import { TransferNoteDisputeDecisionEnum } from '../enums/TransferNoteDisputeDecisionEnum';
4
+
5
+ // Campos que comparten los eventos del ciclo de vida de la nota; solo ids y montos, sin razones en texto libre.
6
+ export class TransferNoteLifecycleEventBase {
7
+ /** Fijo por ocurrencia (`{noteId}#{evento}#{instante}`): un reenvío llega con el mismo id. */
8
+ eventId!: string;
9
+ tenantId!: string;
10
+ noteId!: string;
11
+ pairKey!: string;
12
+ isoWeek!: string;
13
+ netAmountCents!: number;
14
+ currency!: CurrencyId;
15
+ }
16
+
17
+ // `detail` de TransferNoteSpeiCaptured: el pagador capturó el SPEI y la nota pasó a SENT.
18
+ export class TransferNoteSpeiCapturedEventDetail extends TransferNoteLifecycleEventBase {
19
+ trackingKey!: string;
20
+ /** ISO 8601 del pago declarado. */
21
+ paidAt!: string;
22
+ paidAmountCents!: number;
23
+ /** CLABE a la que se mandó el SPEI, si se capturó. */
24
+ paidToClabe?: string;
25
+ capturedAt!: string;
26
+ }
27
+
28
+ // `detail` de TransferNoteSpeiReverted: el SPEI se anuló y la nota volvió a ISSUED sin confirmaciones.
29
+ export class TransferNoteSpeiRevertedEventDetail extends TransferNoteLifecycleEventBase {
30
+ /** Clave de rastreo del SPEI anulado. */
31
+ trackingKey!: string;
32
+ revertedAt!: string;
33
+ }
34
+
35
+ // `detail` de TransferNoteCancelled: la nota se canceló y sus items volvieron al pool.
36
+ export class TransferNoteCancelledEventDetail extends TransferNoteLifecycleEventBase {
37
+ cancelledAt!: string;
38
+ }
39
+
40
+ // `detail` de TransferNoteDisputed: el retailer objetó la nota.
41
+ export class TransferNoteDisputedEventDetail extends TransferNoteLifecycleEventBase {
42
+ category!: TransferNoteDisputeCategoryEnum;
43
+ disputedAt!: string;
44
+ }
45
+
46
+ // `detail` de TransferNoteDisputeResolved: quien controla la nota aceptó o rechazó la objeción. La nota que
47
+ // reemplaza a una aceptada sale después en su propio TransferNoteIssued.
48
+ export class TransferNoteDisputeResolvedEventDetail extends TransferNoteLifecycleEventBase {
49
+ decision!: TransferNoteDisputeDecisionEnum;
50
+ resolvedAt!: string;
51
+ }
@@ -6,6 +6,11 @@ export const TRANSFER_NOTE_EVENTS_SOURCE = 'fiado.transferNote';
6
6
  export enum TransferNoteEventDetailTypeEnum {
7
7
  TRANSFER_NOTE_RECONCILED = 'transferNote.TransferNoteReconciled',
8
8
  TRANSFER_NOTE_ISSUED = 'transferNote.TransferNoteIssued',
9
+ TRANSFER_NOTE_SPEI_CAPTURED = 'transferNote.TransferNoteSpeiCaptured',
10
+ TRANSFER_NOTE_SPEI_REVERTED = 'transferNote.TransferNoteSpeiReverted',
11
+ TRANSFER_NOTE_CANCELLED = 'transferNote.TransferNoteCancelled',
12
+ TRANSFER_NOTE_DISPUTED = 'transferNote.TransferNoteDisputed',
13
+ TRANSFER_NOTE_DISPUTE_RESOLVED = 'transferNote.TransferNoteDisputeResolved',
9
14
  }
10
15
 
11
16
  // `detail` de TransferNoteReconciled; el motor recalcula con él el agregado de la semana del par.
@@ -13,12 +13,14 @@ export * from './enums/TransferNoteConfirmationSourceEnum';
13
13
  export * from './enums/TransferNoteHistoryActionEnum';
14
14
  export * from './enums/TransferNoteFeedConceptEnum';
15
15
  export * from './enums/TransferNoteErrorCodeEnum';
16
+ export * from './enums/TransferNoteConfigChangeDecisionEnum';
16
17
 
17
18
  // Constantes y validadores
18
19
  export * from './constants/transferNoteErrorHttpStatus';
19
20
  export * from './constants/transferNoteValidation';
20
21
  export * from './constants/transferNoteDefaultFees';
21
22
  export * from './validators/IsTransferNoteSelectionConstraint';
23
+ export * from './validators/IsTransferNoteClabeConstraint';
22
24
 
23
25
  // Entidades del dominio
24
26
  export * from './dtos/TransferNote';
@@ -44,3 +46,4 @@ export * from './dtos/responses/PrivateTransferNoteResponses';
44
46
  // Eventos
45
47
  export * from './events/TransferNoteReconciledEventDetail';
46
48
  export * from './events/TransferNoteIssuedEventDetail';
49
+ export * from './events/TransferNoteLifecycleEventDetails';
@@ -0,0 +1,24 @@
1
+ import { ValidatorConstraint, type ValidatorConstraintInterface } from 'class-validator';
2
+ import { TRANSFER_NOTE_CLABE_PATTERN } from '../constants/transferNoteValidation';
3
+
4
+ /** Pesos 3, 7, 1 del dígito verificador de la CLABE (Banxico). */
5
+ const CLABE_CHECK_WEIGHTS = [3, 7, 1] as const;
6
+
7
+ /** CLABE de 18 dígitos cuyo último dígito es el verificador correcto de los 17 anteriores. */
8
+ export function isValidTransferNoteClabe(clabe: unknown): boolean {
9
+ if (typeof clabe !== 'string' || !TRANSFER_NOTE_CLABE_PATTERN.test(clabe)) return false;
10
+ const sum = [...clabe.slice(0, 17)].reduce((acc, digit, index) => acc + ((Number(digit) * CLABE_CHECK_WEIGHTS[index % 3]) % 10), 0);
11
+ return (10 - (sum % 10)) % 10 === Number(clabe[17]);
12
+ }
13
+
14
+ /** CLABE con formato y dígito verificador válidos. */
15
+ @ValidatorConstraint({ name: 'isTransferNoteClabe', async: false })
16
+ export class IsTransferNoteClabeConstraint implements ValidatorConstraintInterface {
17
+ validate(value: unknown): boolean {
18
+ return isValidTransferNoteClabe(value);
19
+ }
20
+
21
+ defaultMessage(): string {
22
+ return 'CLABE de 18 dígitos con dígito verificador válido';
23
+ }
24
+ }