@fiado/type-kit 3.499.0 → 3.501.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 (79) hide show
  1. package/_test_/unit/cognitoBackofficeConnector/dtos/AuthStartLogin.test.ts +76 -0
  2. package/_test_/unit/platformRbac/auth/dtos.test.ts +33 -0
  3. package/_test_/unit/platformRbac/enums/ChallengeNameEnum.test.ts +17 -0
  4. package/_test_/unit/retailPromotions/constants.test.ts +1 -1
  5. package/_test_/unit/retailPromotions/evaluatedSalePromotion.test.ts +2 -0
  6. package/_test_/unit/retailPromotions/resolveSalePromotionKind.test.ts +2 -0
  7. package/_test_/unit/retailPromotions/salePromotionConditions.test.ts +4 -0
  8. package/_test_/unit/retailPromotions/stackablePromotions.test.ts +238 -0
  9. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginRequest.d.ts +13 -0
  10. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginRequest.js +53 -0
  11. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginResponse.d.ts +22 -0
  12. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginResponse.js +79 -0
  13. package/bin/cognitoBackofficeConnector/index.d.ts +2 -0
  14. package/bin/cognitoBackofficeConnector/index.js +2 -0
  15. package/bin/platformRbac/auth/DefineNextChallengeRequest.d.ts +1 -0
  16. package/bin/platformRbac/auth/DefineNextChallengeRequest.js +5 -0
  17. package/bin/platformRbac/enums/ChallengeNameEnum.d.ts +3 -1
  18. package/bin/platformRbac/enums/ChallengeNameEnum.js +4 -0
  19. package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +1 -0
  20. package/bin/retailPromotions/dtos/LinePromotionAmount.d.ts +6 -0
  21. package/bin/retailPromotions/dtos/LinePromotionAmount.js +36 -0
  22. package/bin/retailPromotions/dtos/LinePromotionDiscount.d.ts +4 -0
  23. package/bin/retailPromotions/dtos/SalePromotion.d.ts +2 -0
  24. package/bin/retailPromotions/dtos/SalePromotionRef.d.ts +5 -0
  25. package/bin/retailPromotions/dtos/SalePromotionRef.js +30 -0
  26. package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.d.ts +2 -0
  27. package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.js +6 -0
  28. package/bin/retailPromotions/dtos/requests/HoldPromotionInput.d.ts +8 -0
  29. package/bin/retailPromotions/dtos/requests/HoldPromotionInput.js +46 -0
  30. package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.d.ts +9 -3
  31. package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.js +11 -0
  32. package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.d.ts +2 -0
  33. package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.js +6 -0
  34. package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.d.ts +20 -0
  35. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +3 -1
  36. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +2 -0
  37. package/bin/retailPromotions/index.d.ts +4 -0
  38. package/bin/retailPromotions/index.js +5 -0
  39. package/bin/retailPromotions/validators/IsHoldModeExclusiveConstraint.d.ts +6 -0
  40. package/bin/retailPromotions/validators/IsHoldModeExclusiveConstraint.js +24 -0
  41. package/bin/retailWizard/dtos/Sale.d.ts +3 -0
  42. package/bin/retailWizard/dtos/WizardLinePromotionFields.d.ts +4 -0
  43. package/bin/retailWizard/dtos/WizardSession.d.ts +3 -0
  44. package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.d.ts +5 -0
  45. package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.js +16 -0
  46. package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.d.ts +3 -0
  47. package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.js +9 -0
  48. package/bin/retailWizard/dtos/responses/PrivateWizardSessionResponse.d.ts +3 -0
  49. package/bin/retailWizard/events/RetailPromotionInboundMessage.d.ts +8 -0
  50. package/bin/retailWizard/events/RetailPromotionInboundMessage.js +22 -0
  51. package/bin/retailWizard/index.d.ts +2 -0
  52. package/bin/retailWizard/index.js +5 -0
  53. package/package.json +1 -1
  54. package/src/cognitoBackofficeConnector/dtos/AuthStartLoginRequest.ts +16 -0
  55. package/src/cognitoBackofficeConnector/dtos/AuthStartLoginResponse.ts +29 -0
  56. package/src/cognitoBackofficeConnector/index.ts +2 -0
  57. package/src/platformRbac/auth/DefineNextChallengeRequest.ts +7 -1
  58. package/src/platformRbac/enums/ChallengeNameEnum.ts +4 -0
  59. package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +1 -0
  60. package/src/retailPromotions/dtos/LinePromotionAmount.ts +20 -0
  61. package/src/retailPromotions/dtos/LinePromotionDiscount.ts +5 -0
  62. package/src/retailPromotions/dtos/SalePromotion.ts +2 -0
  63. package/src/retailPromotions/dtos/SalePromotionRef.ts +15 -0
  64. package/src/retailPromotions/dtos/requests/CreateSalePromotionRequest.ts +6 -0
  65. package/src/retailPromotions/dtos/requests/HoldPromotionInput.ts +30 -0
  66. package/src/retailPromotions/dtos/requests/HoldSalePromotionRequest.ts +28 -4
  67. package/src/retailPromotions/dtos/requests/UpdateSalePromotionRequest.ts +6 -0
  68. package/src/retailPromotions/dtos/responses/WizardSalePromotionResponses.ts +21 -0
  69. package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +2 -0
  70. package/src/retailPromotions/index.ts +6 -0
  71. package/src/retailPromotions/validators/IsHoldModeExclusiveConstraint.ts +18 -0
  72. package/src/retailWizard/dtos/Sale.ts +3 -0
  73. package/src/retailWizard/dtos/WizardLinePromotionFields.ts +4 -0
  74. package/src/retailWizard/dtos/WizardSession.ts +3 -0
  75. package/src/retailWizard/dtos/requests/ApplyLinePromotionRequest.ts +18 -2
  76. package/src/retailWizard/dtos/requests/ApplySalePromotionRequest.ts +11 -2
  77. package/src/retailWizard/dtos/responses/PrivateWizardSessionResponse.ts +3 -0
  78. package/src/retailWizard/events/RetailPromotionInboundMessage.ts +22 -0
  79. package/src/retailWizard/index.ts +2 -0
@@ -0,0 +1,76 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance } from 'class-transformer';
3
+ import { validate } from 'class-validator';
4
+ import { AuthStartLoginRequest } from '../../../../src/cognitoBackofficeConnector/dtos/AuthStartLoginRequest';
5
+ import {
6
+ AuthStartLoginResponse,
7
+ AuthStartLoginTokens,
8
+ } from '../../../../src/cognitoBackofficeConnector/dtos/AuthStartLoginResponse';
9
+ import { CognitoChallengeType } from '../../../../src/cognitoBackofficeConnector/enums/CognitoChallengeType';
10
+
11
+ describe('AuthStartLoginRequest', () => {
12
+ const base = {
13
+ userPoolId: 'us-east-1_abc',
14
+ region: 'us-east-1',
15
+ clientId: 'client-123',
16
+ username: 'sub-123',
17
+ password: 'Secret123!',
18
+ };
19
+
20
+ it('valida happy path', async () => {
21
+ const dto = plainToInstance(AuthStartLoginRequest, base);
22
+ expect(await validate(dto)).toEqual([]);
23
+ });
24
+
25
+ it('falla si falta password', async () => {
26
+ const dto = plainToInstance(AuthStartLoginRequest, { ...base, password: '' });
27
+ expect((await validate(dto)).some((e) => e.property === 'password')).toBe(true);
28
+ });
29
+
30
+ it('falla si clientId no es string', async () => {
31
+ const dto = plainToInstance(AuthStartLoginRequest, { ...base, clientId: 42 });
32
+ expect((await validate(dto)).some((e) => e.property === 'clientId')).toBe(true);
33
+ });
34
+ });
35
+
36
+ describe('AuthStartLoginResponse', () => {
37
+ it('tokens directos (2FA apagado)', async () => {
38
+ const dto = plainToInstance(AuthStartLoginResponse, {
39
+ tokens: {
40
+ accessToken: 'a',
41
+ idToken: 'i',
42
+ refreshToken: 'r',
43
+ expiresIn: 3600,
44
+ tokenType: 'Bearer',
45
+ },
46
+ });
47
+ expect(await validate(dto)).toEqual([]);
48
+ expect(dto.tokens).toBeInstanceOf(AuthStartLoginTokens);
49
+ });
50
+
51
+ it('challenge CUSTOM_CHALLENGE + session + parameters', async () => {
52
+ const dto = plainToInstance(AuthStartLoginResponse, {
53
+ challengeType: CognitoChallengeType.CUSTOM_CHALLENGE,
54
+ session: 'sess-123',
55
+ challengeParameters: { challengeName: 'EMAIL_OTP' },
56
+ });
57
+ expect(await validate(dto)).toEqual([]);
58
+ });
59
+
60
+ it('vacio es valido (todos opcionales)', async () => {
61
+ const dto = plainToInstance(AuthStartLoginResponse, {});
62
+ expect(await validate(dto)).toEqual([]);
63
+ });
64
+
65
+ it('falla si challengeType no es del enum', async () => {
66
+ const dto = plainToInstance(AuthStartLoginResponse, { challengeType: 'BOGUS' });
67
+ expect((await validate(dto)).some((e) => e.property === 'challengeType')).toBe(true);
68
+ });
69
+
70
+ it('falla si expiresIn de tokens no es number', async () => {
71
+ const dto = plainToInstance(AuthStartLoginResponse, {
72
+ tokens: { accessToken: 'a', idToken: 'i', refreshToken: 'r', expiresIn: 'x', tokenType: 'Bearer' },
73
+ });
74
+ expect((await validate(dto)).length).toBeGreaterThan(0);
75
+ });
76
+ });
@@ -39,4 +39,37 @@ describe('DefineNextChallengeRequest', () => {
39
39
  expect(errors.length).toBeGreaterThan(0);
40
40
  expect(errors[0].property).toBe('cognitoSub');
41
41
  });
42
+
43
+ it('acepta userStatus opcional (cognito:user_status, C-06)', async () => {
44
+ const dto = plainToInstance(DefineNextChallengeRequest, {
45
+ cognitoSub: 'abc-123',
46
+ userNotFound: false,
47
+ session: [],
48
+ userStatus: 'FORCE_CHANGE_PASSWORD',
49
+ });
50
+ const errors = await validate(dto);
51
+ expect(errors).toEqual([]);
52
+ expect(dto.userStatus).toBe('FORCE_CHANGE_PASSWORD');
53
+ });
54
+
55
+ it('userStatus ausente sigue siendo valido (aditivo)', async () => {
56
+ const dto = plainToInstance(DefineNextChallengeRequest, {
57
+ cognitoSub: 'abc-123',
58
+ userNotFound: false,
59
+ session: [],
60
+ });
61
+ expect(await validate(dto)).toEqual([]);
62
+ expect(dto.userStatus).toBeUndefined();
63
+ });
64
+
65
+ it('falla si userStatus no es string', async () => {
66
+ const dto = plainToInstance(DefineNextChallengeRequest, {
67
+ cognitoSub: 'abc-123',
68
+ userNotFound: false,
69
+ session: [],
70
+ userStatus: 99,
71
+ });
72
+ const errors = await validate(dto);
73
+ expect(errors.some((e) => e.property === 'userStatus')).toBe(true);
74
+ });
42
75
  });
@@ -0,0 +1,17 @@
1
+ import { ChallengeNameEnum } from '../../../../src/platformRbac/enums/ChallengeNameEnum';
2
+
3
+ describe('ChallengeNameEnum', () => {
4
+ it('incluye SRP_A y PASSWORD_VERIFIER (SRP dentro de CUSTOM_AUTH)', () => {
5
+ expect(ChallengeNameEnum.SRP_A).toBe('SRP_A');
6
+ expect(ChallengeNameEnum.PASSWORD_VERIFIER).toBe('PASSWORD_VERIFIER');
7
+ });
8
+
9
+ it('conserva los valores previos', () => {
10
+ expect(ChallengeNameEnum.PASSWORD).toBe('PASSWORD');
11
+ expect(ChallengeNameEnum.EMAIL_OTP).toBe('EMAIL_OTP');
12
+ expect(ChallengeNameEnum.TOTP).toBe('TOTP');
13
+ expect(ChallengeNameEnum.NEW_PASSWORD_REQUIRED).toBe('NEW_PASSWORD_REQUIRED');
14
+ expect(ChallengeNameEnum.RESET_PASSWORD).toBe('RESET_PASSWORD');
15
+ expect(ChallengeNameEnum.SMS_OTP).toBe('SMS_OTP');
16
+ });
17
+ });
@@ -43,7 +43,7 @@ describe('catálogo de errores', () => {
43
43
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_FOUND).toBe(404);
44
44
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.DISCOUNT_AFTER_QUOTE).toBe(409);
45
45
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.CODE_NOT_OWNED).toBe(422);
46
- expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(30);
46
+ expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(31);
47
47
  expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.SPLIT_FINANCING_DISABLED).toBe(409);
48
48
  });
49
49
 
@@ -19,6 +19,8 @@ const basePromotion: EvaluatedSalePromotion = {
19
19
  giftChooseCount: null,
20
20
  afterSale: [],
21
21
  appliesTo: PromotionAppliesToEnum.SUREKEEP_ONLY,
22
+ stackable: false,
23
+ anchorBound: false,
22
24
  };
23
25
 
24
26
  describe('EvaluatedSalePromotion.blockedBy', () => {
@@ -42,6 +42,8 @@ describe('EvaluatedSalePromotion.kind', () => {
42
42
  giftChooseCount: 1,
43
43
  afterSale: [],
44
44
  appliesTo: PromotionAppliesToEnum.SUREKEEP_ONLY,
45
+ stackable: false,
46
+ anchorBound: false,
45
47
  };
46
48
  expect(promotion.kind).toBe(SalePromotionKindEnum.GIFT);
47
49
  });
@@ -16,6 +16,8 @@ const storeItem: StoreSalePromotionItem = {
16
16
  levers: { gift: null, deviceDiscount: null, accessoryDiscount: null, coupon: null, walletBonus: null, extendedWarranty: null, airtime: null },
17
17
  scope: { storeIds: null, productCategories: null, tiers: null, skus: null },
18
18
  appliesTo: PromotionAppliesToEnum.ANY_FINANCIER,
19
+ stackable: false,
20
+ anchorBound: false,
19
21
  };
20
22
 
21
23
  const evaluated: EvaluatedSalePromotion = {
@@ -31,6 +33,8 @@ const evaluated: EvaluatedSalePromotion = {
31
33
  giftChooseCount: null,
32
34
  afterSale: [],
33
35
  appliesTo: PromotionAppliesToEnum.SUREKEEP_ONLY,
36
+ stackable: false,
37
+ anchorBound: false,
34
38
  };
35
39
 
36
40
  describe('SalePromotionConditions', () => {
@@ -0,0 +1,238 @@
1
+ import 'reflect-metadata';
2
+ import { plainToInstance, type ClassConstructor } from 'class-transformer';
3
+ import { validateSync, type ValidationError } from 'class-validator';
4
+ import {
5
+ CreateSalePromotionRequest,
6
+ HoldPromotionInput,
7
+ HoldSalePromotionRequest,
8
+ LinePromotionAmount,
9
+ RETAIL_PROMOTIONS_ERROR_HTTP_STATUS,
10
+ RetailPromotionsErrorCodeEnum,
11
+ SalePromotionKindEnum,
12
+ SalePromotionRef,
13
+ UpdateSalePromotionRequest,
14
+ type HeldPromotion,
15
+ type HoldSalePromotionResponse,
16
+ type LinePromotionDiscount,
17
+ } from '../../../src/retailPromotions/index';
18
+ import {
19
+ ApplyLinePromotionRequest,
20
+ ApplySalePromotionRequest,
21
+ RetailPromotionInboundMessage,
22
+ RetailPromotionInboundMessageTypeEnum,
23
+ LinePromotionAmount as WizardLinePromotionAmount,
24
+ SalePromotionRef as WizardSalePromotionRef,
25
+ type WizardLinePromotionFields,
26
+ } from '../../../src/retailWizard';
27
+
28
+ function paths(errors: ValidationError[], prefix = ''): string[] {
29
+ return errors.flatMap((e) => {
30
+ const p = prefix ? `${prefix}.${e.property}` : e.property;
31
+ return e.children && e.children.length > 0 ? paths(e.children, p) : [p];
32
+ });
33
+ }
34
+
35
+ function errorsOf<T extends object>(cls: ClassConstructor<T>, payload: object): string[] {
36
+ return paths(validateSync(plainToInstance(cls, payload) as object));
37
+ }
38
+
39
+ const amount = { promotionId: 'P1', promotionVersion: 1, discountCents: 5000 };
40
+
41
+ describe('stackable en el CRUD de promos', () => {
42
+ const create = { name: 'Promo', customerText: 'Texto', levers: {} };
43
+
44
+ it('acepta stackable booleano o ausente', () => {
45
+ expect(errorsOf(CreateSalePromotionRequest, { ...create, stackable: true })).not.toContain('stackable');
46
+ expect(errorsOf(CreateSalePromotionRequest, create)).not.toContain('stackable');
47
+ expect(errorsOf(UpdateSalePromotionRequest, { stackable: false })).toEqual([]);
48
+ });
49
+
50
+ it('rechaza stackable que no es booleano', () => {
51
+ expect(errorsOf(CreateSalePromotionRequest, { ...create, stackable: 'si' })).toContain('stackable');
52
+ expect(errorsOf(UpdateSalePromotionRequest, { stackable: 1 })).toContain('stackable');
53
+ });
54
+ });
55
+
56
+ describe('HoldSalePromotionRequest.promotions', () => {
57
+ const promotions = [
58
+ { promotionId: 'P1', promotionVersion: 2, lineIds: ['L1'], giftSkus: ['SKU-1'] },
59
+ { promotionId: 'P2', promotionVersion: 1 },
60
+ ];
61
+
62
+ it('acepta la lista nueva sola, con o sin código', () => {
63
+ expect(errorsOf(HoldSalePromotionRequest, { promotions })).toEqual([]);
64
+ expect(errorsOf(HoldSalePromotionRequest, { promotions, discountCode: 'BUENFIN' })).toEqual([]);
65
+ });
66
+
67
+ it('sigue aceptando la forma vieja sola', () => {
68
+ expect(
69
+ errorsOf(HoldSalePromotionRequest, {
70
+ gift: { promotionId: 'G1', promotionVersion: 1, skus: ['SKU-1'] },
71
+ promotionId: 'P1',
72
+ promotionVersion: 1,
73
+ saleGiftSkus: ['SKU-2'],
74
+ }),
75
+ ).toEqual([]);
76
+ });
77
+
78
+ it.each([
79
+ ['gift', { gift: { promotionId: 'G1', promotionVersion: 1, skus: ['SKU-1'] } }],
80
+ ['promotionId', { promotionId: 'P1', promotionVersion: 1 }],
81
+ ['saleGiftSkus', { saleGiftSkus: ['SKU-2'] }],
82
+ ])('rechaza mezclar promotions con %s', (_field, legacy) => {
83
+ expect(errorsOf(HoldSalePromotionRequest, { promotions, ...legacy })).toContain('promotions');
84
+ });
85
+
86
+ it('valida cada elemento anidado', () => {
87
+ const found = errorsOf(HoldSalePromotionRequest, {
88
+ promotions: [{ promotionId: '', promotionVersion: 0, lineIds: [''], giftSkus: 'SKU' }],
89
+ });
90
+ expect(found).toEqual(
91
+ expect.arrayContaining([
92
+ 'promotions.0.promotionId',
93
+ 'promotions.0.promotionVersion',
94
+ 'promotions.0.lineIds',
95
+ 'promotions.0.giftSkus',
96
+ ]),
97
+ );
98
+ });
99
+
100
+ it('HoldPromotionInput se valida solo', () => {
101
+ expect(errorsOf(HoldPromotionInput, { promotionId: 'P1', promotionVersion: 1 })).toEqual([]);
102
+ });
103
+ });
104
+
105
+ describe('promotionDiscounts por línea', () => {
106
+ it('ApplyLinePromotionRequest acepta la lista y el descuento del código', () => {
107
+ expect(
108
+ errorsOf(ApplyLinePromotionRequest, {
109
+ promotionId: 'P1',
110
+ promotionVersion: 1,
111
+ discountCents: 7000,
112
+ codeDiscountCents: 2000,
113
+ promotionDiscounts: [amount],
114
+ }),
115
+ ).toEqual([]);
116
+ });
117
+
118
+ it('ApplyLinePromotionRequest valida la lista anidada y el código negativo', () => {
119
+ const found = errorsOf(ApplyLinePromotionRequest, {
120
+ codeDiscountCents: -1,
121
+ promotionDiscounts: [{ promotionId: '', promotionVersion: 0, discountCents: -5 }],
122
+ });
123
+ expect(found).toEqual(
124
+ expect.arrayContaining([
125
+ 'codeDiscountCents',
126
+ 'promotionDiscounts.0.promotionId',
127
+ 'promotionDiscounts.0.promotionVersion',
128
+ 'promotionDiscounts.0.discountCents',
129
+ ]),
130
+ );
131
+ });
132
+
133
+ it('LinePromotionAmount es la misma clase en ambos módulos', () => {
134
+ expect(WizardLinePromotionAmount).toBe(LinePromotionAmount);
135
+ expect(errorsOf(LinePromotionAmount, amount)).toEqual([]);
136
+ });
137
+
138
+ it('las interfaces aceptan la lista por promo', () => {
139
+ const fields: WizardLinePromotionFields = { promotionDiscounts: [amount], codeDiscountCents: 0 };
140
+ const discount: LinePromotionDiscount = {
141
+ lineId: 'L1',
142
+ promotionId: 'P1',
143
+ promotionVersion: 1,
144
+ discountCode: null,
145
+ originalPriceCents: 10000,
146
+ discountCents: 5000,
147
+ finalPriceCents: 5000,
148
+ promotionDiscounts: [amount],
149
+ codeDiscountCents: 0,
150
+ };
151
+ expect(fields.promotionDiscounts).toEqual(discount.promotionDiscounts);
152
+ });
153
+ });
154
+
155
+ describe('salePromotions por venta', () => {
156
+ const ref = { promotionId: 'P1', promotionVersion: 1 };
157
+
158
+ it('ApplySalePromotionRequest acepta la lista y la valida anidada', () => {
159
+ const base = { salePromotionId: null, salePromotionVersion: null };
160
+ expect(errorsOf(ApplySalePromotionRequest, { ...base, salePromotions: [ref] })).toEqual([]);
161
+ expect(errorsOf(ApplySalePromotionRequest, { ...base, salePromotions: null })).toEqual([]);
162
+ expect(
163
+ errorsOf(ApplySalePromotionRequest, { ...base, salePromotions: [{ promotionId: '', promotionVersion: 0 }] }),
164
+ ).toEqual(expect.arrayContaining(['salePromotions.0.promotionId', 'salePromotions.0.promotionVersion']));
165
+ });
166
+
167
+ it('SalePromotionRef es la misma clase en ambos módulos', () => {
168
+ expect(WizardSalePromotionRef).toBe(SalePromotionRef);
169
+ });
170
+
171
+ it('RetailPromotionInboundMessage valida salePromotions y promotionDiscounts de la línea', () => {
172
+ const message = {
173
+ type: RetailPromotionInboundMessageTypeEnum.SALE_CONFIRMED,
174
+ tenantId: 'T',
175
+ retailerId: 'R',
176
+ storeId: 'S',
177
+ saleId: 'V',
178
+ sessionId: 'W',
179
+ customerId: 'C',
180
+ soldAt: '2026-10-06T00:00:00.000Z',
181
+ occurredAt: '2026-10-06T00:00:00.000Z',
182
+ salePromotions: [ref],
183
+ lines: [
184
+ {
185
+ lineId: 'L1',
186
+ sku: 'SKU',
187
+ qty: 1,
188
+ originalPriceCents: 10000,
189
+ identifiers: [],
190
+ promotionDiscounts: [amount],
191
+ codeDiscountCents: 0,
192
+ },
193
+ ],
194
+ };
195
+ expect(errorsOf(RetailPromotionInboundMessage, message)).toEqual([]);
196
+ const bad = {
197
+ ...message,
198
+ salePromotions: [{ promotionId: 'P1' }],
199
+ lines: [{ ...message.lines[0], promotionDiscounts: [{ ...amount, discountCents: 'x' }], codeDiscountCents: -1 }],
200
+ };
201
+ expect(errorsOf(RetailPromotionInboundMessage, bad)).toEqual(
202
+ expect.arrayContaining([
203
+ 'salePromotions.0.promotionVersion',
204
+ 'lines.0.promotionDiscounts.0.discountCents',
205
+ 'lines.0.codeDiscountCents',
206
+ ]),
207
+ );
208
+ });
209
+ });
210
+
211
+ describe('respuesta del apartado y error nuevo', () => {
212
+ it('HoldSalePromotionResponse trae promotions sin quitar los campos viejos', () => {
213
+ const held: HeldPromotion = {
214
+ promotionId: 'P1',
215
+ promotionVersion: 1,
216
+ kind: SalePromotionKindEnum.SALE,
217
+ stackable: true,
218
+ lineIds: ['L1'],
219
+ giftSkus: [],
220
+ };
221
+ const response: HoldSalePromotionResponse = {
222
+ sessionId: 'W',
223
+ gift: null,
224
+ promotionId: null,
225
+ promotionVersion: null,
226
+ discountCode: null,
227
+ saleGiftSkus: [],
228
+ lines: [],
229
+ totalDiscountCents: 0,
230
+ promotions: [held],
231
+ };
232
+ expect(response.promotions).toHaveLength(1);
233
+ });
234
+
235
+ it('PROMOTION_NOT_STACKABLE responde 422', () => {
236
+ expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[RetailPromotionsErrorCodeEnum.PROMOTION_NOT_STACKABLE]).toBe(422);
237
+ });
238
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Request del login de un solo flujo (SRP dentro de CUSTOM_AUTH). El connector
3
+ * hace InitiateAuth(CUSTOM_AUTH, CHALLENGE_NAME=SRP_A) y responde PASSWORD_VERIFIER
4
+ * firmado en la misma operacion. La contraseña nunca viaja en claro a Cognito: SRP
5
+ * la convierte en prueba. El caller (rbac) no conoce el mecanismo.
6
+ */
7
+ export declare class AuthStartLoginRequest {
8
+ userPoolId: string;
9
+ region: string;
10
+ clientId: string;
11
+ username: string;
12
+ password: string;
13
+ }
@@ -0,0 +1,53 @@
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
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AuthStartLoginRequest = void 0;
13
+ const class_transformer_1 = require("class-transformer");
14
+ const class_validator_1 = require("class-validator");
15
+ /**
16
+ * Request del login de un solo flujo (SRP dentro de CUSTOM_AUTH). El connector
17
+ * hace InitiateAuth(CUSTOM_AUTH, CHALLENGE_NAME=SRP_A) y responde PASSWORD_VERIFIER
18
+ * firmado en la misma operacion. La contraseña nunca viaja en claro a Cognito: SRP
19
+ * la convierte en prueba. El caller (rbac) no conoce el mecanismo.
20
+ */
21
+ class AuthStartLoginRequest {
22
+ }
23
+ exports.AuthStartLoginRequest = AuthStartLoginRequest;
24
+ __decorate([
25
+ (0, class_transformer_1.Expose)(),
26
+ (0, class_validator_1.IsString)(),
27
+ (0, class_validator_1.IsNotEmpty)(),
28
+ __metadata("design:type", String)
29
+ ], AuthStartLoginRequest.prototype, "userPoolId", void 0);
30
+ __decorate([
31
+ (0, class_transformer_1.Expose)(),
32
+ (0, class_validator_1.IsString)(),
33
+ (0, class_validator_1.IsNotEmpty)(),
34
+ __metadata("design:type", String)
35
+ ], AuthStartLoginRequest.prototype, "region", void 0);
36
+ __decorate([
37
+ (0, class_transformer_1.Expose)(),
38
+ (0, class_validator_1.IsString)(),
39
+ (0, class_validator_1.IsNotEmpty)(),
40
+ __metadata("design:type", String)
41
+ ], AuthStartLoginRequest.prototype, "clientId", void 0);
42
+ __decorate([
43
+ (0, class_transformer_1.Expose)(),
44
+ (0, class_validator_1.IsString)(),
45
+ (0, class_validator_1.IsNotEmpty)(),
46
+ __metadata("design:type", String)
47
+ ], AuthStartLoginRequest.prototype, "username", void 0);
48
+ __decorate([
49
+ (0, class_transformer_1.Expose)(),
50
+ (0, class_validator_1.IsString)(),
51
+ (0, class_validator_1.IsNotEmpty)(),
52
+ __metadata("design:type", String)
53
+ ], AuthStartLoginRequest.prototype, "password", void 0);
@@ -0,0 +1,22 @@
1
+ import { CognitoChallengeType } from '../enums/CognitoChallengeType';
2
+ export declare class AuthStartLoginTokens {
3
+ accessToken: string;
4
+ idToken: string;
5
+ refreshToken: string;
6
+ expiresIn: number;
7
+ tokenType: string;
8
+ }
9
+ /**
10
+ * Respuesta de `authStartLogin`. Tras verificar el password via SRP
11
+ * (PASSWORD_VERIFIER), Cognito devuelve una de dos cosas: `tokens` si el flujo
12
+ * quedo completo (2FA apagado), o `challengeType` + `session` + `challengeParameters`
13
+ * si falta un paso (NEW_PASSWORD_REQUIRED, o el CUSTOM_CHALLENGE del 2do factor con
14
+ * el sub-challenge real en `challengeParameters.challengeName`). Todos los campos son
15
+ * opcionales: el caller (rbac) decide por que rama sigue.
16
+ */
17
+ export declare class AuthStartLoginResponse {
18
+ tokens?: AuthStartLoginTokens;
19
+ challengeType?: CognitoChallengeType;
20
+ session?: string;
21
+ challengeParameters?: Record<string, string>;
22
+ }
@@ -0,0 +1,79 @@
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
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AuthStartLoginResponse = exports.AuthStartLoginTokens = void 0;
13
+ const class_transformer_1 = require("class-transformer");
14
+ const class_validator_1 = require("class-validator");
15
+ const CognitoChallengeType_1 = require("../enums/CognitoChallengeType");
16
+ class AuthStartLoginTokens {
17
+ }
18
+ exports.AuthStartLoginTokens = AuthStartLoginTokens;
19
+ __decorate([
20
+ (0, class_transformer_1.Expose)(),
21
+ (0, class_validator_1.IsString)(),
22
+ __metadata("design:type", String)
23
+ ], AuthStartLoginTokens.prototype, "accessToken", void 0);
24
+ __decorate([
25
+ (0, class_transformer_1.Expose)(),
26
+ (0, class_validator_1.IsString)(),
27
+ __metadata("design:type", String)
28
+ ], AuthStartLoginTokens.prototype, "idToken", void 0);
29
+ __decorate([
30
+ (0, class_transformer_1.Expose)(),
31
+ (0, class_validator_1.IsString)(),
32
+ __metadata("design:type", String)
33
+ ], AuthStartLoginTokens.prototype, "refreshToken", void 0);
34
+ __decorate([
35
+ (0, class_transformer_1.Expose)(),
36
+ (0, class_validator_1.IsNumber)(),
37
+ __metadata("design:type", Number)
38
+ ], AuthStartLoginTokens.prototype, "expiresIn", void 0);
39
+ __decorate([
40
+ (0, class_transformer_1.Expose)(),
41
+ (0, class_validator_1.IsString)(),
42
+ __metadata("design:type", String)
43
+ ], AuthStartLoginTokens.prototype, "tokenType", void 0);
44
+ /**
45
+ * Respuesta de `authStartLogin`. Tras verificar el password via SRP
46
+ * (PASSWORD_VERIFIER), Cognito devuelve una de dos cosas: `tokens` si el flujo
47
+ * quedo completo (2FA apagado), o `challengeType` + `session` + `challengeParameters`
48
+ * si falta un paso (NEW_PASSWORD_REQUIRED, o el CUSTOM_CHALLENGE del 2do factor con
49
+ * el sub-challenge real en `challengeParameters.challengeName`). Todos los campos son
50
+ * opcionales: el caller (rbac) decide por que rama sigue.
51
+ */
52
+ class AuthStartLoginResponse {
53
+ }
54
+ exports.AuthStartLoginResponse = AuthStartLoginResponse;
55
+ __decorate([
56
+ (0, class_transformer_1.Expose)(),
57
+ (0, class_validator_1.IsOptional)(),
58
+ (0, class_validator_1.ValidateNested)(),
59
+ (0, class_transformer_1.Type)(() => AuthStartLoginTokens),
60
+ __metadata("design:type", AuthStartLoginTokens)
61
+ ], AuthStartLoginResponse.prototype, "tokens", void 0);
62
+ __decorate([
63
+ (0, class_transformer_1.Expose)(),
64
+ (0, class_validator_1.IsOptional)(),
65
+ (0, class_validator_1.IsEnum)(CognitoChallengeType_1.CognitoChallengeType),
66
+ __metadata("design:type", String)
67
+ ], AuthStartLoginResponse.prototype, "challengeType", void 0);
68
+ __decorate([
69
+ (0, class_transformer_1.Expose)(),
70
+ (0, class_validator_1.IsOptional)(),
71
+ (0, class_validator_1.IsString)(),
72
+ __metadata("design:type", String)
73
+ ], AuthStartLoginResponse.prototype, "session", void 0);
74
+ __decorate([
75
+ (0, class_transformer_1.Expose)(),
76
+ (0, class_validator_1.IsOptional)(),
77
+ (0, class_validator_1.IsObject)(),
78
+ __metadata("design:type", Object)
79
+ ], AuthStartLoginResponse.prototype, "challengeParameters", void 0);
@@ -21,6 +21,8 @@ export * from './dtos/UserDetailResponse';
21
21
  export * from './dtos/AuthEventsRequest';
22
22
  export * from './dtos/AuthEventResponse';
23
23
  export * from './dtos/AuthTokensResponse';
24
+ export * from './dtos/AuthStartLoginRequest';
25
+ export * from './dtos/AuthStartLoginResponse';
24
26
  export * from './dtos/InitiateAuthRequest';
25
27
  export * from './dtos/InitiateAuthResponse';
26
28
  export * from './dtos/VerifyPasswordRequest';
@@ -37,6 +37,8 @@ __exportStar(require("./dtos/UserDetailResponse"), exports);
37
37
  __exportStar(require("./dtos/AuthEventsRequest"), exports);
38
38
  __exportStar(require("./dtos/AuthEventResponse"), exports);
39
39
  __exportStar(require("./dtos/AuthTokensResponse"), exports);
40
+ __exportStar(require("./dtos/AuthStartLoginRequest"), exports);
41
+ __exportStar(require("./dtos/AuthStartLoginResponse"), exports);
40
42
  __exportStar(require("./dtos/InitiateAuthRequest"), exports);
41
43
  __exportStar(require("./dtos/InitiateAuthResponse"), exports);
42
44
  __exportStar(require("./dtos/VerifyPasswordRequest"), exports);
@@ -8,4 +8,5 @@ export declare class DefineNextChallengeRequest {
8
8
  cognitoSub: string;
9
9
  userNotFound: boolean;
10
10
  session: ChallengeResultEntry[];
11
+ userStatus?: string;
11
12
  }
@@ -42,3 +42,8 @@ __decorate([
42
42
  (0, class_transformer_1.Type)(() => ChallengeResultEntry),
43
43
  __metadata("design:type", Array)
44
44
  ], DefineNextChallengeRequest.prototype, "session", void 0);
45
+ __decorate([
46
+ (0, class_validator_1.IsOptional)(),
47
+ (0, class_validator_1.IsString)(),
48
+ __metadata("design:type", String)
49
+ ], DefineNextChallengeRequest.prototype, "userStatus", void 0);
@@ -4,5 +4,7 @@ export declare enum ChallengeNameEnum {
4
4
  TOTP = "TOTP",
5
5
  NEW_PASSWORD_REQUIRED = "NEW_PASSWORD_REQUIRED",
6
6
  RESET_PASSWORD = "RESET_PASSWORD",
7
- SMS_OTP = "SMS_OTP"
7
+ SMS_OTP = "SMS_OTP",
8
+ SRP_A = "SRP_A",
9
+ PASSWORD_VERIFIER = "PASSWORD_VERIFIER"
8
10
  }
@@ -9,4 +9,8 @@ var ChallengeNameEnum;
9
9
  ChallengeNameEnum["NEW_PASSWORD_REQUIRED"] = "NEW_PASSWORD_REQUIRED";
10
10
  ChallengeNameEnum["RESET_PASSWORD"] = "RESET_PASSWORD";
11
11
  ChallengeNameEnum["SMS_OTP"] = "SMS_OTP";
12
+ // SRP dentro de CUSTOM_AUTH: entries nativos que Cognito deja en session[] y que
13
+ // el orquestador del rbac lee para decidir el siguiente paso del login de un solo flujo.
14
+ ChallengeNameEnum["SRP_A"] = "SRP_A";
15
+ ChallengeNameEnum["PASSWORD_VERIFIER"] = "PASSWORD_VERIFIER";
12
16
  })(ChallengeNameEnum || (exports.ChallengeNameEnum = ChallengeNameEnum = {}));
@@ -34,4 +34,5 @@ exports.RETAIL_PROMOTIONS_ERROR_HTTP_STATUS = {
34
34
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.INVALID_NEXT_TOKEN]: 400,
35
35
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.CATALOG_SKU_NOT_FOUND]: 404,
36
36
  [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.SPLIT_FINANCING_DISABLED]: 409,
37
+ [RetailPromotionsErrorCodeEnum_1.RetailPromotionsErrorCodeEnum.PROMOTION_NOT_STACKABLE]: 422,
37
38
  };
@@ -0,0 +1,6 @@
1
+ /** Descuento que una promo le aplica a una línea; la suma de la lista + el código da el total. */
2
+ export declare class LinePromotionAmount {
3
+ promotionId: string;
4
+ promotionVersion: number;
5
+ discountCents: number;
6
+ }