@fiado/type-kit 3.564.0 → 3.566.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.
- package/_test_/unit/retailCustomer/customerReferralContract.test.ts +228 -0
- package/_test_/unit/retailPromotions/constants.test.ts +1 -1
- package/_test_/unit/retailPromotions/referralPromotion.test.ts +177 -0
- package/_test_/unit/retailPromotions/referralRewardsByPhones.test.ts +73 -0
- package/_test_/unit/retailPromotions/referralWizardContracts.test.ts +119 -0
- package/_test_/unit/retailPromotions/salePromotionPaymentCheck.test.ts +29 -0
- package/_test_/unit/retailWizard/backofficeCustomerReferrals.test.ts +78 -0
- package/_test_/unit/retailWizard/promotionInboundMessages.test.ts +151 -0
- package/_test_/unit/retailWizard/referralsCapture.test.ts +66 -0
- package/_test_/unit/retailWizard/ventaSagaMessages.test.ts +7 -2
- package/bin/retailCustomer/dtos/CustomerReferral.d.ts +25 -0
- package/bin/retailCustomer/dtos/CustomerReferral.js +99 -0
- package/bin/retailCustomer/dtos/requests/CreateCustomerReferralsRequest.d.ts +19 -0
- package/bin/retailCustomer/dtos/requests/CreateCustomerReferralsRequest.js +101 -0
- package/bin/retailCustomer/dtos/requests/GetCustomersByPhonesRequest.d.ts +4 -0
- package/bin/retailCustomer/dtos/requests/GetCustomersByPhonesRequest.js +28 -0
- package/bin/retailCustomer/dtos/requests/GetReferralsByPhoneQuery.d.ts +6 -0
- package/bin/retailCustomer/dtos/requests/GetReferralsByPhoneQuery.js +39 -0
- package/bin/retailCustomer/dtos/requests/UpdateCustomerReferralRequest.d.ts +8 -0
- package/bin/retailCustomer/dtos/requests/UpdateCustomerReferralRequest.js +53 -0
- package/bin/retailCustomer/dtos/requests/UpsertPromotionBonusRequest.d.ts +4 -0
- package/bin/retailCustomer/dtos/requests/UpsertPromotionBonusRequest.js +15 -0
- package/bin/retailCustomer/dtos/responses/CustomerReferralResponses.d.ts +39 -0
- package/bin/retailCustomer/dtos/responses/CustomerReferralResponses.js +2 -0
- package/bin/retailCustomer/enums/ReferralRoleEnum.d.ts +5 -0
- package/bin/retailCustomer/enums/ReferralRoleEnum.js +9 -0
- package/bin/retailCustomer/enums/ReferralSourceEnum.d.ts +6 -0
- package/bin/retailCustomer/enums/ReferralSourceEnum.js +10 -0
- package/bin/retailCustomer/enums/ReferralStatusEnum.d.ts +7 -0
- package/bin/retailCustomer/enums/ReferralStatusEnum.js +11 -0
- package/bin/retailCustomer/index.d.ts +9 -0
- package/bin/retailCustomer/index.js +9 -0
- package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +2 -0
- package/bin/retailPromotions/dtos/ReferralPromotion.d.ts +33 -0
- package/bin/retailPromotions/dtos/ReferralPromotion.js +2 -0
- package/bin/retailPromotions/dtos/SalePromotion.d.ts +3 -0
- package/bin/retailPromotions/dtos/requests/CheckSalePromotionPaymentRequest.d.ts +6 -0
- package/bin/retailPromotions/dtos/requests/CheckSalePromotionPaymentRequest.js +25 -0
- package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.d.ts +3 -0
- package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.js +8 -0
- package/bin/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.d.ts +2 -0
- package/bin/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.js +7 -0
- package/bin/retailPromotions/dtos/requests/GetReferralRewardsByPhonesRequest.d.ts +5 -0
- package/bin/retailPromotions/dtos/requests/GetReferralRewardsByPhonesRequest.js +34 -0
- package/bin/retailPromotions/dtos/requests/ListLiveReferralPromotionsQuery.d.ts +10 -0
- package/bin/retailPromotions/dtos/requests/ListLiveReferralPromotionsQuery.js +38 -0
- package/bin/retailPromotions/dtos/requests/ListSalePromotionsQuery.d.ts +6 -1
- package/bin/retailPromotions/dtos/requests/ListSalePromotionsQuery.js +15 -1
- package/bin/retailPromotions/dtos/requests/ReferralPromotionInput.d.ts +24 -0
- package/bin/retailPromotions/dtos/requests/ReferralPromotionInput.js +119 -0
- package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.d.ts +3 -0
- package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.js +8 -0
- package/bin/retailPromotions/dtos/responses/LiveReferralPromotionsResponse.d.ts +24 -0
- package/bin/retailPromotions/dtos/responses/LiveReferralPromotionsResponse.js +2 -0
- package/bin/retailPromotions/dtos/responses/ReferralRewardsByPhonesResponses.d.ts +25 -0
- package/bin/retailPromotions/dtos/responses/ReferralRewardsByPhonesResponses.js +2 -0
- package/bin/retailPromotions/dtos/responses/SalePromotionPaymentCheckResponses.d.ts +16 -0
- package/bin/retailPromotions/dtos/responses/SalePromotionPaymentCheckResponses.js +2 -0
- package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.d.ts +13 -0
- package/bin/retailPromotions/enums/ReferralPromotionChannelEnum.d.ts +5 -0
- package/bin/retailPromotions/enums/ReferralPromotionChannelEnum.js +9 -0
- package/bin/retailPromotions/enums/ReferralRewardStatusEnum.d.ts +9 -0
- package/bin/retailPromotions/enums/ReferralRewardStatusEnum.js +13 -0
- package/bin/retailPromotions/enums/ReferralRewardTypeEnum.d.ts +10 -0
- package/bin/retailPromotions/enums/ReferralRewardTypeEnum.js +14 -0
- package/bin/retailPromotions/enums/ReferralTriggerEnum.d.ts +13 -0
- package/bin/retailPromotions/enums/ReferralTriggerEnum.js +17 -0
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +5 -1
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +4 -0
- package/bin/retailPromotions/enums/SalePromotionKindEnum.d.ts +3 -2
- package/bin/retailPromotions/enums/SalePromotionKindEnum.js +2 -1
- package/bin/retailPromotions/index.d.ts +12 -0
- package/bin/retailPromotions/index.js +12 -0
- package/bin/retailWizard/dtos/responses/BackofficeCustomerReferralsResponse.d.ts +58 -0
- package/bin/retailWizard/dtos/responses/BackofficeCustomerReferralsResponse.js +2 -0
- package/bin/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.d.ts +4 -2
- package/bin/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.js +3 -1
- package/bin/retailWizard/enums/WizardStepEnum.d.ts +3 -1
- package/bin/retailWizard/enums/WizardStepEnum.js +2 -0
- package/bin/retailWizard/events/RetailCommissionInboundMessage.d.ts +7 -0
- package/bin/retailWizard/events/RetailCommissionInboundMessage.js +22 -1
- package/bin/retailWizard/events/RetailPromotionAccountOpenedMessage.d.ts +15 -0
- package/bin/retailWizard/events/RetailPromotionAccountOpenedMessage.js +56 -0
- package/bin/retailWizard/events/RetailPromotionInboundMessage.d.ts +15 -0
- package/bin/retailWizard/events/RetailPromotionInboundMessage.js +41 -1
- package/bin/retailWizard/events/RetailPromotionInstallmentPaidMessage.d.ts +20 -0
- package/bin/retailWizard/events/RetailPromotionInstallmentPaidMessage.js +75 -0
- package/bin/retailWizard/events/RetailPromotionQueueMessage.d.ts +5 -0
- package/bin/retailWizard/events/RetailPromotionQueueMessage.js +2 -0
- package/bin/retailWizard/index.d.ts +4 -0
- package/bin/retailWizard/index.js +4 -0
- package/package.json +1 -1
- package/src/retailCustomer/dtos/CustomerReferral.ts +77 -0
- package/src/retailCustomer/dtos/requests/CreateCustomerReferralsRequest.ts +91 -0
- package/src/retailCustomer/dtos/requests/GetCustomersByPhonesRequest.ts +15 -0
- package/src/retailCustomer/dtos/requests/GetReferralsByPhoneQuery.ts +23 -0
- package/src/retailCustomer/dtos/requests/UpdateCustomerReferralRequest.ts +38 -0
- package/src/retailCustomer/dtos/requests/UpsertPromotionBonusRequest.ts +15 -1
- package/src/retailCustomer/dtos/responses/CustomerReferralResponses.ts +46 -0
- package/src/retailCustomer/enums/ReferralRoleEnum.ts +5 -0
- package/src/retailCustomer/enums/ReferralSourceEnum.ts +6 -0
- package/src/retailCustomer/enums/ReferralStatusEnum.ts +7 -0
- package/src/retailCustomer/index.ts +9 -0
- package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +2 -0
- package/src/retailPromotions/dtos/ReferralPromotion.ts +35 -0
- package/src/retailPromotions/dtos/SalePromotion.ts +3 -0
- package/src/retailPromotions/dtos/requests/CheckSalePromotionPaymentRequest.ts +12 -0
- package/src/retailPromotions/dtos/requests/CreateSalePromotionRequest.ts +8 -0
- package/src/retailPromotions/dtos/requests/EvaluateSalePromotionsRequest.ts +7 -0
- package/src/retailPromotions/dtos/requests/GetReferralRewardsByPhonesRequest.ts +20 -0
- package/src/retailPromotions/dtos/requests/ListLiveReferralPromotionsQuery.ts +24 -0
- package/src/retailPromotions/dtos/requests/ListSalePromotionsQuery.ts +14 -1
- package/src/retailPromotions/dtos/requests/ReferralPromotionInput.ts +93 -0
- package/src/retailPromotions/dtos/requests/UpdateSalePromotionRequest.ts +8 -0
- package/src/retailPromotions/dtos/responses/LiveReferralPromotionsResponse.ts +26 -0
- package/src/retailPromotions/dtos/responses/ReferralRewardsByPhonesResponses.ts +28 -0
- package/src/retailPromotions/dtos/responses/SalePromotionPaymentCheckResponses.ts +17 -0
- package/src/retailPromotions/dtos/responses/WizardSalePromotionResponses.ts +15 -0
- package/src/retailPromotions/enums/ReferralPromotionChannelEnum.ts +5 -0
- package/src/retailPromotions/enums/ReferralRewardStatusEnum.ts +9 -0
- package/src/retailPromotions/enums/ReferralRewardTypeEnum.ts +10 -0
- package/src/retailPromotions/enums/ReferralTriggerEnum.ts +13 -0
- package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +4 -0
- package/src/retailPromotions/enums/SalePromotionKindEnum.ts +2 -1
- package/src/retailPromotions/index.ts +12 -0
- package/src/retailWizard/dtos/responses/BackofficeCustomerReferralsResponse.ts +63 -0
- package/src/retailWizard/enums/RetailPromotionInboundMessageTypeEnum.ts +3 -1
- package/src/retailWizard/enums/WizardStepEnum.ts +2 -0
- package/src/retailWizard/events/RetailCommissionInboundMessage.ts +18 -0
- package/src/retailWizard/events/RetailPromotionAccountOpenedMessage.ts +38 -0
- package/src/retailWizard/events/RetailPromotionInboundMessage.ts +43 -1
- package/src/retailWizard/events/RetailPromotionInstallmentPaidMessage.ts +53 -0
- package/src/retailWizard/events/RetailPromotionQueueMessage.ts +9 -0
- package/src/retailWizard/index.ts +4 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { ValidationError, validateSync } from 'class-validator';
|
|
4
|
+
import { RetailCustomer } from '../../../src/index';
|
|
5
|
+
import {
|
|
6
|
+
AccountStatusEnum,
|
|
7
|
+
CreateCustomerReferralsRequest,
|
|
8
|
+
CustomerReferral,
|
|
9
|
+
GetCustomersByPhonesRequest,
|
|
10
|
+
GetReferralsByPhoneQuery,
|
|
11
|
+
ReferralRoleEnum,
|
|
12
|
+
ReferralSourceEnum,
|
|
13
|
+
ReferralStatusEnum,
|
|
14
|
+
UpdateCustomerReferralRequest,
|
|
15
|
+
UpsertPromotionBonusRequest,
|
|
16
|
+
} from '../../../src/retailCustomer/index';
|
|
17
|
+
import type {
|
|
18
|
+
CreateCustomerReferralsResponse,
|
|
19
|
+
CustomersByPhonesResponse,
|
|
20
|
+
ListCustomerReferralsResponse,
|
|
21
|
+
ReferralsByPhoneResponse,
|
|
22
|
+
UpdateCustomerReferralResponse,
|
|
23
|
+
} from '../../../src/retailCustomer/index';
|
|
24
|
+
|
|
25
|
+
/** Rutas `propiedad` o `lista.0.propiedad` de los errores, como las lee el lambda para armar el renglón. */
|
|
26
|
+
function pathsOf(errors: ValidationError[], prefix = ''): string[] {
|
|
27
|
+
return errors.flatMap((e) => {
|
|
28
|
+
const path = `${prefix}${e.property}`;
|
|
29
|
+
return e.children && e.children.length > 0 ? pathsOf(e.children, `${path}.`) : [path];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function errorsOf<T extends object>(cls: new () => T, payload: Record<string, unknown>): string[] {
|
|
34
|
+
const dto = plainToInstance(cls, payload, { excludeExtraneousValues: true });
|
|
35
|
+
return pathsOf(validateSync(dto));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('enums de referidos', () => {
|
|
39
|
+
it('ReferralSourceEnum distingue venta, apertura y cajón', () => {
|
|
40
|
+
expect(Object.values(ReferralSourceEnum)).toEqual(['SALE', 'OPENING', 'BACKOFFICE']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('ReferralStatusEnum trae los cuatro estados del cajón', () => {
|
|
44
|
+
expect(Object.values(ReferralStatusEnum)).toEqual([
|
|
45
|
+
'NOT_PURCHASED',
|
|
46
|
+
'ACCOUNT_OPENED',
|
|
47
|
+
'PURCHASED',
|
|
48
|
+
'SUREKEEP_CREDIT',
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('ReferralRoleEnum distingue a quien recomienda del recomendado', () => {
|
|
53
|
+
expect(Object.values(ReferralRoleEnum)).toEqual(['REFERRER', 'REFERRED']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('salen por el index raíz', () => {
|
|
57
|
+
expect(RetailCustomer.ReferralSourceEnum.BACKOFFICE).toBe('BACKOFFICE');
|
|
58
|
+
expect(new RetailCustomer.CreateCustomerReferralsRequest()).toBeInstanceOf(CreateCustomerReferralsRequest);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const SALE_BATCH = {
|
|
63
|
+
source: 'SALE',
|
|
64
|
+
storeId: 'st-1',
|
|
65
|
+
wizardSessionId: 'ws-1',
|
|
66
|
+
capturedBy: 'seller-1',
|
|
67
|
+
referrals: [{ name: 'Ana López', phone: '55 1234 5678' }],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
describe('CreateCustomerReferralsRequest', () => {
|
|
71
|
+
it('acepta un lote de la venta con correo opcional ausente', () => {
|
|
72
|
+
expect(errorsOf(CreateCustomerReferralsRequest, SALE_BATCH)).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('acepta el correo cuando viene', () => {
|
|
76
|
+
const referrals = [{ name: 'Ana', phone: '5512345678', email: 'ana@correo.mx' }];
|
|
77
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...SALE_BATCH, referrals })).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('marca el renglón y el campo que faltan', () => {
|
|
81
|
+
const referrals = [{ name: 'Ana', phone: '5512345678' }, { name: ' ', phone: '' }];
|
|
82
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...SALE_BATCH, referrals })).toEqual([
|
|
83
|
+
'referrals.1.name',
|
|
84
|
+
'referrals.1.phone',
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('rechaza un correo inválido en su renglón', () => {
|
|
89
|
+
const referrals = [{ name: 'Ana', phone: '5512345678', email: 'no-es-correo' }];
|
|
90
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...SALE_BATCH, referrals })).toEqual(['referrals.0.email']);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('rechaza el lote vacío', () => {
|
|
94
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...SALE_BATCH, referrals: [] })).toContain('referrals');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('exige quién capturó, sin default', () => {
|
|
98
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...SALE_BATCH, capturedBy: ' ' })).toContain('capturedBy');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('rechaza un origen fuera del catálogo', () => {
|
|
102
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...SALE_BATCH, source: 'WEB' })).toContain('source');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('venta y apertura exigen la tienda; el cajón no', () => {
|
|
106
|
+
const { storeId: _storeId, ...noStore } = SALE_BATCH;
|
|
107
|
+
expect(errorsOf(CreateCustomerReferralsRequest, noStore)).toContain('storeId');
|
|
108
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...noStore, source: 'OPENING' })).toContain('storeId');
|
|
109
|
+
expect(errorsOf(CreateCustomerReferralsRequest, { ...noStore, source: 'BACKOFFICE' })).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('UpdateCustomerReferralRequest', () => {
|
|
114
|
+
it('acepta corregir solo el teléfono', () => {
|
|
115
|
+
expect(errorsOf(UpdateCustomerReferralRequest, { phone: '5512345678', updatedBy: 'admin-1' })).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('acepta quitar el correo con null', () => {
|
|
119
|
+
expect(errorsOf(UpdateCustomerReferralRequest, { email: null, updatedBy: 'admin-1' })).toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('rechaza vaciar el nombre o el teléfono', () => {
|
|
123
|
+
const errors = errorsOf(UpdateCustomerReferralRequest, { name: ' ', phone: '', updatedBy: 'admin-1' });
|
|
124
|
+
expect(errors).toEqual(['name', 'phone']);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('exige quién edita', () => {
|
|
128
|
+
expect(errorsOf(UpdateCustomerReferralRequest, { name: 'Ana' })).toContain('updatedBy');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('GetCustomersByPhonesRequest', () => {
|
|
133
|
+
it('acepta un lote de teléfonos', () => {
|
|
134
|
+
expect(errorsOf(GetCustomersByPhonesRequest, { phones: ['5512345678', '5587654321'] })).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('rechaza el lote vacío o de más de 100', () => {
|
|
138
|
+
expect(errorsOf(GetCustomersByPhonesRequest, { phones: [] })).toContain('phones');
|
|
139
|
+
const many = Array.from({ length: 101 }, (_, i) => String(5500000000 + i));
|
|
140
|
+
expect(errorsOf(GetCustomersByPhonesRequest, { phones: many })).toContain('phones');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('GetReferralsByPhoneQuery', () => {
|
|
145
|
+
it('acepta cadena y teléfono, con el corte opcional', () => {
|
|
146
|
+
expect(errorsOf(GetReferralsByPhoneQuery, { retailerId: 'r-1', phone: '5512345678' })).toEqual([]);
|
|
147
|
+
expect(
|
|
148
|
+
errorsOf(GetReferralsByPhoneQuery, { retailerId: 'r-1', phone: '5512345678', before: '2026-10-21T10:00:00Z' }),
|
|
149
|
+
).toEqual([]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('rechaza un corte que no es fecha ISO', () => {
|
|
153
|
+
expect(errorsOf(GetReferralsByPhoneQuery, { retailerId: 'r-1', phone: '5512345678', before: 'ayer' })).toEqual([
|
|
154
|
+
'before',
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('CustomerReferral y respuestas', () => {
|
|
160
|
+
const referral: CustomerReferral = {
|
|
161
|
+
referralId: 'ref-1',
|
|
162
|
+
retailCustomerId: 'c-1',
|
|
163
|
+
retailerId: 'r-1',
|
|
164
|
+
storeId: 'st-1',
|
|
165
|
+
name: 'Ana López',
|
|
166
|
+
phone: '5512345678',
|
|
167
|
+
email: null,
|
|
168
|
+
source: ReferralSourceEnum.SALE,
|
|
169
|
+
wizardSessionId: 'ws-1',
|
|
170
|
+
saleId: 'sale-1',
|
|
171
|
+
accountOpeningSessionId: null,
|
|
172
|
+
createdBy: 'seller-1',
|
|
173
|
+
updatedBy: 'seller-1',
|
|
174
|
+
createdAt: '2026-10-21T10:00:00Z',
|
|
175
|
+
updatedAt: '2026-10-21T10:00:00Z',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
it('valida la entidad con @Expose', () => {
|
|
179
|
+
expect(errorsOf(CustomerReferral, { ...referral })).toEqual([]);
|
|
180
|
+
expect(errorsOf(CustomerReferral, { ...referral, source: 'WEB' })).toEqual(['source']);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('describe las respuestas de las rutas privadas', () => {
|
|
184
|
+
const created: CreateCustomerReferralsResponse = { referrals: [referral] };
|
|
185
|
+
const updated: UpdateCustomerReferralResponse = { referral };
|
|
186
|
+
const listed: ListCustomerReferralsResponse = { referrals: [referral] };
|
|
187
|
+
const byPhone: ReferralsByPhoneResponse = {
|
|
188
|
+
referrals: [
|
|
189
|
+
{
|
|
190
|
+
referralId: 'ref-1',
|
|
191
|
+
referrerCustomerId: 'c-1',
|
|
192
|
+
referrerName: 'Juan Pérez',
|
|
193
|
+
source: ReferralSourceEnum.SALE,
|
|
194
|
+
capturedAt: '2026-10-21T10:00:00Z',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
};
|
|
198
|
+
const customers: CustomersByPhonesResponse = {
|
|
199
|
+
customers: [{ phone: '5512345678', retailCustomerId: 'c-9', retailerId: 'r-1', accountStatus: AccountStatusEnum.OPEN }],
|
|
200
|
+
};
|
|
201
|
+
expect([created, updated, listed, byPhone, customers]).toHaveLength(5);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('UpsertPromotionBonusRequest con referido', () => {
|
|
206
|
+
const PENDING_SALE = {
|
|
207
|
+
bonusType: 'SALE_PROMOTION',
|
|
208
|
+
promotionId: 'promo-1',
|
|
209
|
+
sourceId: 'sale-1',
|
|
210
|
+
storeId: 'st-1',
|
|
211
|
+
amountCents: 10_000,
|
|
212
|
+
currency: 'MXN',
|
|
213
|
+
status: 'PENDING',
|
|
214
|
+
idempotencyKey: 'sale-1-WALLET-1',
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
it('acepta rol y referralId opcionales', () => {
|
|
218
|
+
expect(errorsOf(UpsertPromotionBonusRequest, PENDING_SALE)).toEqual([]);
|
|
219
|
+
expect(
|
|
220
|
+
errorsOf(UpsertPromotionBonusRequest, { ...PENDING_SALE, role: 'REFERRER', referralId: 'ref-1' }),
|
|
221
|
+
).toEqual([]);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('rechaza un rol fuera del catálogo o un referralId vacío', () => {
|
|
225
|
+
expect(errorsOf(UpsertPromotionBonusRequest, { ...PENDING_SALE, role: 'SELLER' })).toEqual(['role']);
|
|
226
|
+
expect(errorsOf(UpsertPromotionBonusRequest, { ...PENDING_SALE, referralId: ' ' })).toEqual(['referralId']);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
@@ -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(
|
|
46
|
+
expect(Object.keys(RetailPromotionsErrorCodeEnum)).toHaveLength(36);
|
|
47
47
|
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS.SPLIT_FINANCING_DISABLED).toBe(409);
|
|
48
48
|
});
|
|
49
49
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { ValidationError, validateSync } from 'class-validator';
|
|
4
|
+
import { RetailPromotions } from '../../../src/index';
|
|
5
|
+
import {
|
|
6
|
+
CreateSalePromotionRequest,
|
|
7
|
+
ListSalePromotionsQuery,
|
|
8
|
+
RETAIL_PROMOTIONS_ERROR_HTTP_STATUS,
|
|
9
|
+
ReferralPromotionChannelEnum,
|
|
10
|
+
ReferralPromotionInput,
|
|
11
|
+
ReferralRewardTypeEnum,
|
|
12
|
+
ReferralTriggerEnum,
|
|
13
|
+
RetailPromotionsErrorCodeEnum,
|
|
14
|
+
SalePromotionKindEnum,
|
|
15
|
+
UpdateSalePromotionRequest,
|
|
16
|
+
} from '../../../src/retailPromotions/index';
|
|
17
|
+
import type { ReferralPromotionConfig, SalePromotion } from '../../../src/retailPromotions/index';
|
|
18
|
+
|
|
19
|
+
function pathsOf(errors: ValidationError[], prefix = ''): string[] {
|
|
20
|
+
return errors.flatMap((e) => {
|
|
21
|
+
const path = `${prefix}${e.property}`;
|
|
22
|
+
return e.children && e.children.length > 0 ? pathsOf(e.children, `${path}.`) : [path];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const errorsOf = <T extends object>(cls: new () => T, payload: Record<string, unknown>): string[] =>
|
|
27
|
+
pathsOf(validateSync(plainToInstance(cls, payload, { excludeExtraneousValues: true })));
|
|
28
|
+
|
|
29
|
+
describe('enums de promos de referido', () => {
|
|
30
|
+
it('SalePromotionKindEnum suma REFERRAL', () => {
|
|
31
|
+
expect(Object.values(SalePromotionKindEnum)).toEqual(['GIFT', 'SALE', 'REFERRAL']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('canal de venta o de crédito', () => {
|
|
35
|
+
expect(Object.values(ReferralPromotionChannelEnum)).toEqual(['SALE', 'CREDIT']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('los 7 eventos del referido', () => {
|
|
39
|
+
expect(Object.values(ReferralTriggerEnum)).toEqual([
|
|
40
|
+
'FIRST_PURCHASE',
|
|
41
|
+
'ACCOUNT_OPENED',
|
|
42
|
+
'ANY_PURCHASE',
|
|
43
|
+
'NTH_PURCHASE',
|
|
44
|
+
'CREDIT_ACTIVATED',
|
|
45
|
+
'FIRST_INSTALLMENT',
|
|
46
|
+
'INSTALLMENT_STREAK',
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('bono fijo, % con tope, cupón y descuento en la venta', () => {
|
|
51
|
+
expect(Object.values(ReferralRewardTypeEnum)).toEqual(['FIXED_BONUS', 'PERCENT_BONUS', 'COUPON', 'SALE_DISCOUNT']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('codes de validación de la promo de referido', () => {
|
|
55
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[RetailPromotionsErrorCodeEnum.REFERRAL_REWARD_NEEDS_PURCHASE]).toBe(422);
|
|
56
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[RetailPromotionsErrorCodeEnum.INVALID_REFERRAL_TRIGGER]).toBe(400);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('salen por el index raíz', () => {
|
|
60
|
+
expect(RetailPromotions.ReferralTriggerEnum.INSTALLMENT_STREAK).toBe('INSTALLMENT_STREAK');
|
|
61
|
+
expect(new RetailPromotions.ReferralPromotionInput()).toBeInstanceOf(ReferralPromotionInput);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const FIXED = { type: 'FIXED_BONUS', amountCents: 200_00 };
|
|
66
|
+
const REFERRAL = {
|
|
67
|
+
channel: 'SALE',
|
|
68
|
+
trigger: 'FIRST_PURCHASE',
|
|
69
|
+
minAmountCents: 3_000_00,
|
|
70
|
+
windowDays: 30,
|
|
71
|
+
maxRewardsPerReferrer: 5,
|
|
72
|
+
referrerReward: FIXED,
|
|
73
|
+
referredReward: { type: 'SALE_DISCOUNT', percentBps: 1000, maxAmountCents: 500_00 },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
describe('ReferralPromotionInput', () => {
|
|
77
|
+
it('acepta una promo de venta con premio para los dos', () => {
|
|
78
|
+
expect(errorsOf(ReferralPromotionInput, REFERRAL)).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('acepta una promo de crédito con racha y sin premio al recomendado', () => {
|
|
82
|
+
const credit = {
|
|
83
|
+
channel: 'CREDIT',
|
|
84
|
+
trigger: 'INSTALLMENT_STREAK',
|
|
85
|
+
installmentStreak: 3,
|
|
86
|
+
referrerReward: { type: 'PERCENT_BONUS', percentBps: 500, maxAmountCents: 300_00 },
|
|
87
|
+
referredReward: null,
|
|
88
|
+
};
|
|
89
|
+
expect(errorsOf(ReferralPromotionInput, credit)).toEqual([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('acepta el cupón como premio de quien recomienda', () => {
|
|
93
|
+
const coupon = { type: 'PERCENTAGE', percentBps: 1500, validityDays: 30, scope: 'CHAIN' };
|
|
94
|
+
const referrerReward = { type: 'COUPON', coupon };
|
|
95
|
+
expect(errorsOf(ReferralPromotionInput, { ...REFERRAL, referrerReward })).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('exige canal, evento y premio de quien recomienda', () => {
|
|
99
|
+
expect(errorsOf(ReferralPromotionInput, {})).toEqual(['channel', 'trigger', 'referrerReward']);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('exige el dato que corresponde a cada premio', () => {
|
|
103
|
+
expect(errorsOf(ReferralPromotionInput, { ...REFERRAL, referrerReward: { type: 'FIXED_BONUS' } })).toEqual([
|
|
104
|
+
'referrerReward.amountCents',
|
|
105
|
+
]);
|
|
106
|
+
expect(errorsOf(ReferralPromotionInput, { ...REFERRAL, referrerReward: { type: 'PERCENT_BONUS' } })).toEqual([
|
|
107
|
+
'referrerReward.percentBps',
|
|
108
|
+
]);
|
|
109
|
+
expect(errorsOf(ReferralPromotionInput, { ...REFERRAL, referrerReward: { type: 'COUPON' } })).toEqual([
|
|
110
|
+
'referrerReward.coupon',
|
|
111
|
+
]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('rechaza un % fuera de 1..10000 y montos no positivos', () => {
|
|
115
|
+
const pct = { type: 'PERCENT_BONUS', percentBps: 10001, maxAmountCents: 0 };
|
|
116
|
+
expect(errorsOf(ReferralPromotionInput, { ...REFERRAL, referrerReward: pct })).toEqual([
|
|
117
|
+
'referrerReward.percentBps',
|
|
118
|
+
'referrerReward.maxAmountCents',
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('rechaza ventana, máximo y monto mínimo negativos o cero', () => {
|
|
123
|
+
const bad = { ...REFERRAL, minAmountCents: -1, windowDays: 0, maxRewardsPerReferrer: 0 };
|
|
124
|
+
expect(errorsOf(ReferralPromotionInput, bad)).toEqual(['minAmountCents', 'windowDays', 'maxRewardsPerReferrer']);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('deja al lambda el mínimo de 2 en compra N y racha (INVALID_REFERRAL_TRIGGER)', () => {
|
|
128
|
+
const nth = { ...REFERRAL, trigger: 'NTH_PURCHASE', purchaseNumber: 1 };
|
|
129
|
+
expect(errorsOf(ReferralPromotionInput, nth)).toEqual([]);
|
|
130
|
+
expect(errorsOf(ReferralPromotionInput, { ...nth, purchaseNumber: 1.5 })).toEqual(['purchaseNumber']);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('promo de referido en el CRUD de promos', () => {
|
|
135
|
+
const CREATE = { name: 'Trae a un amigo', customerText: 'Gana $200', levers: {}, referral: REFERRAL };
|
|
136
|
+
|
|
137
|
+
it('CreateSalePromotionRequest acepta el bloque referral', () => {
|
|
138
|
+
expect(errorsOf(CreateSalePromotionRequest, CREATE)).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('CreateSalePromotionRequest valida el bloque referral anidado', () => {
|
|
142
|
+
expect(errorsOf(CreateSalePromotionRequest, { ...CREATE, referral: { ...REFERRAL, channel: 'APP' } })).toEqual([
|
|
143
|
+
'referral.channel',
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('UpdateSalePromotionRequest acepta el bloque referral', () => {
|
|
148
|
+
expect(errorsOf(UpdateSalePromotionRequest, { referral: REFERRAL })).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('ListSalePromotionsQuery filtra por kind y canal', () => {
|
|
152
|
+
expect(errorsOf(ListSalePromotionsQuery, { kind: 'REFERRAL', channel: 'CREDIT' })).toEqual([]);
|
|
153
|
+
expect(errorsOf(ListSalePromotionsQuery, { kind: 'X', channel: 'Y' })).toEqual(['kind', 'channel']);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('SalePromotion trae la configuración de referido opcional', () => {
|
|
157
|
+
const referral: ReferralPromotionConfig = {
|
|
158
|
+
channel: ReferralPromotionChannelEnum.CREDIT,
|
|
159
|
+
trigger: ReferralTriggerEnum.FIRST_INSTALLMENT,
|
|
160
|
+
purchaseNumber: null,
|
|
161
|
+
installmentStreak: null,
|
|
162
|
+
minAmountCents: null,
|
|
163
|
+
windowDays: 60,
|
|
164
|
+
maxRewardsPerReferrer: null,
|
|
165
|
+
referrerReward: {
|
|
166
|
+
type: ReferralRewardTypeEnum.FIXED_BONUS,
|
|
167
|
+
amountCents: 100_00,
|
|
168
|
+
percentBps: null,
|
|
169
|
+
maxAmountCents: null,
|
|
170
|
+
coupon: null,
|
|
171
|
+
},
|
|
172
|
+
referredReward: null,
|
|
173
|
+
};
|
|
174
|
+
const promo: Pick<SalePromotion, 'promotionId' | 'referral'> = { promotionId: 'p-1', referral };
|
|
175
|
+
expect(promo.referral?.trigger).toBe('FIRST_INSTALLMENT');
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import { RetailPromotions } from '../../../src/index';
|
|
5
|
+
import {
|
|
6
|
+
GetReferralRewardsByPhonesRequest,
|
|
7
|
+
ReferralRewardStatusEnum,
|
|
8
|
+
ReferralRewardTypeEnum,
|
|
9
|
+
} from '../../../src/retailPromotions/index';
|
|
10
|
+
import type { ReferralRewardsByPhonesResponse } from '../../../src/retailPromotions/index';
|
|
11
|
+
import { ReferralRoleEnum } from '../../../src/retailCustomer/index';
|
|
12
|
+
|
|
13
|
+
function errorsOf(payload: Record<string, unknown>): string[] {
|
|
14
|
+
const dto = plainToInstance(GetReferralRewardsByPhonesRequest, payload, { excludeExtraneousValues: true });
|
|
15
|
+
return validateSync(dto).map((e) => e.property);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const phonesOf = (n: number): string[] => Array.from({ length: n }, (_, i) => `55${String(i).padStart(8, '0')}`);
|
|
19
|
+
|
|
20
|
+
describe('ReferralRewardStatusEnum', () => {
|
|
21
|
+
it('trae pagado, pendiente y no aplica', () => {
|
|
22
|
+
expect(Object.values(ReferralRewardStatusEnum)).toEqual(['PAID', 'PENDING', 'NOT_APPLICABLE']);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('GetReferralRewardsByPhonesRequest', () => {
|
|
27
|
+
it('acepta cadena y lote de teléfonos', () => {
|
|
28
|
+
expect(errorsOf({ retailerId: 'ret_1', phones: ['5512345678'] })).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('exige la cadena', () => {
|
|
32
|
+
expect(errorsOf({ phones: ['5512345678'] })).toEqual(['retailerId']);
|
|
33
|
+
expect(errorsOf({ retailerId: '', phones: ['5512345678'] })).toEqual(['retailerId']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rechaza el lote vacío, de más de 100 o con teléfonos vacíos', () => {
|
|
37
|
+
expect(errorsOf({ retailerId: 'ret_1', phones: [] })).toEqual(['phones']);
|
|
38
|
+
expect(errorsOf({ retailerId: 'ret_1', phones: phonesOf(101) })).toEqual(['phones']);
|
|
39
|
+
expect(errorsOf({ retailerId: 'ret_1', phones: [''] })).toEqual(['phones']);
|
|
40
|
+
expect(errorsOf({ retailerId: 'ret_1', phones: phonesOf(100) })).toEqual([]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('sale por el index raíz', () => {
|
|
44
|
+
expect(new RetailPromotions.GetReferralRewardsByPhonesRequest()).toBeInstanceOf(
|
|
45
|
+
GetReferralRewardsByPhonesRequest,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('ReferralRewardsByPhonesResponse', () => {
|
|
51
|
+
it('agrupa los premios por teléfono con rol, tipo, monto, estado y fecha', () => {
|
|
52
|
+
const response: ReferralRewardsByPhonesResponse = {
|
|
53
|
+
phones: [
|
|
54
|
+
{
|
|
55
|
+
phone: '5512345678',
|
|
56
|
+
rewards: [
|
|
57
|
+
{
|
|
58
|
+
promotionId: 'promo_1',
|
|
59
|
+
promotionName: 'Recomienda y gana',
|
|
60
|
+
referralId: 'ref_1',
|
|
61
|
+
role: ReferralRoleEnum.REFERRER,
|
|
62
|
+
rewardType: ReferralRewardTypeEnum.FIXED_BONUS,
|
|
63
|
+
amountCents: 20000,
|
|
64
|
+
status: ReferralRewardStatusEnum.PAID,
|
|
65
|
+
statusAt: '2026-10-12T10:00:00.000Z',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
expect(response.phones[0].rewards[0].status).toBe('PAID');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validateSync } from 'class-validator';
|
|
4
|
+
import { RetailPromotions } from '../../../src/index';
|
|
5
|
+
import {
|
|
6
|
+
EvaluateSalePromotionsRequest,
|
|
7
|
+
ListLiveReferralPromotionsQuery,
|
|
8
|
+
ReferralPromotionChannelEnum,
|
|
9
|
+
ReferralRewardTypeEnum,
|
|
10
|
+
ReferralTriggerEnum,
|
|
11
|
+
} from '../../../src/retailPromotions/index';
|
|
12
|
+
import type {
|
|
13
|
+
EvaluateSalePromotionsResponse,
|
|
14
|
+
ListLiveReferralPromotionsResponse,
|
|
15
|
+
} from '../../../src/retailPromotions/index';
|
|
16
|
+
|
|
17
|
+
type Dto = new () => object;
|
|
18
|
+
|
|
19
|
+
const errorsOf = (dtoClass: Dto, raw: Record<string, unknown>): string[] =>
|
|
20
|
+
validateSync(plainToInstance(dtoClass, raw, { excludeExtraneousValues: true }) as object).map((e) => e.property);
|
|
21
|
+
|
|
22
|
+
describe('EvaluateSalePromotionsRequest con teléfono', () => {
|
|
23
|
+
it('acepta el teléfono del cliente o su ausencia', () => {
|
|
24
|
+
expect(errorsOf(EvaluateSalePromotionsRequest, { sessionId: 's1', phone: '5512345678' })).toEqual([]);
|
|
25
|
+
expect(errorsOf(EvaluateSalePromotionsRequest, { sessionId: 's1' })).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('rechaza el teléfono vacío o no texto', () => {
|
|
29
|
+
expect(errorsOf(EvaluateSalePromotionsRequest, { sessionId: 's1', phone: '' })).toEqual(['phone']);
|
|
30
|
+
expect(errorsOf(EvaluateSalePromotionsRequest, { sessionId: 's1', phone: 5512345678 })).toEqual(['phone']);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('EvaluateSalePromotionsResponse con referredBy', () => {
|
|
35
|
+
it('trae solo el nombre de quien recomendó y lo que gana el recomendado', () => {
|
|
36
|
+
const response: EvaluateSalePromotionsResponse = {
|
|
37
|
+
promotions: [],
|
|
38
|
+
discountCode: null,
|
|
39
|
+
referredBy: {
|
|
40
|
+
name: 'María Fernanda',
|
|
41
|
+
rewards: [
|
|
42
|
+
{
|
|
43
|
+
promotionId: 'promo_1',
|
|
44
|
+
promotionName: 'Gama alta · ganan los dos',
|
|
45
|
+
reward: {
|
|
46
|
+
type: ReferralRewardTypeEnum.SALE_DISCOUNT,
|
|
47
|
+
amountCents: null,
|
|
48
|
+
percentBps: 1000,
|
|
49
|
+
maxAmountCents: null,
|
|
50
|
+
coupon: null,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
expect(response.referredBy?.rewards?.[0].reward.percentBps).toBe(1000);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('referredBy es opcional', () => {
|
|
60
|
+
const response: EvaluateSalePromotionsResponse = { promotions: [], discountCode: null };
|
|
61
|
+
expect(response.referredBy).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('ListLiveReferralPromotionsQuery', () => {
|
|
66
|
+
it('acepta sesión sola o con canal y evento', () => {
|
|
67
|
+
expect(errorsOf(ListLiveReferralPromotionsQuery, { sessionId: 's1' })).toEqual([]);
|
|
68
|
+
expect(
|
|
69
|
+
errorsOf(ListLiveReferralPromotionsQuery, {
|
|
70
|
+
sessionId: 's1',
|
|
71
|
+
channel: ReferralPromotionChannelEnum.CREDIT,
|
|
72
|
+
trigger: ReferralTriggerEnum.ACCOUNT_OPENED,
|
|
73
|
+
}),
|
|
74
|
+
).toEqual([]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('exige la sesión', () => {
|
|
78
|
+
expect(errorsOf(ListLiveReferralPromotionsQuery, {})).toEqual(['sessionId']);
|
|
79
|
+
expect(errorsOf(ListLiveReferralPromotionsQuery, { sessionId: '' })).toEqual(['sessionId']);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('rechaza canal o evento fuera del catálogo', () => {
|
|
83
|
+
expect(errorsOf(ListLiveReferralPromotionsQuery, { sessionId: 's1', channel: 'WALLET' })).toEqual(['channel']);
|
|
84
|
+
expect(errorsOf(ListLiveReferralPromotionsQuery, { sessionId: 's1', trigger: 'SIGNUP' })).toEqual(['trigger']);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('sale por el index raíz', () => {
|
|
88
|
+
expect(new RetailPromotions.ListLiveReferralPromotionsQuery()).toBeInstanceOf(ListLiveReferralPromotionsQuery);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('ListLiveReferralPromotionsResponse', () => {
|
|
93
|
+
it('trae «tú ganas» y «tu amigo gana» por promo', () => {
|
|
94
|
+
const response: ListLiveReferralPromotionsResponse = {
|
|
95
|
+
items: [
|
|
96
|
+
{
|
|
97
|
+
promotionId: 'promo_1',
|
|
98
|
+
version: 2,
|
|
99
|
+
name: 'Abre tu cuenta · ganan los dos',
|
|
100
|
+
customerText: 'Cuando tu amigo abra su cuenta Fiado',
|
|
101
|
+
channel: ReferralPromotionChannelEnum.SALE,
|
|
102
|
+
trigger: ReferralTriggerEnum.ACCOUNT_OPENED,
|
|
103
|
+
purchaseNumber: null,
|
|
104
|
+
installmentStreak: null,
|
|
105
|
+
validUntil: '2026-11-17T23:59:59.000Z',
|
|
106
|
+
referrerReward: {
|
|
107
|
+
type: ReferralRewardTypeEnum.FIXED_BONUS,
|
|
108
|
+
amountCents: 15000,
|
|
109
|
+
percentBps: null,
|
|
110
|
+
maxAmountCents: null,
|
|
111
|
+
coupon: null,
|
|
112
|
+
},
|
|
113
|
+
referredReward: null,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
expect(response.items[0].referrerReward.amountCents).toBe(15000);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { plainToInstance } from 'class-transformer';
|
|
3
|
+
import { validate } from 'class-validator';
|
|
4
|
+
import { CheckSalePromotionPaymentRequest } from '../../../src/retailPromotions/index';
|
|
5
|
+
import type { PromotionPaymentNotEligibleDetails } from '../../../src/retailPromotions/index';
|
|
6
|
+
|
|
7
|
+
const errorsOf = async (raw: Record<string, unknown>) =>
|
|
8
|
+
(await validate(plainToInstance(CheckSalePromotionPaymentRequest, raw) as object)).map((e) => e.property);
|
|
9
|
+
|
|
10
|
+
describe('CheckSalePromotionPaymentRequest', () => {
|
|
11
|
+
it.each(['CASH', 'CARD', 'WIRE'])('acepta el medio %s', async (method) => {
|
|
12
|
+
expect(await errorsOf({ method })).toEqual([]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('rechaza un medio fuera del enum', async () => {
|
|
16
|
+
expect(await errorsOf({ method: 'BITCOIN' })).toEqual(['method']);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('sin medio es válido: se revisa contra la forma de pago de la sesión', async () => {
|
|
20
|
+
expect(await errorsOf({})).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('el detalle del 422 nombra la promo y los medios que acepta', () => {
|
|
24
|
+
const details: PromotionPaymentNotEligibleDetails = {
|
|
25
|
+
promotionIds: 'p-1,p-2', promotionId: 'p-1', promotionName: 'Bono', acceptedOptions: 'SUREKEEP,CASH',
|
|
26
|
+
};
|
|
27
|
+
expect(Object.keys(details)).toEqual(['promotionIds', 'promotionId', 'promotionName', 'acceptedOptions']);
|
|
28
|
+
});
|
|
29
|
+
});
|