@fiado/type-kit 3.563.0 → 3.565.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/retailCustomer/welcomeBonusRequests.test.ts +1 -1
- 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/salePromotionPaymentCheck.test.ts +26 -0
- package/_test_/unit/retailPromotions/welcomeBonusPromotion.test.ts +34 -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/enums/WelcomeBonusAmountSourceEnum.d.ts +3 -1
- package/bin/retailCustomer/enums/WelcomeBonusAmountSourceEnum.js +2 -0
- package/bin/retailCustomer/index.d.ts +9 -0
- package/bin/retailCustomer/index.js +9 -0
- package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +4 -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 +6 -0
- package/bin/retailPromotions/dtos/requests/CheckSalePromotionPaymentRequest.d.ts +5 -0
- package/bin/retailPromotions/dtos/requests/CheckSalePromotionPaymentRequest.js +24 -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/GetReferralRewardsByPhonesRequest.d.ts +5 -0
- package/bin/retailPromotions/dtos/requests/GetReferralRewardsByPhonesRequest.js +34 -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/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/WelcomeBonusPromotionResponse.d.ts +6 -0
- package/bin/retailPromotions/dtos/responses/WelcomeBonusPromotionResponse.js +2 -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 +9 -1
- package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +8 -0
- package/bin/retailPromotions/enums/SalePromotionKindEnum.d.ts +3 -2
- package/bin/retailPromotions/enums/SalePromotionKindEnum.js +2 -1
- package/bin/retailPromotions/enums/SalePromotionSystemKeyEnum.d.ts +5 -0
- package/bin/retailPromotions/enums/SalePromotionSystemKeyEnum.js +9 -0
- 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/enums/WelcomeBonusAmountSourceEnum.ts +2 -0
- package/src/retailCustomer/index.ts +9 -0
- package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +4 -0
- package/src/retailPromotions/dtos/ReferralPromotion.ts +35 -0
- package/src/retailPromotions/dtos/SalePromotion.ts +6 -0
- package/src/retailPromotions/dtos/requests/CheckSalePromotionPaymentRequest.ts +10 -0
- package/src/retailPromotions/dtos/requests/CreateSalePromotionRequest.ts +8 -0
- package/src/retailPromotions/dtos/requests/GetReferralRewardsByPhonesRequest.ts +20 -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/ReferralRewardsByPhonesResponses.ts +28 -0
- package/src/retailPromotions/dtos/responses/SalePromotionPaymentCheckResponses.ts +17 -0
- package/src/retailPromotions/dtos/responses/WelcomeBonusPromotionResponse.ts +6 -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 +8 -0
- package/src/retailPromotions/enums/SalePromotionKindEnum.ts +2 -1
- package/src/retailPromotions/enums/SalePromotionSystemKeyEnum.ts +5 -0
- 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
|
+
});
|
|
@@ -219,6 +219,6 @@ describe('enums del bono de bienvenida', () => {
|
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
it('el origen del monto distingue el parámetro del fallback local', () => {
|
|
222
|
-
expect(Object.values(WelcomeBonusAmountSourceEnum)).toEqual(['PARAM', 'DEFAULT']);
|
|
222
|
+
expect(Object.values(WelcomeBonusAmountSourceEnum)).toEqual(['PARAM', 'DEFAULT', 'SALE_PROMOTION']);
|
|
223
223
|
});
|
|
224
224
|
});
|
|
@@ -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,26 @@
|
|
|
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 o ausente', async () => {
|
|
16
|
+
expect(await errorsOf({ method: 'BITCOIN' })).toEqual(['method']);
|
|
17
|
+
expect(await errorsOf({})).toEqual(['method']);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('el detalle del 422 nombra la promo y los medios que acepta', () => {
|
|
21
|
+
const details: PromotionPaymentNotEligibleDetails = {
|
|
22
|
+
promotionIds: 'p-1,p-2', promotionId: 'p-1', promotionName: 'Bono', acceptedOptions: 'SUREKEEP,CASH',
|
|
23
|
+
};
|
|
24
|
+
expect(Object.keys(details)).toEqual(['promotionIds', 'promotionId', 'promotionName', 'acceptedOptions']);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import {
|
|
3
|
+
RETAIL_PROMOTIONS_ERROR_HTTP_STATUS,
|
|
4
|
+
RetailPromotionsErrorCodeEnum,
|
|
5
|
+
SalePromotion,
|
|
6
|
+
SalePromotionSystemKeyEnum,
|
|
7
|
+
WelcomeBonusPromotionResponse,
|
|
8
|
+
} from '../../../src/retailPromotions/index';
|
|
9
|
+
import { WelcomeBonusAmountSourceEnum } from '../../../src/retailCustomer/index';
|
|
10
|
+
|
|
11
|
+
describe('promo de sistema del bono de bienvenida', () => {
|
|
12
|
+
it('la llave de sistema solo tiene WELCOME_BONUS', () => {
|
|
13
|
+
expect(Object.values(SalePromotionSystemKeyEnum)).toEqual(['WELCOME_BONUS']);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('la promo de venta expone systemKey opcional', () => {
|
|
17
|
+
const systemKey: SalePromotion['systemKey'] = SalePromotionSystemKeyEnum.WELCOME_BONUS;
|
|
18
|
+
expect(systemKey).toBe('WELCOME_BONUS');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('la respuesta privada trae id, estado y monto', () => {
|
|
22
|
+
const response: WelcomeBonusPromotionResponse = { promotionId: 'p', active: true, amountCents: 2500 };
|
|
23
|
+
expect(Object.keys(response)).toEqual(['promotionId', 'active', 'amountCents']);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('los candados de la promo de sistema son 422', () => {
|
|
27
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[RetailPromotionsErrorCodeEnum.SYSTEM_PROMOTION_TRANSITION_NOT_ALLOWED]).toBe(422);
|
|
28
|
+
expect(RETAIL_PROMOTIONS_ERROR_HTTP_STATUS[RetailPromotionsErrorCodeEnum.SYSTEM_PROMOTION_FIELD_LOCKED]).toBe(422);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('el monto del bono puede salir de la promo de venta', () => {
|
|
32
|
+
expect(WelcomeBonusAmountSourceEnum.SALE_PROMOTION).toBe('SALE_PROMOTION');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { RetailWizard } from '../../../src/index';
|
|
3
|
+
import type {
|
|
4
|
+
BackofficeCustomerReferral,
|
|
5
|
+
BackofficeCustomerReferralsResponse,
|
|
6
|
+
BackofficeReferralReward,
|
|
7
|
+
} from '../../../src/retailWizard/index';
|
|
8
|
+
import { ReferralRoleEnum, ReferralSourceEnum, ReferralStatusEnum } from '../../../src/retailCustomer/index';
|
|
9
|
+
import { ReferralRewardStatusEnum, ReferralRewardTypeEnum } from '../../../src/retailPromotions/index';
|
|
10
|
+
|
|
11
|
+
const reward: BackofficeReferralReward = {
|
|
12
|
+
promotionId: 'promo_1',
|
|
13
|
+
promotionName: 'Abre tu cuenta · ganan los dos',
|
|
14
|
+
role: ReferralRoleEnum.REFERRED,
|
|
15
|
+
rewardType: ReferralRewardTypeEnum.FIXED_BONUS,
|
|
16
|
+
amountCents: 10000,
|
|
17
|
+
status: ReferralRewardStatusEnum.PAID,
|
|
18
|
+
statusAt: '2025-03-14T10:00:00.000Z',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const referral: BackofficeCustomerReferral = {
|
|
22
|
+
referralId: 'ref_1',
|
|
23
|
+
name: 'Ana Sofía Martínez Ruiz',
|
|
24
|
+
phone: '5587654321',
|
|
25
|
+
email: null,
|
|
26
|
+
status: ReferralStatusEnum.SUREKEEP_CREDIT,
|
|
27
|
+
capture: {
|
|
28
|
+
source: ReferralSourceEnum.SALE,
|
|
29
|
+
storeId: 'store_1',
|
|
30
|
+
storeName: 'Tienda Coyoacán Centro',
|
|
31
|
+
saleId: 'sale_1',
|
|
32
|
+
saleFolio: 'F-10423',
|
|
33
|
+
accountOpeningSessionId: null,
|
|
34
|
+
capturedBy: 'user_1',
|
|
35
|
+
capturedAt: '2026-10-03T10:00:00.000Z',
|
|
36
|
+
},
|
|
37
|
+
updatedBy: 'user_1',
|
|
38
|
+
updatedAt: '2026-10-03T10:00:00.000Z',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe('BackofficeCustomerReferralsResponse', () => {
|
|
42
|
+
it('lista los referidos con estado y captura, sin premios en la tanda A', () => {
|
|
43
|
+
const response: BackofficeCustomerReferralsResponse = { referrals: [referral], referredBy: null };
|
|
44
|
+
expect(response.referrals[0].rewards).toBeUndefined();
|
|
45
|
+
expect(response.referrals[0].capture.source).toBe('SALE');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('suma premios por referido y el bloque «llegó recomendado por» en la tanda B', () => {
|
|
49
|
+
const response: BackofficeCustomerReferralsResponse = {
|
|
50
|
+
referrals: [{ ...referral, rewards: [{ ...reward, role: ReferralRoleEnum.REFERRER }] }],
|
|
51
|
+
referredBy: {
|
|
52
|
+
referralId: 'ref_9',
|
|
53
|
+
referrerCustomerId: 'rc_9',
|
|
54
|
+
referrerName: 'Mariana López Ortega',
|
|
55
|
+
referredAt: '2025-03-02T10:00:00.000Z',
|
|
56
|
+
rewards: [reward],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
expect(response.referredBy?.rewards?.[0].promotionName).toBe('Abre tu cuenta · ganan los dos');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('el bloque «llegó recomendado por» no exige promo', () => {
|
|
63
|
+
const response: BackofficeCustomerReferralsResponse = {
|
|
64
|
+
referrals: [],
|
|
65
|
+
referredBy: {
|
|
66
|
+
referralId: 'ref_9',
|
|
67
|
+
referrerCustomerId: 'rc_9',
|
|
68
|
+
referrerName: 'Mariana López Ortega',
|
|
69
|
+
referredAt: '2025-03-02T10:00:00.000Z',
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
expect(response.referredBy?.rewards).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('el index raíz sigue exponiendo el módulo', () => {
|
|
76
|
+
expect(RetailWizard.WizardStepEnum).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|