@fiado/type-kit 3.509.0 → 3.510.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 (174) hide show
  1. package/_test_/unit/dynamicForm/dynamicForm.test.ts +78 -0
  2. package/_test_/unit/rewards/enums.test.ts +87 -0
  3. package/_test_/unit/rewards/grantMessages.test.ts +123 -0
  4. package/_test_/unit/rewards/readResponses.test.ts +136 -0
  5. package/_test_/unit/rewardsEligibility/events.test.ts +130 -0
  6. package/bin/benefitCenter/dtos/InputField.d.ts +1 -13
  7. package/bin/benefitCenter/dtos/InputField.js +3 -3
  8. package/bin/benefitCenter/dtos/InputSchema.d.ts +1 -6
  9. package/bin/benefitCenter/dtos/InputSchema.js +3 -3
  10. package/bin/benefitCenter/dtos/InputValidation.d.ts +1 -8
  11. package/bin/benefitCenter/dtos/InputValidation.js +3 -3
  12. package/bin/benefitCenter/dtos/SelectOption.d.ts +1 -4
  13. package/bin/benefitCenter/dtos/SelectOption.js +3 -3
  14. package/bin/benefitCenter/enums/InputFieldTypeEnum.d.ts +1 -10
  15. package/bin/benefitCenter/enums/InputFieldTypeEnum.js +3 -11
  16. package/bin/common/index.d.ts +1 -0
  17. package/bin/common/index.js +2 -0
  18. package/bin/common/types/JsonValue.d.ts +7 -0
  19. package/bin/common/types/JsonValue.js +2 -0
  20. package/bin/dynamicForm/dtos/InputField.d.ts +15 -0
  21. package/bin/dynamicForm/dtos/InputField.js +6 -0
  22. package/bin/dynamicForm/dtos/InputSchema.d.ts +6 -0
  23. package/bin/dynamicForm/dtos/InputSchema.js +6 -0
  24. package/bin/dynamicForm/dtos/InputValidation.d.ts +8 -0
  25. package/bin/dynamicForm/dtos/InputValidation.js +6 -0
  26. package/bin/dynamicForm/dtos/SelectOption.d.ts +4 -0
  27. package/bin/dynamicForm/dtos/SelectOption.js +6 -0
  28. package/bin/dynamicForm/enums/InputFieldTypeEnum.d.ts +18 -0
  29. package/bin/dynamicForm/enums/InputFieldTypeEnum.js +22 -0
  30. package/bin/dynamicForm/helpers/isDynamicFormValue.d.ts +7 -0
  31. package/bin/dynamicForm/helpers/isDynamicFormValue.js +19 -0
  32. package/bin/dynamicForm/index.d.ts +8 -0
  33. package/bin/dynamicForm/index.js +29 -0
  34. package/bin/dynamicForm/types/DynamicFormValue.d.ts +10 -0
  35. package/bin/dynamicForm/types/DynamicFormValue.js +2 -0
  36. package/bin/dynamicForm/validators/IsDynamicFormValues.d.ts +7 -0
  37. package/bin/dynamicForm/validators/IsDynamicFormValues.js +26 -0
  38. package/bin/index.d.ts +3 -0
  39. package/bin/index.js +12 -1
  40. package/bin/rewards/constants/RewardsLimits.d.ts +2 -0
  41. package/bin/rewards/constants/RewardsLimits.js +5 -0
  42. package/bin/rewards/dtos/GrantFailedMessage.d.ts +13 -0
  43. package/bin/rewards/dtos/GrantFailedMessage.js +46 -0
  44. package/bin/rewards/dtos/GrantRequestedMessage.d.ts +21 -0
  45. package/bin/rewards/dtos/GrantRequestedMessage.js +61 -0
  46. package/bin/rewards/dtos/RewardChargeTo.d.ts +5 -0
  47. package/bin/rewards/dtos/RewardChargeTo.js +7 -0
  48. package/bin/rewards/dtos/RewardCost.d.ts +6 -0
  49. package/bin/rewards/dtos/RewardCost.js +7 -0
  50. package/bin/rewards/dtos/RewardDependency.d.ts +17 -0
  51. package/bin/rewards/dtos/RewardDependency.js +12 -0
  52. package/bin/rewards/dtos/RewardSource.d.ts +12 -0
  53. package/bin/rewards/dtos/RewardSource.js +40 -0
  54. package/bin/rewards/dtos/responses/GrantAttemptResponse.d.ts +15 -0
  55. package/bin/rewards/dtos/responses/GrantAttemptResponse.js +10 -0
  56. package/bin/rewards/dtos/responses/GrantItemResponse.d.ts +20 -0
  57. package/bin/rewards/dtos/responses/GrantItemResponse.js +6 -0
  58. package/bin/rewards/dtos/responses/GrantResponse.d.ts +23 -0
  59. package/bin/rewards/dtos/responses/GrantResponse.js +7 -0
  60. package/bin/rewards/dtos/responses/PackageContractItem.d.ts +12 -0
  61. package/bin/rewards/dtos/responses/PackageContractItem.js +7 -0
  62. package/bin/rewards/dtos/responses/PackageContractResponse.d.ts +13 -0
  63. package/bin/rewards/dtos/responses/PackageContractResponse.js +7 -0
  64. package/bin/rewards/enums/DependencyIfMissingEnum.d.ts +5 -0
  65. package/bin/rewards/enums/DependencyIfMissingEnum.js +9 -0
  66. package/bin/rewards/enums/GrantFailedReasonEnum.d.ts +11 -0
  67. package/bin/rewards/enums/GrantFailedReasonEnum.js +15 -0
  68. package/bin/rewards/enums/GrantItemStatusEnum.d.ts +6 -0
  69. package/bin/rewards/enums/GrantItemStatusEnum.js +10 -0
  70. package/bin/rewards/enums/GrantRejectReasonEnum.d.ts +6 -0
  71. package/bin/rewards/enums/GrantRejectReasonEnum.js +10 -0
  72. package/bin/rewards/enums/GrantStatusEnum.d.ts +8 -0
  73. package/bin/rewards/enums/GrantStatusEnum.js +12 -0
  74. package/bin/rewards/enums/ItemErrorCategoryEnum.d.ts +7 -0
  75. package/bin/rewards/enums/ItemErrorCategoryEnum.js +11 -0
  76. package/bin/rewards/enums/RewardStatusEnum.d.ts +4 -0
  77. package/bin/rewards/enums/RewardStatusEnum.js +8 -0
  78. package/bin/rewards/enums/RewardsErrorCodeEnum.d.ts +5 -0
  79. package/bin/rewards/enums/RewardsErrorCodeEnum.js +9 -0
  80. package/bin/rewards/enums/RewardsMessageTypeEnum.d.ts +4 -0
  81. package/bin/rewards/enums/RewardsMessageTypeEnum.js +8 -0
  82. package/bin/rewards/index.d.ts +24 -0
  83. package/bin/rewards/index.js +47 -0
  84. package/bin/rewards/types/RewardInputs.d.ts +3 -0
  85. package/bin/rewards/types/RewardInputs.js +2 -0
  86. package/bin/rewards/validators/IsRewardInputs.d.ts +7 -0
  87. package/bin/rewards/validators/IsRewardInputs.js +26 -0
  88. package/bin/rewards/validators/IsRewardTags.d.ts +7 -0
  89. package/bin/rewards/validators/IsRewardTags.js +28 -0
  90. package/bin/rewardsEligibility/dtos/EventRunResult.d.ts +11 -0
  91. package/bin/rewardsEligibility/dtos/EventRunResult.js +7 -0
  92. package/bin/rewardsEligibility/dtos/EventSource.d.ts +5 -0
  93. package/bin/rewardsEligibility/dtos/EventSource.js +30 -0
  94. package/bin/rewardsEligibility/dtos/GetRunResponse.d.ts +22 -0
  95. package/bin/rewardsEligibility/dtos/GetRunResponse.js +7 -0
  96. package/bin/rewardsEligibility/dtos/PostEventRequest.d.ts +14 -0
  97. package/bin/rewardsEligibility/dtos/PostEventRequest.js +47 -0
  98. package/bin/rewardsEligibility/dtos/PostEventResponse.d.ts +5 -0
  99. package/bin/rewardsEligibility/dtos/PostEventResponse.js +7 -0
  100. package/bin/rewardsEligibility/dtos/RunCounts.d.ts +12 -0
  101. package/bin/rewardsEligibility/dtos/RunCounts.js +7 -0
  102. package/bin/rewardsEligibility/dtos/RunResultItem.d.ts +16 -0
  103. package/bin/rewardsEligibility/dtos/RunResultItem.js +7 -0
  104. package/bin/rewardsEligibility/enums/EvaluationOutcomeEnum.d.ts +11 -0
  105. package/bin/rewardsEligibility/enums/EvaluationOutcomeEnum.js +15 -0
  106. package/bin/rewardsEligibility/enums/GrantRequestStatusEnum.d.ts +11 -0
  107. package/bin/rewardsEligibility/enums/GrantRequestStatusEnum.js +15 -0
  108. package/bin/rewardsEligibility/enums/RewardsEligibilityErrorCodeEnum.d.ts +8 -0
  109. package/bin/rewardsEligibility/enums/RewardsEligibilityErrorCodeEnum.js +12 -0
  110. package/bin/rewardsEligibility/enums/RunModeEnum.d.ts +4 -0
  111. package/bin/rewardsEligibility/enums/RunModeEnum.js +8 -0
  112. package/bin/rewardsEligibility/enums/RunStatusEnum.d.ts +9 -0
  113. package/bin/rewardsEligibility/enums/RunStatusEnum.js +13 -0
  114. package/bin/rewardsEligibility/enums/TriggerTypeEnum.d.ts +5 -0
  115. package/bin/rewardsEligibility/enums/TriggerTypeEnum.js +9 -0
  116. package/bin/rewardsEligibility/index.d.ts +13 -0
  117. package/bin/rewardsEligibility/index.js +31 -0
  118. package/package.json +1 -1
  119. package/src/benefitCenter/dtos/InputField.ts +2 -14
  120. package/src/benefitCenter/dtos/InputSchema.ts +2 -7
  121. package/src/benefitCenter/dtos/InputValidation.ts +2 -8
  122. package/src/benefitCenter/dtos/SelectOption.ts +2 -4
  123. package/src/benefitCenter/enums/InputFieldTypeEnum.ts +2 -10
  124. package/src/common/index.ts +4 -1
  125. package/src/common/types/JsonValue.ts +5 -0
  126. package/src/dynamicForm/dtos/InputField.ts +16 -0
  127. package/src/dynamicForm/dtos/InputSchema.ts +7 -0
  128. package/src/dynamicForm/dtos/InputValidation.ts +8 -0
  129. package/src/dynamicForm/dtos/SelectOption.ts +4 -0
  130. package/src/dynamicForm/enums/InputFieldTypeEnum.ts +18 -0
  131. package/src/dynamicForm/helpers/isDynamicFormValue.ts +16 -0
  132. package/src/dynamicForm/index.ts +17 -0
  133. package/src/dynamicForm/types/DynamicFormValue.ts +8 -0
  134. package/src/dynamicForm/validators/IsDynamicFormValues.ts +16 -0
  135. package/src/index.ts +14 -0
  136. package/src/rewards/constants/RewardsLimits.ts +2 -0
  137. package/src/rewards/dtos/GrantFailedMessage.ts +29 -0
  138. package/src/rewards/dtos/GrantRequestedMessage.ts +46 -0
  139. package/src/rewards/dtos/RewardChargeTo.ts +5 -0
  140. package/src/rewards/dtos/RewardCost.ts +7 -0
  141. package/src/rewards/dtos/RewardDependency.ts +18 -0
  142. package/src/rewards/dtos/RewardSource.ts +27 -0
  143. package/src/rewards/dtos/responses/GrantAttemptResponse.ts +16 -0
  144. package/src/rewards/dtos/responses/GrantItemResponse.ts +21 -0
  145. package/src/rewards/dtos/responses/GrantResponse.ts +24 -0
  146. package/src/rewards/dtos/responses/PackageContractItem.ts +13 -0
  147. package/src/rewards/dtos/responses/PackageContractResponse.ts +14 -0
  148. package/src/rewards/enums/DependencyIfMissingEnum.ts +5 -0
  149. package/src/rewards/enums/GrantFailedReasonEnum.ts +11 -0
  150. package/src/rewards/enums/GrantItemStatusEnum.ts +6 -0
  151. package/src/rewards/enums/GrantRejectReasonEnum.ts +6 -0
  152. package/src/rewards/enums/GrantStatusEnum.ts +8 -0
  153. package/src/rewards/enums/ItemErrorCategoryEnum.ts +7 -0
  154. package/src/rewards/enums/RewardStatusEnum.ts +4 -0
  155. package/src/rewards/enums/RewardsErrorCodeEnum.ts +5 -0
  156. package/src/rewards/enums/RewardsMessageTypeEnum.ts +4 -0
  157. package/src/rewards/index.ts +37 -0
  158. package/src/rewards/types/RewardInputs.ts +4 -0
  159. package/src/rewards/validators/IsRewardInputs.ts +18 -0
  160. package/src/rewards/validators/IsRewardTags.ts +18 -0
  161. package/src/rewardsEligibility/dtos/EventRunResult.ts +12 -0
  162. package/src/rewardsEligibility/dtos/EventSource.ts +15 -0
  163. package/src/rewardsEligibility/dtos/GetRunResponse.ts +23 -0
  164. package/src/rewardsEligibility/dtos/PostEventRequest.ts +32 -0
  165. package/src/rewardsEligibility/dtos/PostEventResponse.ts +6 -0
  166. package/src/rewardsEligibility/dtos/RunCounts.ts +12 -0
  167. package/src/rewardsEligibility/dtos/RunResultItem.ts +17 -0
  168. package/src/rewardsEligibility/enums/EvaluationOutcomeEnum.ts +11 -0
  169. package/src/rewardsEligibility/enums/GrantRequestStatusEnum.ts +11 -0
  170. package/src/rewardsEligibility/enums/RewardsEligibilityErrorCodeEnum.ts +8 -0
  171. package/src/rewardsEligibility/enums/RunModeEnum.ts +4 -0
  172. package/src/rewardsEligibility/enums/RunStatusEnum.ts +9 -0
  173. package/src/rewardsEligibility/enums/TriggerTypeEnum.ts +5 -0
  174. package/src/rewardsEligibility/index.ts +16 -0
@@ -0,0 +1,78 @@
1
+ import 'reflect-metadata';
2
+ import { ValidationArguments } from 'class-validator';
3
+ import * as benefit from '../../../src/benefitCenter';
4
+ import * as form from '../../../src/dynamicForm';
5
+ import * as root from '../../../src';
6
+ import { JsonValue } from '../../../src/common';
7
+
8
+ const args = {} as ValidationArguments;
9
+
10
+ describe('dynamicForm: alias con benefitCenter (D-E5)', () => {
11
+ it('benefitCenter reexporta las MISMAS clases y enum, sin copias', () => {
12
+ expect(benefit.InputField).toBe(form.InputField);
13
+ expect(benefit.InputSchema).toBe(form.InputSchema);
14
+ expect(benefit.InputValidation).toBe(form.InputValidation);
15
+ expect(benefit.SelectOption).toBe(form.SelectOption);
16
+ expect(benefit.InputFieldTypeEnum).toBe(form.InputFieldTypeEnum);
17
+ });
18
+
19
+ it('conserva los 8 tipos originales con exactamente los mismos valores', () => {
20
+ const originales = ['PHONE', 'BARCODE', 'ACCOUNT_NUMBER', 'TEXT', 'NUMBER', 'MONEY', 'SELECT', 'DATE'];
21
+ originales.forEach((valor) => {
22
+ expect(form.InputFieldTypeEnum[valor as keyof typeof form.InputFieldTypeEnum]).toBe(valor);
23
+ });
24
+ });
25
+
26
+ it('agrega DIRECTORY, ENTITY_REF, DATE_RANGE y BOOLEAN', () => {
27
+ expect(form.InputFieldTypeEnum.DIRECTORY).toBe('DIRECTORY');
28
+ expect(form.InputFieldTypeEnum.ENTITY_REF).toBe('ENTITY_REF');
29
+ expect(form.InputFieldTypeEnum.DATE_RANGE).toBe('DATE_RANGE');
30
+ expect(form.InputFieldTypeEnum.BOOLEAN).toBe('BOOLEAN');
31
+ expect(Object.values(form.InputFieldTypeEnum)).toHaveLength(12);
32
+ });
33
+
34
+ it('InputField acepta optionsSource (SELECT/ENTITY_REF con opciones desde un endpoint)', () => {
35
+ const campo: form.InputField = {
36
+ key: 'saleId',
37
+ type: form.InputFieldTypeEnum.ENTITY_REF,
38
+ label: 'Venta',
39
+ required: true,
40
+ optionsSource: '/phone-sales/backoffice/sales',
41
+ };
42
+ expect(campo.optionsSource).toBe('/phone-sales/backoffice/sales');
43
+ });
44
+
45
+ it('se expone en el barrel raíz como namespace DynamicForm', () => {
46
+ expect(root.DynamicForm.InputFieldTypeEnum.DIRECTORY).toBe('DIRECTORY');
47
+ });
48
+ });
49
+
50
+ describe('IsDynamicFormValues', () => {
51
+ const validator = new form.IsDynamicFormValues();
52
+ const ok = (value: JsonValue): boolean => validator.validate(value, args);
53
+
54
+ it('acepta string, number, boolean y rango de fechas', () => {
55
+ expect(ok({ a: 'x', b: 1, c: false, d: { from: '2026-10-01', to: '2026-10-07' } })).toBe(true);
56
+ });
57
+
58
+ it('acepta el mapa vacío', () => {
59
+ expect(ok({})).toBe(true);
60
+ });
61
+
62
+ it.each<[string, JsonValue]>([
63
+ ['hoja null', { a: null }],
64
+ ['hoja arreglo', { a: ['x'] }],
65
+ ['objeto que no es rango', { a: { x: 1 } }],
66
+ ['rango incompleto', { a: { from: '2026-10-01' } }],
67
+ ['rango con llave extra', { a: { from: 'a', to: 'b', extra: 'c' } }],
68
+ ['raíz arreglo', ['x']],
69
+ ['raíz string', 'x'],
70
+ ['raíz null', null],
71
+ ])('rechaza %s', (_nombre, value) => {
72
+ expect(ok(value)).toBe(false);
73
+ });
74
+
75
+ it('rechaza un number no finito', () => {
76
+ expect(ok({ a: Number.POSITIVE_INFINITY })).toBe(false);
77
+ });
78
+ });
@@ -0,0 +1,87 @@
1
+ import 'reflect-metadata';
2
+ import {
3
+ RewardsMessageTypeEnum,
4
+ GrantStatusEnum,
5
+ GrantItemStatusEnum,
6
+ GrantRejectReasonEnum,
7
+ GrantFailedReasonEnum,
8
+ ItemErrorCategoryEnum,
9
+ RewardStatusEnum,
10
+ DependencyIfMissingEnum,
11
+ RewardsErrorCodeEnum,
12
+ RewardCost,
13
+ RewardChargeTo,
14
+ RewardDependency,
15
+ RewardInputs,
16
+ } from '../../../src/rewards';
17
+ import { InputFieldTypeEnum } from '../../../src/dynamicForm';
18
+ import { CurrencyId } from '../../../src/currency';
19
+
20
+ describe('rewards: enums (los valores viajan por cola y por HTTP: no se renombran)', () => {
21
+ it('RewardsMessageTypeEnum', () => {
22
+ expect(Object.values(RewardsMessageTypeEnum).sort()).toEqual(['GRANT_FAILED', 'GRANT_REQUESTED']);
23
+ });
24
+
25
+ it('GrantStatusEnum coincide con spec A 7.1', () => {
26
+ expect(Object.values(GrantStatusEnum).sort()).toEqual(
27
+ ['COMPLETED', 'FAILED', 'IN_PROGRESS', 'PARTIAL', 'PENDING', 'REJECTED'],
28
+ );
29
+ });
30
+
31
+ it('GrantItemStatusEnum coincide con spec A 7.1', () => {
32
+ expect(Object.values(GrantItemStatusEnum).sort()).toEqual(['FAILED', 'GRANTED', 'PENDING', 'SKIPPED']);
33
+ });
34
+
35
+ it('ItemErrorCategoryEnum coincide con spec A §8', () => {
36
+ expect(Object.values(ItemErrorCategoryEnum).sort()).toEqual([
37
+ 'INPUT_UNRESOLVABLE', 'INTERNAL_ERROR', 'PROVIDER_REJECTED', 'PROVIDER_UNAVAILABLE', 'REQUEST_INVALID',
38
+ ]);
39
+ });
40
+
41
+ it('GrantFailedReasonEnum incluye todas las razones de rechazo más ITEMS_FAILED', () => {
42
+ Object.values(GrantRejectReasonEnum).forEach((razon) => {
43
+ expect(Object.values(GrantFailedReasonEnum)).toContain(razon);
44
+ });
45
+ expect(GrantFailedReasonEnum.ITEMS_FAILED).toBe('ITEMS_FAILED');
46
+ expect(Object.values(GrantFailedReasonEnum)).toHaveLength(4);
47
+ });
48
+
49
+ it('RewardStatusEnum, DependencyIfMissingEnum y RewardsErrorCodeEnum', () => {
50
+ expect(Object.values(RewardStatusEnum).sort()).toEqual(['ACTIVE', 'INACTIVE']);
51
+ expect(Object.values(DependencyIfMissingEnum).sort()).toEqual(['REJECT', 'SKIP']);
52
+ expect(Object.values(RewardsErrorCodeEnum).sort()).toEqual(['GRANT_NOT_FOUND', 'PACKAGE_NOT_FOUND']);
53
+ });
54
+ });
55
+
56
+ describe('rewards: tipos de valor', () => {
57
+ it('RewardCost usa CurrencyId del dominio currency (no un string libre)', () => {
58
+ const costo: RewardCost = { amount: 50, currency: CurrencyId.MXN };
59
+ expect(costo.currency).toBe('MXN');
60
+ });
61
+
62
+ it('RewardChargeTo exige partnerId y partnerName', () => {
63
+ const aliado: RewardChargeTo = { partnerId: 'ALIADO-001', partnerName: 'Aliado de ejemplo' };
64
+ expect(aliado.partnerId).toBe('ALIADO-001');
65
+ });
66
+
67
+ it('RewardDependency es un InputField con los campos propios de rewards (spec A 5.1)', () => {
68
+ const dependencia: RewardDependency = {
69
+ key: 'referenceId',
70
+ type: InputFieldTypeEnum.TEXT,
71
+ label: 'Referencia del destinatario',
72
+ required: false,
73
+ requiredWhen: 'useAccountReference == false',
74
+ ifMissing: DependencyIfMissingEnum.SKIP,
75
+ resolvedFrom: 'directory.referenceId (cuando useAccountReference == true)',
76
+ description: 'Referencia a usar cuando no es la de la cuenta.',
77
+ };
78
+ expect(dependencia.ifMissing).toBe('SKIP');
79
+ });
80
+
81
+ it('RewardInputs mapea itemKey → inputKey → valor', () => {
82
+ const inputs: RewardInputs = {
83
+ exampleItem: { useAccountReference: false, referenceId: 'REF-001' },
84
+ };
85
+ expect(inputs.exampleItem.useAccountReference).toBe(false);
86
+ });
87
+ });
@@ -0,0 +1,123 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { JsonValue } from '../../../src/common';
5
+ import {
6
+ GrantRequestedMessage,
7
+ GrantFailedMessage,
8
+ GrantFailedReasonEnum,
9
+ RewardsMessageTypeEnum,
10
+ REWARD_TAGS_MAX_KEYS,
11
+ } from '../../../src/rewards';
12
+
13
+ const base = {
14
+ messageType: 'GRANT_REQUESTED',
15
+ directoryId: 'd-1',
16
+ packageId: 'paquete-1',
17
+ source: { service: 'rewards-eligibility', reference: 'run-1', tags: { campaignId: 'referidos-q4' } },
18
+ idempotencyKey: 'elig:ev-9:d-1:ONCE:r1',
19
+ inputs: { exampleItem: { useAccountReference: false, referenceId: 'REF-001' } },
20
+ };
21
+
22
+ const requested = (raw: Record<string, JsonValue>): GrantRequestedMessage =>
23
+ plainToInstance(GrantRequestedMessage, raw, { excludeExtraneousValues: true });
24
+
25
+ const errorProps = async (dto: object): Promise<string[]> =>
26
+ (await validate(dto)).map((e) => e.property);
27
+
28
+ describe('GrantRequestedMessage (spec A 6.1)', () => {
29
+ it('valida el mensaje de ejemplo del spec', async () => {
30
+ const dto = requested(base);
31
+ expect(await validate(dto)).toHaveLength(0);
32
+ expect(dto.messageType).toBe(RewardsMessageTypeEnum.GRANT_REQUESTED);
33
+ expect(dto.inputs.exampleItem.referenceId).toBe('REF-001');
34
+ });
35
+
36
+ it('acepta inputs vacío (paquete sin dependencias) y source sin tags', async () => {
37
+ const dto = requested({ ...base, inputs: {}, source: { service: 'x', reference: 'y' } });
38
+ expect(await validate(dto)).toHaveLength(0);
39
+ });
40
+
41
+ it('rechaza un messageType ajeno o ausente (se rutea por messageType, no por forma)', async () => {
42
+ expect(await errorProps(requested({ ...base, messageType: 'GRANT_FAILED' }))).toContain('messageType');
43
+ const { messageType: _omitido, ...sinTipo } = base;
44
+ expect(await errorProps(requested(sinTipo))).toContain('messageType');
45
+ });
46
+
47
+ it.each(['directoryId', 'packageId', 'idempotencyKey'])('rechaza %s vacío', async (campo) => {
48
+ expect(await errorProps(requested({ ...base, [campo]: '' }))).toContain(campo);
49
+ });
50
+
51
+ it('rechaza source.service o source.reference vacíos', async () => {
52
+ const dto = requested({ ...base, source: { service: '', reference: 'run-1' } });
53
+ expect(await errorProps(dto)).toContain('source');
54
+ const dto2 = requested({ ...base, source: { service: 'x', reference: '' } });
55
+ expect(await errorProps(dto2)).toContain('source');
56
+ });
57
+
58
+ it(`rechaza más de ${REWARD_TAGS_MAX_KEYS} tags (spec A 5.3)`, async () => {
59
+ const tags: Record<string, string> = {};
60
+ for (let i = 0; i <= REWARD_TAGS_MAX_KEYS; i += 1) tags[`k${i}`] = 'v';
61
+ expect(await errorProps(requested({ ...base, source: { service: 'x', reference: 'y', tags } }))).toContain('source');
62
+ });
63
+
64
+ it('acepta exactamente el máximo de tags', async () => {
65
+ const tags: Record<string, string> = {};
66
+ for (let i = 0; i < REWARD_TAGS_MAX_KEYS; i += 1) tags[`k${i}`] = 'v';
67
+ expect(await validate(requested({ ...base, source: { service: 'x', reference: 'y', tags } }))).toHaveLength(0);
68
+ });
69
+
70
+ it('rechaza tags con valores que no son string', async () => {
71
+ const dto = requested({ ...base, source: { service: 'x', reference: 'y', tags: { n: 1 } } });
72
+ expect(await errorProps(dto)).toContain('source');
73
+ });
74
+
75
+ it.each<[string, JsonValue]>([
76
+ ['hoja null', { exampleItem: { referenceId: null } }],
77
+ ['hoja arreglo', { exampleItem: { referenceId: ['REF-001'] } }],
78
+ ['anidación de 3 niveles', { exampleItem: { referenceId: { a: { b: 'c' } } } }],
79
+ ['itemKey con valor escalar', { exampleItem: 'EXAMPLE' }],
80
+ ['raíz arreglo', [{ exampleItem: 'x' }]],
81
+ ])('rechaza inputs inválido: %s', async (_nombre, inputs) => {
82
+ expect(await errorProps(requested({ ...base, inputs }))).toContain('inputs');
83
+ });
84
+
85
+ it('descarta campos extra: el grantId NO viaja y cualquier otro campo se ignora (excludeExtraneousValues)', () => {
86
+ const dto = requested({ ...base, grantId: 'forjado', prefetchedFacts: [{ value: 1 }] });
87
+ expect('grantId' in dto).toBe(false);
88
+ expect('prefetchedFacts' in dto).toBe(false);
89
+ });
90
+ });
91
+
92
+ describe('GrantFailedMessage (spec A 6.5)', () => {
93
+ const baseFailed = {
94
+ messageType: 'GRANT_FAILED',
95
+ idempotencyKey: 'elig:ev-9:d-1:ONCE:r1',
96
+ grantId: 'g-123',
97
+ reason: 'PACKAGE_INACTIVE',
98
+ };
99
+ const failed = (raw: Record<string, JsonValue>): GrantFailedMessage =>
100
+ plainToInstance(GrantFailedMessage, raw, { excludeExtraneousValues: true });
101
+
102
+ it('valida el ejemplo del spec', async () => {
103
+ const dto = failed(baseFailed);
104
+ expect(await validate(dto)).toHaveLength(0);
105
+ expect(dto.reason).toBe(GrantFailedReasonEnum.PACKAGE_INACTIVE);
106
+ });
107
+
108
+ it.each(Object.values(GrantFailedReasonEnum))('acepta la razón %s', async (reason) => {
109
+ expect(await validate(failed({ ...baseFailed, reason }))).toHaveLength(0);
110
+ });
111
+
112
+ it('rechaza una razón desconocida', async () => {
113
+ expect(await errorProps(failed({ ...baseFailed, reason: 'OTRA' }))).toContain('reason');
114
+ });
115
+
116
+ it('rechaza messageType GRANT_REQUESTED (no se confunde un tipo con el otro)', async () => {
117
+ expect(await errorProps(failed({ ...baseFailed, messageType: 'GRANT_REQUESTED' }))).toContain('messageType');
118
+ });
119
+
120
+ it.each(['idempotencyKey', 'grantId'])('rechaza %s vacío', async (campo) => {
121
+ expect(await errorProps(failed({ ...baseFailed, [campo]: '' }))).toContain(campo);
122
+ });
123
+ });
@@ -0,0 +1,136 @@
1
+ import 'reflect-metadata';
2
+ import {
3
+ PackageContractResponse,
4
+ GrantResponse,
5
+ GrantAttemptResponse,
6
+ RewardStatusEnum,
7
+ GrantStatusEnum,
8
+ GrantItemStatusEnum,
9
+ GrantRejectReasonEnum,
10
+ ItemErrorCategoryEnum,
11
+ } from '../../../src/rewards';
12
+ import { InputFieldTypeEnum } from '../../../src/dynamicForm';
13
+ import { CurrencyId } from '../../../src/currency';
14
+
15
+ describe('PackageContractResponse (GET /packages/{id}, spec A 6.3)', () => {
16
+ const paquete: PackageContractResponse = {
17
+ id: 'paquete-referido',
18
+ name: 'Paquete referido',
19
+ status: RewardStatusEnum.ACTIVE,
20
+ version: 2,
21
+ items: [
22
+ {
23
+ itemKey: 'exampleItem',
24
+ catalogItemId: 'example-reward-a',
25
+ name: 'Recompensa de ejemplo A',
26
+ description: 'Recompensa genérica de ejemplo por $50.',
27
+ cost: { amount: 50, currency: CurrencyId.MXN },
28
+ dependencies: [
29
+ {
30
+ key: 'useAccountReference',
31
+ type: InputFieldTypeEnum.BOOLEAN,
32
+ label: 'Usar la referencia de la cuenta',
33
+ required: true,
34
+ description: 'Indica si se usa la referencia de la cuenta del usuario para esta recompensa.',
35
+ },
36
+ ],
37
+ },
38
+ ],
39
+ totalCost: [{ amount: 50, currency: CurrencyId.MXN }],
40
+ };
41
+
42
+ it('sobrevive a un viaje JSON sin perder campos', () => {
43
+ expect(JSON.parse(JSON.stringify(paquete))).toEqual(paquete);
44
+ });
45
+
46
+ it('totalCost es una lista por moneda (un paquete puede mezclar monedas)', () => {
47
+ const mixto: PackageContractResponse = {
48
+ ...paquete,
49
+ totalCost: [
50
+ { amount: 50, currency: CurrencyId.MXN },
51
+ { amount: 3, currency: CurrencyId.USD },
52
+ ],
53
+ };
54
+ expect(mixto.totalCost.map((c) => c.currency)).toEqual(['MXN', 'USD']);
55
+ });
56
+ });
57
+
58
+ describe('GrantResponse (GET /grants/{id}, spec A 6.4)', () => {
59
+ const intento: GrantAttemptResponse = {
60
+ attempt: 2,
61
+ startedAt: 1760000000000,
62
+ finishedAt: 1760000001000,
63
+ errorCategory: ItemErrorCategoryEnum.REQUEST_INVALID,
64
+ errorCode: 'BAD_PRODUCT',
65
+ httpStatus: 400,
66
+ };
67
+
68
+ const entrega: GrantResponse = {
69
+ id: 'g-123',
70
+ directoryId: 'd-1',
71
+ packageId: 'paquete-referido',
72
+ packageVersion: 2,
73
+ source: { service: 'rewards-eligibility', reference: 'run-1', tags: { campaignId: 'referidos-q4' } },
74
+ status: GrantStatusEnum.PARTIAL,
75
+ items: [
76
+ {
77
+ itemKey: 'exampleItem',
78
+ catalogItemId: 'example-reward-a',
79
+ status: GrantItemStatusEnum.FAILED,
80
+ chargeTo: { partnerId: 'ALIADO-001', partnerName: 'Aliado de ejemplo' },
81
+ cost: { amount: 50, currency: CurrencyId.MXN },
82
+ attemptCount: 2,
83
+ externalRef: { transactionNumber: 'RW-g-123-exampleItem' },
84
+ attempts: [intento],
85
+ },
86
+ {
87
+ itemKey: 'exampleItemB',
88
+ catalogItemId: 'example-reward-b',
89
+ status: GrantItemStatusEnum.GRANTED,
90
+ chargeTo: { partnerId: 'ALIADO-001', partnerName: 'Aliado de ejemplo' },
91
+ cost: { amount: 20, currency: CurrencyId.MXN },
92
+ attemptCount: 1,
93
+ grantedAt: 1760000002000,
94
+ attempts: [],
95
+ },
96
+ ],
97
+ createdAt: 1760000000000,
98
+ updatedAt: 1760000002000,
99
+ };
100
+
101
+ it('sobrevive a un viaje JSON', () => {
102
+ expect(JSON.parse(JSON.stringify(entrega))).toEqual(entrega);
103
+ });
104
+
105
+ it('una entrega REJECTED lleva rejectReason y no tiene ítems ejecutados', () => {
106
+ const rechazada: GrantResponse = {
107
+ ...entrega,
108
+ status: GrantStatusEnum.REJECTED,
109
+ rejectReason: GrantRejectReasonEnum.PACKAGE_INACTIVE,
110
+ items: [],
111
+ };
112
+ expect(rechazada.rejectReason).toBe('PACKAGE_INACTIVE');
113
+ });
114
+
115
+ it('un INPUT_INVALID expone la lista de campos inválidos (spec A 6.2)', () => {
116
+ const invalida: GrantResponse = {
117
+ ...entrega,
118
+ status: GrantStatusEnum.REJECTED,
119
+ rejectReason: GrantRejectReasonEnum.INPUT_INVALID,
120
+ invalidFields: ['exampleItem.referenceId'],
121
+ items: [],
122
+ };
123
+ expect(JSON.parse(JSON.stringify(invalida)).invalidFields).toEqual(['exampleItem.referenceId']);
124
+ });
125
+
126
+ it('el intento NO expone datos sensibles (R2): providerResponse/requestSnapshot no existen en el contrato', () => {
127
+ const conSensibles: GrantAttemptResponse = {
128
+ attempt: 1,
129
+ startedAt: 1,
130
+ finishedAt: 2,
131
+ // @ts-expect-error requestSnapshot puede contener datos del destinatario: solo backoffice
132
+ requestSnapshot: { referenceId: 'REF-001' },
133
+ };
134
+ expect(conSensibles.attempt).toBe(1);
135
+ });
136
+ });
@@ -0,0 +1,130 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { JsonValue } from '../../../src/common';
5
+ import {
6
+ PostEventRequest,
7
+ PostEventResponse,
8
+ GetRunResponse,
9
+ EvaluationOutcomeEnum,
10
+ GrantRequestStatusEnum,
11
+ RunStatusEnum,
12
+ RunModeEnum,
13
+ TriggerTypeEnum,
14
+ RewardsEligibilityErrorCodeEnum,
15
+ } from '../../../src/rewardsEligibility';
16
+
17
+ const base = {
18
+ eventKey: 'PHONE_SALES_CODE_REDEEMED',
19
+ idempotencyKey: 'redeem:s-9',
20
+ source: { service: 'phone-sales', reference: 's-9' },
21
+ inputs: { buyerDirectoryId: 'd-1', redeemerDirectoryId: 'd-2', saleId: 's-9' },
22
+ };
23
+
24
+ const parse = (raw: Record<string, JsonValue>): PostEventRequest =>
25
+ plainToInstance(PostEventRequest, raw, { excludeExtraneousValues: true });
26
+
27
+ const errorProps = async (dto: object): Promise<string[]> => (await validate(dto)).map((e) => e.property);
28
+
29
+ describe('PostEventRequest (POST /events, spec B 6.1)', () => {
30
+ it('valida el ejemplo del spec', async () => {
31
+ const dto = parse(base);
32
+ expect(await validate(dto)).toHaveLength(0);
33
+ expect(dto.inputs.saleId).toBe('s-9');
34
+ });
35
+
36
+ it.each(['eventKey', 'idempotencyKey'])('rechaza %s vacío', async (campo) => {
37
+ expect(await errorProps(parse({ ...base, [campo]: '' }))).toContain(campo);
38
+ });
39
+
40
+ it('rechaza source sin service o sin reference', async () => {
41
+ expect(await errorProps(parse({ ...base, source: { service: '', reference: 's-9' } }))).toContain('source');
42
+ expect(await errorProps(parse({ ...base, source: { service: 'phone-sales', reference: '' } }))).toContain('source');
43
+ });
44
+
45
+ it.each<[string, JsonValue]>([
46
+ ['hoja null', { saleId: null }],
47
+ ['hoja arreglo', { saleId: ['s-9'] }],
48
+ ['objeto que no es rango', { saleId: { id: 's-9' } }],
49
+ ['raíz arreglo', ['s-9']],
50
+ ])('rechaza inputs inválido: %s', async (_nombre, inputs) => {
51
+ expect(await errorProps(parse({ ...base, inputs }))).toContain('inputs');
52
+ });
53
+
54
+ it('un emisor NO puede declarar hechos: prefetchedFacts y cualquier campo extra se descartan (E3)', () => {
55
+ const dto = parse({
56
+ ...base,
57
+ prefetchedFacts: [{ source: 'transactions', field: 'sumAmount', value: 9999 }],
58
+ outcome: 'MATCH',
59
+ });
60
+ expect('prefetchedFacts' in dto).toBe(false);
61
+ expect('outcome' in dto).toBe(false);
62
+ });
63
+ });
64
+
65
+ describe('enums de rewardsEligibility (valores que viajan por HTTP)', () => {
66
+ it('coinciden con spec B 5.4, 7.5 y 6.1', () => {
67
+ expect(Object.values(EvaluationOutcomeEnum).sort()).toEqual(
68
+ ['ALREADY_GRANTED', 'ERROR', 'EXCLUDED', 'MATCH', 'NO_MATCH', 'QUOTA_EXHAUSTED'],
69
+ );
70
+ expect(Object.values(GrantRequestStatusEnum).sort()).toEqual(
71
+ ['CANCELLED', 'NOT_APPLICABLE', 'PENDING_APPROVAL', 'QUEUED', 'REQUESTED', 'REQUEST_FAILED'],
72
+ );
73
+ expect(Object.values(RunStatusEnum).sort()).toEqual(
74
+ ['AWAITING_APPROVAL', 'COMPLETED', 'FAILED', 'PARTIAL', 'PUBLISHING', 'REJECTED', 'RUNNING'],
75
+ );
76
+ expect(Object.values(RunModeEnum).sort()).toEqual(['LIVE', 'SIMULATION']);
77
+ expect(Object.values(TriggerTypeEnum).sort()).toEqual(['CRON', 'EVENT', 'MANUAL']);
78
+ expect(Object.values(RewardsEligibilityErrorCodeEnum).sort()).toEqual(
79
+ ['EVENT_TYPE_NOT_FOUND', 'INPUT_INVALID', 'RUN_NOT_FOUND'],
80
+ );
81
+ });
82
+ });
83
+
84
+ describe('respuestas', () => {
85
+ it('PostEventResponse: ejemplo del spec y runs vacío cuando nadie escucha el evento', () => {
86
+ const respuesta: PostEventResponse = {
87
+ runs: [
88
+ {
89
+ evaluationId: 'phone-sales-referido-cuenta-nueva',
90
+ runId: 'r-1',
91
+ outcome: EvaluationOutcomeEnum.MATCH,
92
+ grantStatus: GrantRequestStatusEnum.REQUESTED,
93
+ idempotentReplay: false,
94
+ },
95
+ ],
96
+ };
97
+ const vacia: PostEventResponse = { runs: [] };
98
+ expect(JSON.parse(JSON.stringify(respuesta))).toEqual(respuesta);
99
+ expect(vacia.runs).toHaveLength(0);
100
+ });
101
+
102
+ it('GetRunResponse: cabecera + conteos + resultados paginados, sin traza', () => {
103
+ const corrida: GetRunResponse = {
104
+ runId: 'r-1',
105
+ evaluationId: 'phone-sales-referido-cuenta-nueva',
106
+ trigger: TriggerTypeEnum.EVENT,
107
+ mode: RunModeEnum.LIVE,
108
+ status: RunStatusEnum.COMPLETED,
109
+ counts: {
110
+ evaluated: 1, match: 1, noMatch: 0, error: 0, alreadyGranted: 0,
111
+ quotaExhausted: 0, excluded: 0, requested: 1, requestFailed: 0,
112
+ },
113
+ results: [
114
+ {
115
+ subjectKey: '5f1c9e0a2b',
116
+ beneficiaryDirectoryId: 'd-1',
117
+ campaignId: 'referidos-q4',
118
+ evaluationId: 'phone-sales-referido-cuenta-nueva',
119
+ packageId: 'paquete-referido',
120
+ outcome: EvaluationOutcomeEnum.MATCH,
121
+ grantStatus: GrantRequestStatusEnum.REQUESTED,
122
+ grantId: 'g-123',
123
+ },
124
+ ],
125
+ createdAt: 1760000000000,
126
+ updatedAt: 1760000001000,
127
+ };
128
+ expect(JSON.parse(JSON.stringify(corrida))).toEqual(corrida);
129
+ });
130
+ });
@@ -1,13 +1 @@
1
- import { InputFieldTypeEnum } from "../enums/InputFieldTypeEnum";
2
- import { SelectOption } from "./SelectOption";
3
- export declare class InputField {
4
- key: string;
5
- type: InputFieldTypeEnum;
6
- label: string;
7
- required: boolean;
8
- placeholder?: string;
9
- helpImage?: string;
10
- showHelp?: boolean;
11
- options?: SelectOption[];
12
- dependsOn?: string;
13
- }
1
+ export { InputField } from '../../dynamicForm/dtos/InputField';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InputField = void 0;
4
- class InputField {
5
- }
6
- exports.InputField = InputField;
4
+ // Alias (D-E5): el origen vive en `dynamicForm`. No duplicar.
5
+ var InputField_1 = require("../../dynamicForm/dtos/InputField");
6
+ Object.defineProperty(exports, "InputField", { enumerable: true, get: function () { return InputField_1.InputField; } });
@@ -1,6 +1 @@
1
- import { InputField } from "./InputField";
2
- import { InputValidation } from "./InputValidation";
3
- export declare class InputSchema {
4
- fields: InputField[];
5
- validations?: Record<string, InputValidation>;
6
- }
1
+ export { InputSchema } from '../../dynamicForm/dtos/InputSchema';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InputSchema = void 0;
4
- class InputSchema {
5
- }
6
- exports.InputSchema = InputSchema;
4
+ // Alias (D-E5): el origen vive en `dynamicForm`. No duplicar.
5
+ var InputSchema_1 = require("../../dynamicForm/dtos/InputSchema");
6
+ Object.defineProperty(exports, "InputSchema", { enumerable: true, get: function () { return InputSchema_1.InputSchema; } });
@@ -1,8 +1 @@
1
- export declare class InputValidation {
2
- pattern?: string;
3
- maxLength?: number;
4
- minLength?: number;
5
- min?: number;
6
- max?: number;
7
- hasCheckDigit?: boolean;
8
- }
1
+ export { InputValidation } from '../../dynamicForm/dtos/InputValidation';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InputValidation = void 0;
4
- class InputValidation {
5
- }
6
- exports.InputValidation = InputValidation;
4
+ // Alias (D-E5): el origen vive en `dynamicForm`. No duplicar.
5
+ var InputValidation_1 = require("../../dynamicForm/dtos/InputValidation");
6
+ Object.defineProperty(exports, "InputValidation", { enumerable: true, get: function () { return InputValidation_1.InputValidation; } });
@@ -1,4 +1 @@
1
- export declare class SelectOption {
2
- value: string;
3
- label: string;
4
- }
1
+ export { SelectOption } from '../../dynamicForm/dtos/SelectOption';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SelectOption = void 0;
4
- class SelectOption {
5
- }
6
- exports.SelectOption = SelectOption;
4
+ // Alias (D-E5): el origen vive en `dynamicForm`. No duplicar.
5
+ var SelectOption_1 = require("../../dynamicForm/dtos/SelectOption");
6
+ Object.defineProperty(exports, "SelectOption", { enumerable: true, get: function () { return SelectOption_1.SelectOption; } });
@@ -1,10 +1 @@
1
- export declare enum InputFieldTypeEnum {
2
- PHONE = "PHONE",
3
- BARCODE = "BARCODE",
4
- ACCOUNT_NUMBER = "ACCOUNT_NUMBER",
5
- TEXT = "TEXT",
6
- NUMBER = "NUMBER",
7
- MONEY = "MONEY",
8
- SELECT = "SELECT",
9
- DATE = "DATE"
10
- }
1
+ export { InputFieldTypeEnum } from '../../dynamicForm/enums/InputFieldTypeEnum';