@fiado/type-kit 3.630.0 → 3.632.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 (68) hide show
  1. package/_test_/unit/retailIntegration/retailSaleContracts.test.ts +15 -3
  2. package/_test_/unit/retailWizard/accessorySaleRequests.test.ts +2 -2
  3. package/_test_/unit/retailWizard/salePayments.test.ts +140 -0
  4. package/bin/onboarding/dtos/responses/OnboardingStepsResponse.d.ts +13 -0
  5. package/bin/onboarding/dtos/responses/OnboardingStepsResponse.js +5 -1
  6. package/bin/onboarding/enums/OnboardingAccountMessageKeyV2Enum.d.ts +28 -0
  7. package/bin/onboarding/enums/OnboardingAccountMessageKeyV2Enum.js +32 -0
  8. package/bin/onboarding/enums/OnboardingAccountReasonV2Enum.d.ts +20 -0
  9. package/bin/onboarding/enums/OnboardingAccountReasonV2Enum.js +24 -0
  10. package/bin/onboarding/enums/OnboardingAccountStatusV2Enum.d.ts +15 -0
  11. package/bin/onboarding/enums/OnboardingAccountStatusV2Enum.js +19 -0
  12. package/bin/onboarding/index.d.ts +3 -0
  13. package/bin/onboarding/index.js +3 -0
  14. package/bin/retailIntegration/models/RetailSystemSale.d.ts +1 -1
  15. package/bin/retailIntegration/models/RetailSystemSale.js +4 -4
  16. package/bin/retailIntegration/validators/saleValidators.d.ts +1 -1
  17. package/bin/retailIntegration/validators/saleValidators.js +3 -2
  18. package/bin/retailWizard/constants/retailWizardValidation.d.ts +5 -0
  19. package/bin/retailWizard/constants/retailWizardValidation.js +6 -1
  20. package/bin/retailWizard/dtos/Sale.d.ts +5 -0
  21. package/bin/retailWizard/dtos/SalePayment.d.ts +11 -0
  22. package/bin/retailWizard/dtos/SalePayment.js +69 -0
  23. package/bin/retailWizard/dtos/SalePaymentDetail.d.ts +1 -0
  24. package/bin/retailWizard/dtos/SalePaymentDetail.js +1 -0
  25. package/bin/retailWizard/dtos/SellerPendingRefundItem.d.ts +6 -1
  26. package/bin/retailWizard/dtos/WizardPayment.d.ts +5 -0
  27. package/bin/retailWizard/dtos/WizardPayment.js +7 -0
  28. package/bin/retailWizard/dtos/requests/CreateAccessorySaleRequest.d.ts +8 -3
  29. package/bin/retailWizard/dtos/requests/CreateAccessorySaleRequest.js +10 -2
  30. package/bin/retailWizard/dtos/requests/DownPaymentRequest.d.ts +7 -2
  31. package/bin/retailWizard/dtos/requests/DownPaymentRequest.js +9 -1
  32. package/bin/retailWizard/dtos/requests/RegisterPaymentRequest.d.ts +6 -1
  33. package/bin/retailWizard/dtos/requests/RegisterPaymentRequest.js +8 -0
  34. package/bin/retailWizard/events/RetailPromotionInboundMessage.d.ts +3 -1
  35. package/bin/retailWizard/events/RetailPromotionInboundMessage.js +6 -0
  36. package/bin/retailWizard/index.d.ts +5 -0
  37. package/bin/retailWizard/index.js +5 -0
  38. package/bin/retailWizard/validators/IsSalePaymentList.d.ts +2 -0
  39. package/bin/retailWizard/validators/IsSalePaymentList.js +22 -0
  40. package/bin/retailWizard/validators/OnlyForPaymentMethodConstraint.d.ts +6 -0
  41. package/bin/retailWizard/validators/OnlyForPaymentMethodConstraint.js +25 -0
  42. package/bin/retailWizard/validators/PaymentsHaveSingleUseMethodsConstraint.d.ts +6 -0
  43. package/bin/retailWizard/validators/PaymentsHaveSingleUseMethodsConstraint.js +27 -0
  44. package/bin/retailWizard/validators/PaymentsMatchAmountCentsConstraint.d.ts +6 -0
  45. package/bin/retailWizard/validators/PaymentsMatchAmountCentsConstraint.js +29 -0
  46. package/package.json +1 -1
  47. package/src/onboarding/dtos/responses/OnboardingStepsResponse.ts +15 -0
  48. package/src/onboarding/enums/OnboardingAccountMessageKeyV2Enum.ts +28 -0
  49. package/src/onboarding/enums/OnboardingAccountReasonV2Enum.ts +20 -0
  50. package/src/onboarding/enums/OnboardingAccountStatusV2Enum.ts +15 -0
  51. package/src/onboarding/index.ts +3 -0
  52. package/src/retailIntegration/models/RetailSystemSale.ts +4 -4
  53. package/src/retailIntegration/validators/saleValidators.ts +3 -2
  54. package/src/retailWizard/constants/retailWizardValidation.ts +8 -0
  55. package/src/retailWizard/dtos/Sale.ts +5 -0
  56. package/src/retailWizard/dtos/SalePayment.ts +55 -0
  57. package/src/retailWizard/dtos/SalePaymentDetail.ts +1 -0
  58. package/src/retailWizard/dtos/SellerPendingRefundItem.ts +6 -1
  59. package/src/retailWizard/dtos/WizardPayment.ts +10 -0
  60. package/src/retailWizard/dtos/requests/CreateAccessorySaleRequest.ts +17 -5
  61. package/src/retailWizard/dtos/requests/DownPaymentRequest.ts +14 -3
  62. package/src/retailWizard/dtos/requests/RegisterPaymentRequest.ts +13 -2
  63. package/src/retailWizard/events/RetailPromotionInboundMessage.ts +7 -1
  64. package/src/retailWizard/index.ts +5 -0
  65. package/src/retailWizard/validators/IsSalePaymentList.ts +20 -0
  66. package/src/retailWizard/validators/OnlyForPaymentMethodConstraint.ts +18 -0
  67. package/src/retailWizard/validators/PaymentsHaveSingleUseMethodsConstraint.ts +20 -0
  68. package/src/retailWizard/validators/PaymentsMatchAmountCentsConstraint.ts +21 -0
@@ -0,0 +1,55 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsBoolean, IsEnum, IsInt, Matches, Min, Validate, ValidateIf } from 'class-validator';
3
+ import {
4
+ RETAIL_CARD_LAST4_PATTERN,
5
+ RETAIL_CLABE_PATTERN,
6
+ RETAIL_SALE_ID_TEXT_PATTERN,
7
+ RETAIL_SPEI_TRACKING_KEY_PATTERN,
8
+ } from '../../retailIntegration/constants/retailIntegrationLimits';
9
+ import { SalePaymentMethodEnum } from '../enums/SalePaymentMethodEnum';
10
+ import { OnlyForPaymentMethodConstraint } from '../validators/OnlyForPaymentMethodConstraint';
11
+
12
+ const { CASH, CARD, WIRE } = SalePaymentMethodEnum;
13
+ const hasValue = (field: keyof SalePayment) => (o: SalePayment) => o[field] != null;
14
+
15
+ /** Un pago del cobro. El comprobante es opcional: si viene se valida y solo en su medio; si no, `null` o ausente. */
16
+ export class SalePayment {
17
+ @Expose()
18
+ @IsEnum(SalePaymentMethodEnum)
19
+ method!: SalePaymentMethodEnum;
20
+
21
+ @Expose()
22
+ @IsInt()
23
+ @Min(1)
24
+ amountCents!: number;
25
+
26
+ @Expose()
27
+ @ValidateIf(hasValue('cashConfirmedInRegister'))
28
+ @IsBoolean()
29
+ @Validate(OnlyForPaymentMethodConstraint, [CASH])
30
+ cashConfirmedInRegister?: boolean | null;
31
+
32
+ @Expose()
33
+ @ValidateIf(hasValue('cardLast4'))
34
+ @Matches(RETAIL_CARD_LAST4_PATTERN)
35
+ @Validate(OnlyForPaymentMethodConstraint, [CARD])
36
+ cardLast4?: string | null;
37
+
38
+ @Expose()
39
+ @ValidateIf(hasValue('authCode'))
40
+ @Matches(RETAIL_SALE_ID_TEXT_PATTERN)
41
+ @Validate(OnlyForPaymentMethodConstraint, [CARD])
42
+ authCode?: string | null;
43
+
44
+ @Expose()
45
+ @ValidateIf(hasValue('speiTrackingKey'))
46
+ @Matches(RETAIL_SPEI_TRACKING_KEY_PATTERN)
47
+ @Validate(OnlyForPaymentMethodConstraint, [WIRE])
48
+ speiTrackingKey?: string | null;
49
+
50
+ @Expose()
51
+ @ValidateIf(hasValue('senderClabe'))
52
+ @Matches(RETAIL_CLABE_PATTERN)
53
+ @Validate(OnlyForPaymentMethodConstraint, [WIRE])
54
+ senderClabe?: string | null;
55
+ }
@@ -5,6 +5,7 @@ import { IsBoolean, IsOptional, IsString } from 'class-validator';
5
5
  * Detalle del pago según el medio (`Sale.paymentDetail`): efectivo `{cashConfirmedInRegister}` ·
6
6
  * tarjeta `{cardLast4, authCode}` · SPEI `{speiTrackingKey, senderClabe?}`. SureKeep Fase 2 — pista Retail.
7
7
  */
8
+ /** @deprecated Usa `SalePayment`, un elemento por pago. */
8
9
  export class SalePaymentDetail {
9
10
  @Expose()
10
11
  @IsOptional()
@@ -1,5 +1,6 @@
1
1
  import { RefundStatusEnum } from '../enums/RefundStatusEnum';
2
2
  import { SalePaymentMethodEnum } from '../enums/SalePaymentMethodEnum';
3
+ import type { SalePayment } from './SalePayment';
3
4
 
4
5
  /** Un reembolso de enganche de la tienda en el Portal Vendedor (GET /portal/pending-refunds). */
5
6
  export interface SellerPendingRefundItem {
@@ -10,8 +11,12 @@ export interface SellerPendingRefundItem {
10
11
  creditId: string | null;
11
12
  folio: string | null;
12
13
  refundPendingCents: number;
13
- /** `null` = el registro no guardó con qué se cobró el enganche; no se puede marcar entregado. */
14
+ /**
15
+ * @deprecated Usa `payments`. `null` = el registro no guardó con qué se cobró el enganche; no se puede marcar entregado.
16
+ */
14
17
  paymentMethod: SalePaymentMethodEnum | null;
18
+ /** Pagos con que se cobró el enganche; ausente en registros anteriores al campo. */
19
+ payments?: SalePayment[] | null;
15
20
  status: RefundStatusEnum;
16
21
  /** `true` si el vendedor puede marcarlo entregado: efectivo y pendiente. */
17
22
  deliverable: boolean;
@@ -2,9 +2,12 @@ import { Expose } from 'class-transformer';
2
2
  import { IsEnum, IsInt, IsOptional, IsString, Matches, Min } from 'class-validator';
3
3
  import { regexIso8601 } from '../../helpers/constans/regex';
4
4
  import { SalePaymentMethodEnum } from '../enums/SalePaymentMethodEnum';
5
+ import { IsSalePaymentList } from '../validators/IsSalePaymentList';
6
+ import { SalePayment } from './SalePayment';
5
7
 
6
8
  /** Registro del pago de contado dentro de la sesión (`WizardSession.payment`). SureKeep Fase 2 — pista Retail. */
7
9
  export class WizardPayment {
10
+ /** @deprecated Usa `payments`; con varios pagos lleva el método del primero. */
8
11
  @Expose()
9
12
  @IsEnum(SalePaymentMethodEnum)
10
13
  method!: SalePaymentMethodEnum;
@@ -14,11 +17,18 @@ export class WizardPayment {
14
17
  @Min(0)
15
18
  amountCents!: number;
16
19
 
20
+ /** @deprecated Usa `payments`; es el comprobante como texto JSON. */
17
21
  @Expose()
18
22
  @IsOptional()
19
23
  @IsString()
20
24
  detail?: string;
21
25
 
26
+ /** Pagos del cobro (1 a 3); ausente en sesiones anteriores al campo. */
27
+ @Expose()
28
+ @IsOptional()
29
+ @IsSalePaymentList()
30
+ payments?: SalePayment[];
31
+
22
32
  @Expose()
23
33
  @IsString()
24
34
  @Matches(regexIso8601, { message: 'paidAt debe ser una fecha ISO 8601' })
@@ -3,19 +3,22 @@ import {
3
3
  ArrayMaxSize,
4
4
  ArrayMinSize,
5
5
  IsArray,
6
- IsDefined,
7
6
  IsEnum,
8
7
  IsInt,
8
+ IsOptional,
9
9
  Min,
10
+ ValidateIf,
10
11
  ValidateNested,
11
12
  } from 'class-validator';
12
13
  import { SalePaymentMethodEnum } from '../../enums/SalePaymentMethodEnum';
13
14
  import { SalePaymentDetail } from '../SalePaymentDetail';
14
15
  import { AccessorySaleLine } from '../AccessorySaleLine';
16
+ import { SalePayment } from '../SalePayment';
17
+ import { IsSalePaymentList } from '../../validators/IsSalePaymentList';
15
18
 
16
19
  /**
17
20
  * POST /wizard/accessory-sales — cierra la venta de mostrador, sin cliente.
18
- * `amountCents` es lo cobrado en caja y debe igualar la suma del catálogo.
21
+ * `amountCents` es lo cobrado en caja: iguala la suma del catálogo y la de `payments`.
19
22
  */
20
23
  export class CreateAccessorySaleRequest {
21
24
  @Expose()
@@ -26,18 +29,27 @@ export class CreateAccessorySaleRequest {
26
29
  @Type(() => AccessorySaleLine)
27
30
  lines!: AccessorySaleLine[];
28
31
 
32
+ /** @deprecated Usa `payments`; obligatorio solo si `payments` no viene. */
29
33
  @Expose()
34
+ @ValidateIf((o: { payments?: unknown; paymentMethod?: unknown }) => o.payments == null || o.paymentMethod !== undefined)
30
35
  @IsEnum(SalePaymentMethodEnum)
31
- paymentMethod!: SalePaymentMethodEnum;
36
+ paymentMethod?: SalePaymentMethodEnum;
32
37
 
33
38
  @Expose()
34
39
  @IsInt()
35
40
  @Min(0)
36
41
  amountCents!: number;
37
42
 
43
+ /** @deprecated Usa el comprobante de cada elemento de `payments`. */
38
44
  @Expose()
39
- @IsDefined()
45
+ @IsOptional()
40
46
  @ValidateNested()
41
47
  @Type(() => SalePaymentDetail)
42
- paymentDetail!: SalePaymentDetail;
48
+ paymentDetail?: SalePaymentDetail;
49
+
50
+ /** Pagos del cobro (1 a 3); si viene, manda sobre `paymentMethod` y `paymentDetail`. */
51
+ @Expose()
52
+ @IsOptional()
53
+ @IsSalePaymentList()
54
+ payments?: SalePayment[];
43
55
  }
@@ -1,7 +1,9 @@
1
1
  import { Expose, Type } from 'class-transformer';
2
- import { IsEnum, IsInt, IsOptional, Min, ValidateNested } from 'class-validator';
2
+ import { IsEnum, IsInt, IsOptional, Min, ValidateIf, ValidateNested } from 'class-validator';
3
3
  import { SalePaymentMethodEnum } from '../../enums/SalePaymentMethodEnum';
4
4
  import { SalePaymentDetail } from '../SalePaymentDetail';
5
+ import { SalePayment } from '../SalePayment';
6
+ import { IsSalePaymentList } from '../../validators/IsSalePaymentList';
5
7
 
6
8
  /**
7
9
  * POST /wizard/sessions/:id/down-payment — cobro del ENGANCHE de la venta a crédito (paso 07,
@@ -9,22 +11,31 @@ import { SalePaymentDetail } from '../SalePaymentDetail';
9
11
  * `amountCents == downPaymentCents` del crédito (`loan-credit GET /credits/{id}`), NO el precio
10
12
  * full. Idempotente por sesión. El folio `SK-…-ENG-…` lo genera el server (no viaja en el body).
11
13
  * Al confirmar, el wizard marca la condición 1 del checklist (`activate-check { downPayment }`).
12
- * SPEI: el vendedor captura el comprobante (`speiTrackingKey`+`senderClabe`) y avanza.
14
+ * El comprobante de tarjeta y SPEI es opcional; `amountCents` es la suma de `payments`.
13
15
  * SureKeep Fase 2 — pista SOFOM.
14
16
  */
15
17
  export class DownPaymentRequest {
18
+ /** @deprecated Usa `payments`; obligatorio solo si `payments` no viene. */
16
19
  @Expose()
20
+ @ValidateIf((o: { payments?: unknown; method?: unknown }) => o.payments == null || o.method !== undefined)
17
21
  @IsEnum(SalePaymentMethodEnum)
18
- method!: SalePaymentMethodEnum;
22
+ method?: SalePaymentMethodEnum;
19
23
 
20
24
  @Expose()
21
25
  @IsInt()
22
26
  @Min(0)
23
27
  amountCents!: number;
24
28
 
29
+ /** @deprecated Usa el comprobante de cada elemento de `payments`. */
25
30
  @Expose()
26
31
  @IsOptional()
27
32
  @Type(() => SalePaymentDetail)
28
33
  @ValidateNested()
29
34
  detail?: SalePaymentDetail;
35
+
36
+ /** Pagos del cobro (1 a 3); si viene, manda sobre `method` y `detail`. */
37
+ @Expose()
38
+ @IsOptional()
39
+ @IsSalePaymentList()
40
+ payments?: SalePayment[];
30
41
  }
@@ -1,7 +1,9 @@
1
1
  import { Expose, Type } from 'class-transformer';
2
- import { IsEnum, IsInt, IsOptional, ValidateNested, Min } from 'class-validator';
2
+ import { IsEnum, IsInt, IsOptional, ValidateIf, ValidateNested, Min } from 'class-validator';
3
3
  import { SalePaymentMethodEnum } from '../../enums/SalePaymentMethodEnum';
4
4
  import { SalePaymentDetail } from '../SalePaymentDetail';
5
+ import { SalePayment } from '../SalePayment';
6
+ import { IsSalePaymentList } from '../../validators/IsSalePaymentList';
5
7
 
6
8
  /**
7
9
  * POST /wizard/sessions/:id/payment — registra el cobro de contado (idempotente por sesión). Regla
@@ -9,18 +11,27 @@ import { SalePaymentDetail } from '../SalePaymentDetail';
9
11
  * (D11) ANTES del registro; el folio no viaja en el body (lo posee el server). SureKeep Fase 2 — pista Retail.
10
12
  */
11
13
  export class RegisterPaymentRequest {
14
+ /** @deprecated Usa `payments`; obligatorio solo si `payments` no viene. */
12
15
  @Expose()
16
+ @ValidateIf((o: { payments?: unknown; method?: unknown }) => o.payments == null || o.method !== undefined)
13
17
  @IsEnum(SalePaymentMethodEnum)
14
- method!: SalePaymentMethodEnum;
18
+ method?: SalePaymentMethodEnum;
15
19
 
16
20
  @Expose()
17
21
  @IsInt()
18
22
  @Min(0)
19
23
  amountCents!: number;
20
24
 
25
+ /** @deprecated Usa el comprobante de cada elemento de `payments`. */
21
26
  @Expose()
22
27
  @IsOptional()
23
28
  @Type(() => SalePaymentDetail)
24
29
  @ValidateNested()
25
30
  detail?: SalePaymentDetail;
31
+
32
+ /** Pagos del cobro (1 a 3); si viene, manda sobre `method` y `detail`. */
33
+ @Expose()
34
+ @IsOptional()
35
+ @IsSalePaymentList()
36
+ payments?: SalePayment[];
26
37
  }
@@ -174,11 +174,17 @@ export class RetailPromotionInboundMessage {
174
174
  @IsEnum(SaleTypeEnum)
175
175
  saleType?: SaleTypeEnum;
176
176
 
177
- /** Medio de cobro, solo en contado. */
177
+ /** @deprecated Usa `paymentMethods`. Medio de cobro, solo en contado. */
178
178
  @IsOptional()
179
179
  @IsEnum(SalePaymentMethodEnum)
180
180
  paymentMethod?: SalePaymentMethodEnum | null;
181
181
 
182
+ /** Medios con que se cobró, uno por pago; solo en contado. */
183
+ @IsOptional()
184
+ @IsArray()
185
+ @IsEnum(SalePaymentMethodEnum, { each: true })
186
+ paymentMethods?: SalePaymentMethodEnum[] | null;
187
+
182
188
  /** Financiera externa, solo en `EXTERNAL_REFERRAL`. */
183
189
  @IsOptional()
184
190
  @IsEnum(ExternalProviderEnum)
@@ -45,6 +45,11 @@ export * from './dtos/WizardProduct';
45
45
  export * from './dtos/WizardWelcome';
46
46
  export * from './dtos/WizardCreditPromotionBonus';
47
47
  export * from './dtos/SalePaymentDetail';
48
+ export * from './dtos/SalePayment';
49
+ export * from './validators/IsSalePaymentList';
50
+ export * from './validators/OnlyForPaymentMethodConstraint';
51
+ export * from './validators/PaymentsHaveSingleUseMethodsConstraint';
52
+ export * from './validators/PaymentsMatchAmountCentsConstraint';
48
53
  export * from './dtos/ProspectNewCustomer';
49
54
  export * from './dtos/WizardBeneficiary';
50
55
  export * from './dtos/WizardLoan';
@@ -0,0 +1,20 @@
1
+ import { Type } from 'class-transformer';
2
+ import { ArrayMaxSize, ArrayMinSize, IsArray, Validate, ValidateNested } from 'class-validator';
3
+ import { MAX_SALE_PAYMENTS } from '../constants/retailWizardValidation';
4
+ import { SalePayment } from '../dtos/SalePayment';
5
+ import { PaymentsHaveSingleUseMethodsConstraint } from './PaymentsHaveSingleUseMethodsConstraint';
6
+ import { PaymentsMatchAmountCentsConstraint } from './PaymentsMatchAmountCentsConstraint';
7
+
8
+ /** Reglas de `payments`: 1 a 3 pagos válidos, efectivo y SPEI una vez, y la suma igual a `amountCents`. */
9
+ export function IsSalePaymentList(): PropertyDecorator {
10
+ const decorators: PropertyDecorator[] = [
11
+ IsArray(),
12
+ ArrayMinSize(1),
13
+ ArrayMaxSize(MAX_SALE_PAYMENTS),
14
+ ValidateNested({ each: true }),
15
+ Type(() => SalePayment),
16
+ Validate(PaymentsHaveSingleUseMethodsConstraint),
17
+ Validate(PaymentsMatchAmountCentsConstraint),
18
+ ];
19
+ return (target, propertyKey) => decorators.forEach((decorate) => decorate(target, propertyKey));
20
+ }
@@ -0,0 +1,18 @@
1
+ import {
2
+ ValidatorConstraint,
3
+ type ValidationArguments,
4
+ type ValidatorConstraintInterface,
5
+ } from 'class-validator';
6
+
7
+ /** Un campo de comprobante con valor solo se acepta en el medio de pago al que pertenece. */
8
+ @ValidatorConstraint({ name: 'onlyForPaymentMethod', async: false })
9
+ export class OnlyForPaymentMethodConstraint implements ValidatorConstraintInterface {
10
+ validate(value: unknown, args: ValidationArguments): boolean {
11
+ if (value == null) return true;
12
+ return (args.object as { method?: unknown }).method === args.constraints[0];
13
+ }
14
+
15
+ defaultMessage(args: ValidationArguments): string {
16
+ return `${args.property} solo aplica a pagos ${args.constraints[0]}`;
17
+ }
18
+ }
@@ -0,0 +1,20 @@
1
+ import {
2
+ ValidatorConstraint,
3
+ type ValidationArguments,
4
+ type ValidatorConstraintInterface,
5
+ } from 'class-validator';
6
+ import { SINGLE_USE_PAYMENT_METHODS } from '../constants/retailWizardValidation';
7
+
8
+ /** Efectivo y SPEI aparecen a lo más una vez en `payments`; la tarjeta se puede repetir. */
9
+ @ValidatorConstraint({ name: 'paymentsHaveSingleUseMethods', async: false })
10
+ export class PaymentsHaveSingleUseMethodsConstraint implements ValidatorConstraintInterface {
11
+ validate(value: unknown, _args: ValidationArguments): boolean {
12
+ if (!Array.isArray(value)) return true;
13
+ const methods = value.map((p) => (p as { method?: unknown } | null)?.method);
14
+ return SINGLE_USE_PAYMENT_METHODS.every((m) => methods.filter((x) => x === m).length <= 1);
15
+ }
16
+
17
+ defaultMessage(): string {
18
+ return `payments admite a lo más un pago de cada medio ${SINGLE_USE_PAYMENT_METHODS.join(' y ')}`;
19
+ }
20
+ }
@@ -0,0 +1,21 @@
1
+ import {
2
+ ValidatorConstraint,
3
+ type ValidationArguments,
4
+ type ValidatorConstraintInterface,
5
+ } from 'class-validator';
6
+
7
+ /** La suma de `payments[].amountCents` es el `amountCents` del cobro. */
8
+ @ValidatorConstraint({ name: 'paymentsMatchAmountCents', async: false })
9
+ export class PaymentsMatchAmountCentsConstraint implements ValidatorConstraintInterface {
10
+ validate(value: unknown, args: ValidationArguments): boolean {
11
+ const total = (args.object as { amountCents?: unknown }).amountCents;
12
+ if (!Array.isArray(value) || !Number.isInteger(total)) return true;
13
+ const amounts = value.map((p) => (p as { amountCents?: unknown } | null)?.amountCents);
14
+ if (!amounts.every((a) => Number.isInteger(a))) return true;
15
+ return (amounts as number[]).reduce((sum, a) => sum + a, 0) === total;
16
+ }
17
+
18
+ defaultMessage(): string {
19
+ return 'la suma de payments[].amountCents debe ser amountCents';
20
+ }
21
+ }