@fiado/type-kit 3.639.0 → 3.641.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 (51) hide show
  1. package/_test_/unit/reconciliation/reconciliationShapes.test.ts +19 -0
  2. package/_test_/unit/transferNote/transferNoteConfigAndPrivate.test.ts +53 -0
  3. package/_test_/unit/transferNote/transferNoteEventsAndFeeds.test.ts +32 -0
  4. package/_test_/unit/transferNote/transferNoteRequests.test.ts +20 -1
  5. package/bin/reconciliation/dtos/ReconciliationFeedRecord.d.ts +2 -0
  6. package/bin/reconciliation/events/ReconciliationDiscrepancyFoundEventDetail.d.ts +17 -0
  7. package/bin/reconciliation/events/ReconciliationDiscrepancyFoundEventDetail.js +7 -0
  8. package/bin/reconciliation/events/ReconciliationItemMatchedEventDetail.d.ts +5 -1
  9. package/bin/reconciliation/events/ReconciliationItemMatchedEventDetail.js +4 -0
  10. package/bin/reconciliation/events/ReconciliationRunCompletedEventDetail.d.ts +27 -0
  11. package/bin/reconciliation/events/ReconciliationRunCompletedEventDetail.js +7 -0
  12. package/bin/reconciliation/index.d.ts +2 -0
  13. package/bin/reconciliation/index.js +2 -0
  14. package/bin/transferNote/constants/transferNoteErrorHttpStatus.js +4 -0
  15. package/bin/transferNote/dtos/TransferNote.d.ts +10 -0
  16. package/bin/transferNote/dtos/TransferNoteConfigRecords.d.ts +14 -0
  17. package/bin/transferNote/dtos/requests/TransferNoteActionRequests.d.ts +3 -1
  18. package/bin/transferNote/dtos/requests/TransferNoteActionRequests.js +8 -2
  19. package/bin/transferNote/dtos/requests/TransferNoteConfigRequests.d.ts +11 -1
  20. package/bin/transferNote/dtos/requests/TransferNoteConfigRequests.js +20 -2
  21. package/bin/transferNote/dtos/responses/TransferNoteConfigResponses.d.ts +1 -0
  22. package/bin/transferNote/enums/TransferNoteConfigChangeDecisionEnum.d.ts +5 -0
  23. package/bin/transferNote/enums/TransferNoteConfigChangeDecisionEnum.js +9 -0
  24. package/bin/transferNote/enums/TransferNoteErrorCodeEnum.d.ts +9 -1
  25. package/bin/transferNote/enums/TransferNoteErrorCodeEnum.js +8 -0
  26. package/bin/transferNote/events/TransferNoteLifecycleEventDetails.d.ts +38 -0
  27. package/bin/transferNote/events/TransferNoteLifecycleEventDetails.js +28 -0
  28. package/bin/transferNote/events/TransferNoteReconciledEventDetail.d.ts +6 -1
  29. package/bin/transferNote/events/TransferNoteReconciledEventDetail.js +5 -0
  30. package/bin/transferNote/index.d.ts +3 -0
  31. package/bin/transferNote/index.js +3 -0
  32. package/bin/transferNote/validators/IsTransferNoteClabeConstraint.d.ts +8 -0
  33. package/bin/transferNote/validators/IsTransferNoteClabeConstraint.js +34 -0
  34. package/package.json +1 -1
  35. package/src/reconciliation/dtos/ReconciliationFeedRecord.ts +2 -0
  36. package/src/reconciliation/events/ReconciliationDiscrepancyFoundEventDetail.ts +19 -0
  37. package/src/reconciliation/events/ReconciliationItemMatchedEventDetail.ts +4 -0
  38. package/src/reconciliation/events/ReconciliationRunCompletedEventDetail.ts +26 -0
  39. package/src/reconciliation/index.ts +2 -0
  40. package/src/transferNote/constants/transferNoteErrorHttpStatus.ts +4 -0
  41. package/src/transferNote/dtos/TransferNote.ts +11 -0
  42. package/src/transferNote/dtos/TransferNoteConfigRecords.ts +15 -0
  43. package/src/transferNote/dtos/requests/TransferNoteActionRequests.ts +8 -2
  44. package/src/transferNote/dtos/requests/TransferNoteConfigRequests.ts +17 -1
  45. package/src/transferNote/dtos/responses/TransferNoteConfigResponses.ts +2 -0
  46. package/src/transferNote/enums/TransferNoteConfigChangeDecisionEnum.ts +5 -0
  47. package/src/transferNote/enums/TransferNoteErrorCodeEnum.ts +8 -0
  48. package/src/transferNote/events/TransferNoteLifecycleEventDetails.ts +51 -0
  49. package/src/transferNote/events/TransferNoteReconciledEventDetail.ts +5 -0
  50. package/src/transferNote/index.ts +3 -0
  51. package/src/transferNote/validators/IsTransferNoteClabeConstraint.ts +24 -0
@@ -23,6 +23,25 @@ describe('formas del contrato', () => {
23
23
  expect(Object.keys(reversal)).not.toContain('sandbox');
24
24
  });
25
25
 
26
+ it('un registro marcado por su dueño como prueba trae sandbox', () => {
27
+ const test: Reconciliation.ReconciliationFeedRecord = {
28
+ recordId: 'pay-9', tenantId: 'surekeep', concept: ReconciliationFeedConceptEnum.INSTALLMENT_COLLECTION, matchKey: 'att-9',
29
+ amountCents: 100, currency: CurrencyId.MXN, occurredAt: '2026-10-20T15:00:00.000Z', feedDay: '2026-10-20', refs: {}, sandbox: true,
30
+ };
31
+ expect(test.sandbox).toBe(true);
32
+ });
33
+
34
+ it('RunCompleted y DiscrepancyFound tienen su detail-type', () => {
35
+ expect(Reconciliation.ReconciliationEventDetailTypeEnum.RECONCILIATION_RUN_COMPLETED).toBe('reconciliation.ReconciliationRunCompleted');
36
+ expect(Reconciliation.ReconciliationEventDetailTypeEnum.RECONCILIATION_DISCREPANCY_FOUND).toBe('reconciliation.ReconciliationDiscrepancyFound');
37
+ const found: Reconciliation.ReconciliationDiscrepancyFoundEventDetail = {
38
+ eventId: 'surekeep#m8-gateway-sofom#INSTALLMENT_COLLECTION#att-1', tenantId: 'surekeep', definitionId: 'm8-gateway-sofom',
39
+ itemKey: 'INSTALLMENT_COLLECTION#att-1', layer: ReconciliationLayerEnum.ITEM, state: ReconciliationItemStateEnum.MISSING_IN_EXECUTED,
40
+ periodKey: '2026-11-02', runId: 'run-1', foundAt: '2026-11-04T10:00:00.000Z',
41
+ };
42
+ expect(found.state).toBe('MISSING_IN_EXECUTED');
43
+ });
44
+
26
45
  it('el feed de notas usa la misma envoltura con sus registros', () => {
27
46
  const page: Reconciliation.PrivateListReconciliationFeedResponse<TransferNote.TransferNoteSettlementFeedRecord> = {
28
47
  records: [],
@@ -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', () => {
@@ -39,4 +39,6 @@ export interface ReconciliationFeedRecord {
39
39
  feedDay: string;
40
40
  counterpartyType?: TransferNotePartyTypeEnum;
41
41
  refs: ReconciliationFeedRefs;
42
+ /** El dueño marca la operación de prueba; el motor la deja en TEST, fuera del cuadre. Sin el campo, es productiva. */
43
+ sandbox?: boolean;
42
44
  }
@@ -0,0 +1,17 @@
1
+ import { ReconciliationItemStateEnum } from '../enums/ReconciliationItemStateEnum';
2
+ import { ReconciliationLayerEnum } from '../enums/ReconciliationLayerEnum';
3
+ export declare class ReconciliationDiscrepancyFoundEventDetail {
4
+ /** Fijo por partida (`{tenantId}#{definitionId}#{itemKey}`): sale una vez y un reenvío llega con el mismo id. */
5
+ eventId: string;
6
+ tenantId: string;
7
+ definitionId: string;
8
+ itemKey: string;
9
+ layer: ReconciliationLayerEnum;
10
+ state: ReconciliationItemStateEnum;
11
+ periodKey: string;
12
+ differenceCents?: number;
13
+ counterpartyId?: string;
14
+ runId: string;
15
+ /** ISO 8601. */
16
+ foundAt: string;
17
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReconciliationDiscrepancyFoundEventDetail = void 0;
4
+ // `detail` de ReconciliationDiscrepancyFound: una partida entró en diferencia; sin datos personales.
5
+ class ReconciliationDiscrepancyFoundEventDetail {
6
+ }
7
+ exports.ReconciliationDiscrepancyFoundEventDetail = ReconciliationDiscrepancyFoundEventDetail;
@@ -3,7 +3,11 @@ import { ReconciliationRuleTypeEnum } from '../enums/ReconciliationRuleTypeEnum'
3
3
  export declare const RECONCILIATION_EVENTS_SOURCE = "fiado.reconciliation";
4
4
  export declare enum ReconciliationEventDetailTypeEnum {
5
5
  /** Una partida BANK_STATEMENT con `refs.noteId` quedó en OK; sale una vez por partida. */
6
- RECONCILIATION_ITEM_MATCHED = "reconciliation.ReconciliationItemMatched"
6
+ RECONCILIATION_ITEM_MATCHED = "reconciliation.ReconciliationItemMatched",
7
+ /** Una corrida terminó (COMPLETED o FAILED); sale una vez por intento de la corrida. */
8
+ RECONCILIATION_RUN_COMPLETED = "reconciliation.ReconciliationRunCompleted",
9
+ /** Una partida entró en diferencia; sale una vez por partida. */
10
+ RECONCILIATION_DISCREPANCY_FOUND = "reconciliation.ReconciliationDiscrepancyFound"
7
11
  }
8
12
  export declare class ReconciliationItemMatchedEventDetail {
9
13
  /** Fijo por partida: un reenvío llega con el mismo id y el consumidor lo descarta. */
@@ -7,6 +7,10 @@ var ReconciliationEventDetailTypeEnum;
7
7
  (function (ReconciliationEventDetailTypeEnum) {
8
8
  /** Una partida BANK_STATEMENT con `refs.noteId` quedó en OK; sale una vez por partida. */
9
9
  ReconciliationEventDetailTypeEnum["RECONCILIATION_ITEM_MATCHED"] = "reconciliation.ReconciliationItemMatched";
10
+ /** Una corrida terminó (COMPLETED o FAILED); sale una vez por intento de la corrida. */
11
+ ReconciliationEventDetailTypeEnum["RECONCILIATION_RUN_COMPLETED"] = "reconciliation.ReconciliationRunCompleted";
12
+ /** Una partida entró en diferencia; sale una vez por partida. */
13
+ ReconciliationEventDetailTypeEnum["RECONCILIATION_DISCREPANCY_FOUND"] = "reconciliation.ReconciliationDiscrepancyFound";
10
14
  })(ReconciliationEventDetailTypeEnum || (exports.ReconciliationEventDetailTypeEnum = ReconciliationEventDetailTypeEnum = {}));
11
15
  // `detail` de ReconciliationItemMatched; transfer-note llena con él la confirmación de FSA.
12
16
  class ReconciliationItemMatchedEventDetail {
@@ -0,0 +1,27 @@
1
+ import { ReconciliationItemStateEnum } from '../enums/ReconciliationItemStateEnum';
2
+ import { ReconciliationRuleTypeEnum } from '../enums/ReconciliationRuleTypeEnum';
3
+ import { ReconciliationRunStatusEnum } from '../enums/ReconciliationRunStatusEnum';
4
+ export declare class ReconciliationRunCompletedEventDetail {
5
+ /**
6
+ * Fijo por intento y resultado (`{tenantId}#{definitionId}#{runId}#{status}#{inicio del intento}`): un reenvío llega con el
7
+ * mismo id, y el reintento de una corrida FAILED (mismo `runId`) que termina COMPLETED llega con otro.
8
+ */
9
+ eventId: string;
10
+ tenantId: string;
11
+ definitionId: string;
12
+ runId: string;
13
+ periodKey: string;
14
+ ruleType: ReconciliationRuleTypeEnum;
15
+ status: ReconciliationRunStatusEnum;
16
+ countsByState: {
17
+ state: ReconciliationItemStateEnum;
18
+ count: number;
19
+ }[];
20
+ rejectedRecordCount: number;
21
+ /** Code de la causa cuando la corrida falló. */
22
+ failureCode?: string;
23
+ /** Llave del resumen en el bucket de conciliación (`{tenantId}/{definitionId}/{periodKey}/{layer}.json`). */
24
+ summaryObjectKey?: string;
25
+ /** ISO 8601. */
26
+ finishedAt: string;
27
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReconciliationRunCompletedEventDetail = void 0;
4
+ // `detail` de ReconciliationRunCompleted: el resultado de una corrida, sin datos personales.
5
+ class ReconciliationRunCompletedEventDetail {
6
+ }
7
+ exports.ReconciliationRunCompletedEventDetail = ReconciliationRunCompletedEventDetail;
@@ -27,3 +27,5 @@ export * from './dtos/responses/ReconciliationResponses';
27
27
  export * from './dtos/responses/PrivateReconciliationResponses';
28
28
  export * from './dtos/responses/ReconciliationFeedResponses';
29
29
  export * from './events/ReconciliationItemMatchedEventDetail';
30
+ export * from './events/ReconciliationRunCompletedEventDetail';
31
+ export * from './events/ReconciliationDiscrepancyFoundEventDetail';
@@ -49,3 +49,5 @@ __exportStar(require("./dtos/responses/PrivateReconciliationResponses"), exports
49
49
  __exportStar(require("./dtos/responses/ReconciliationFeedResponses"), exports);
50
50
  // Eventos
51
51
  __exportStar(require("./events/ReconciliationItemMatchedEventDetail"), exports);
52
+ __exportStar(require("./events/ReconciliationRunCompletedEventDetail"), exports);
53
+ __exportStar(require("./events/ReconciliationDiscrepancyFoundEventDetail"), exports);
@@ -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.639.0",
3
+ "version": "3.641.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -41,4 +41,6 @@ export interface ReconciliationFeedRecord {
41
41
  feedDay: string;
42
42
  counterpartyType?: TransferNotePartyTypeEnum;
43
43
  refs: ReconciliationFeedRefs;
44
+ /** El dueño marca la operación de prueba; el motor la deja en TEST, fuera del cuadre. Sin el campo, es productiva. */
45
+ sandbox?: boolean;
44
46
  }
@@ -0,0 +1,19 @@
1
+ import { ReconciliationItemStateEnum } from '../enums/ReconciliationItemStateEnum';
2
+ import { ReconciliationLayerEnum } from '../enums/ReconciliationLayerEnum';
3
+
4
+ // `detail` de ReconciliationDiscrepancyFound: una partida entró en diferencia; sin datos personales.
5
+ export class ReconciliationDiscrepancyFoundEventDetail {
6
+ /** Fijo por partida (`{tenantId}#{definitionId}#{itemKey}`): sale una vez y un reenvío llega con el mismo id. */
7
+ eventId!: string;
8
+ tenantId!: string;
9
+ definitionId!: string;
10
+ itemKey!: string;
11
+ layer!: ReconciliationLayerEnum;
12
+ state!: ReconciliationItemStateEnum;
13
+ periodKey!: string;
14
+ differenceCents?: number;
15
+ counterpartyId?: string;
16
+ runId!: string;
17
+ /** ISO 8601. */
18
+ foundAt!: string;
19
+ }
@@ -7,6 +7,10 @@ export const RECONCILIATION_EVENTS_SOURCE = 'fiado.reconciliation';
7
7
  export enum ReconciliationEventDetailTypeEnum {
8
8
  /** Una partida BANK_STATEMENT con `refs.noteId` quedó en OK; sale una vez por partida. */
9
9
  RECONCILIATION_ITEM_MATCHED = 'reconciliation.ReconciliationItemMatched',
10
+ /** Una corrida terminó (COMPLETED o FAILED); sale una vez por intento de la corrida. */
11
+ RECONCILIATION_RUN_COMPLETED = 'reconciliation.ReconciliationRunCompleted',
12
+ /** Una partida entró en diferencia; sale una vez por partida. */
13
+ RECONCILIATION_DISCREPANCY_FOUND = 'reconciliation.ReconciliationDiscrepancyFound',
10
14
  }
11
15
 
12
16
  // `detail` de ReconciliationItemMatched; transfer-note llena con él la confirmación de FSA.
@@ -0,0 +1,26 @@
1
+ import { ReconciliationItemStateEnum } from '../enums/ReconciliationItemStateEnum';
2
+ import { ReconciliationRuleTypeEnum } from '../enums/ReconciliationRuleTypeEnum';
3
+ import { ReconciliationRunStatusEnum } from '../enums/ReconciliationRunStatusEnum';
4
+
5
+ // `detail` de ReconciliationRunCompleted: el resultado de una corrida, sin datos personales.
6
+ export class ReconciliationRunCompletedEventDetail {
7
+ /**
8
+ * Fijo por intento y resultado (`{tenantId}#{definitionId}#{runId}#{status}#{inicio del intento}`): un reenvío llega con el
9
+ * mismo id, y el reintento de una corrida FAILED (mismo `runId`) que termina COMPLETED llega con otro.
10
+ */
11
+ eventId!: string;
12
+ tenantId!: string;
13
+ definitionId!: string;
14
+ runId!: string;
15
+ periodKey!: string;
16
+ ruleType!: ReconciliationRuleTypeEnum;
17
+ status!: ReconciliationRunStatusEnum;
18
+ countsByState!: { state: ReconciliationItemStateEnum; count: number }[];
19
+ rejectedRecordCount!: number;
20
+ /** Code de la causa cuando la corrida falló. */
21
+ failureCode?: string;
22
+ /** Llave del resumen en el bucket de conciliación (`{tenantId}/{definitionId}/{periodKey}/{layer}.json`). */
23
+ summaryObjectKey?: string;
24
+ /** ISO 8601. */
25
+ finishedAt!: string;
26
+ }
@@ -38,3 +38,5 @@ export * from './dtos/responses/ReconciliationFeedResponses';
38
38
 
39
39
  // Eventos
40
40
  export * from './events/ReconciliationItemMatchedEventDetail';
41
+ export * from './events/ReconciliationRunCompletedEventDetail';
42
+ export * from './events/ReconciliationDiscrepancyFoundEventDetail';
@@ -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
+ }