@fiado/type-kit 3.499.0 → 3.501.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/_test_/unit/cognitoBackofficeConnector/dtos/AuthStartLogin.test.ts +76 -0
  2. package/_test_/unit/platformRbac/auth/dtos.test.ts +33 -0
  3. package/_test_/unit/platformRbac/enums/ChallengeNameEnum.test.ts +17 -0
  4. package/_test_/unit/retailPromotions/constants.test.ts +1 -1
  5. package/_test_/unit/retailPromotions/evaluatedSalePromotion.test.ts +2 -0
  6. package/_test_/unit/retailPromotions/resolveSalePromotionKind.test.ts +2 -0
  7. package/_test_/unit/retailPromotions/salePromotionConditions.test.ts +4 -0
  8. package/_test_/unit/retailPromotions/stackablePromotions.test.ts +238 -0
  9. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginRequest.d.ts +13 -0
  10. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginRequest.js +53 -0
  11. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginResponse.d.ts +22 -0
  12. package/bin/cognitoBackofficeConnector/dtos/AuthStartLoginResponse.js +79 -0
  13. package/bin/cognitoBackofficeConnector/index.d.ts +2 -0
  14. package/bin/cognitoBackofficeConnector/index.js +2 -0
  15. package/bin/platformRbac/auth/DefineNextChallengeRequest.d.ts +1 -0
  16. package/bin/platformRbac/auth/DefineNextChallengeRequest.js +5 -0
  17. package/bin/platformRbac/enums/ChallengeNameEnum.d.ts +3 -1
  18. package/bin/platformRbac/enums/ChallengeNameEnum.js +4 -0
  19. package/bin/retailPromotions/constants/retailPromotionsErrorHttpStatus.js +1 -0
  20. package/bin/retailPromotions/dtos/LinePromotionAmount.d.ts +6 -0
  21. package/bin/retailPromotions/dtos/LinePromotionAmount.js +36 -0
  22. package/bin/retailPromotions/dtos/LinePromotionDiscount.d.ts +4 -0
  23. package/bin/retailPromotions/dtos/SalePromotion.d.ts +2 -0
  24. package/bin/retailPromotions/dtos/SalePromotionRef.d.ts +5 -0
  25. package/bin/retailPromotions/dtos/SalePromotionRef.js +30 -0
  26. package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.d.ts +2 -0
  27. package/bin/retailPromotions/dtos/requests/CreateSalePromotionRequest.js +6 -0
  28. package/bin/retailPromotions/dtos/requests/HoldPromotionInput.d.ts +8 -0
  29. package/bin/retailPromotions/dtos/requests/HoldPromotionInput.js +46 -0
  30. package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.d.ts +9 -3
  31. package/bin/retailPromotions/dtos/requests/HoldSalePromotionRequest.js +11 -0
  32. package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.d.ts +2 -0
  33. package/bin/retailPromotions/dtos/requests/UpdateSalePromotionRequest.js +6 -0
  34. package/bin/retailPromotions/dtos/responses/WizardSalePromotionResponses.d.ts +20 -0
  35. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.d.ts +3 -1
  36. package/bin/retailPromotions/enums/RetailPromotionsErrorCodeEnum.js +2 -0
  37. package/bin/retailPromotions/index.d.ts +4 -0
  38. package/bin/retailPromotions/index.js +5 -0
  39. package/bin/retailPromotions/validators/IsHoldModeExclusiveConstraint.d.ts +6 -0
  40. package/bin/retailPromotions/validators/IsHoldModeExclusiveConstraint.js +24 -0
  41. package/bin/retailWizard/dtos/Sale.d.ts +3 -0
  42. package/bin/retailWizard/dtos/WizardLinePromotionFields.d.ts +4 -0
  43. package/bin/retailWizard/dtos/WizardSession.d.ts +3 -0
  44. package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.d.ts +5 -0
  45. package/bin/retailWizard/dtos/requests/ApplyLinePromotionRequest.js +16 -0
  46. package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.d.ts +3 -0
  47. package/bin/retailWizard/dtos/requests/ApplySalePromotionRequest.js +9 -0
  48. package/bin/retailWizard/dtos/responses/PrivateWizardSessionResponse.d.ts +3 -0
  49. package/bin/retailWizard/events/RetailPromotionInboundMessage.d.ts +8 -0
  50. package/bin/retailWizard/events/RetailPromotionInboundMessage.js +22 -0
  51. package/bin/retailWizard/index.d.ts +2 -0
  52. package/bin/retailWizard/index.js +5 -0
  53. package/package.json +1 -1
  54. package/src/cognitoBackofficeConnector/dtos/AuthStartLoginRequest.ts +16 -0
  55. package/src/cognitoBackofficeConnector/dtos/AuthStartLoginResponse.ts +29 -0
  56. package/src/cognitoBackofficeConnector/index.ts +2 -0
  57. package/src/platformRbac/auth/DefineNextChallengeRequest.ts +7 -1
  58. package/src/platformRbac/enums/ChallengeNameEnum.ts +4 -0
  59. package/src/retailPromotions/constants/retailPromotionsErrorHttpStatus.ts +1 -0
  60. package/src/retailPromotions/dtos/LinePromotionAmount.ts +20 -0
  61. package/src/retailPromotions/dtos/LinePromotionDiscount.ts +5 -0
  62. package/src/retailPromotions/dtos/SalePromotion.ts +2 -0
  63. package/src/retailPromotions/dtos/SalePromotionRef.ts +15 -0
  64. package/src/retailPromotions/dtos/requests/CreateSalePromotionRequest.ts +6 -0
  65. package/src/retailPromotions/dtos/requests/HoldPromotionInput.ts +30 -0
  66. package/src/retailPromotions/dtos/requests/HoldSalePromotionRequest.ts +28 -4
  67. package/src/retailPromotions/dtos/requests/UpdateSalePromotionRequest.ts +6 -0
  68. package/src/retailPromotions/dtos/responses/WizardSalePromotionResponses.ts +21 -0
  69. package/src/retailPromotions/enums/RetailPromotionsErrorCodeEnum.ts +2 -0
  70. package/src/retailPromotions/index.ts +6 -0
  71. package/src/retailPromotions/validators/IsHoldModeExclusiveConstraint.ts +18 -0
  72. package/src/retailWizard/dtos/Sale.ts +3 -0
  73. package/src/retailWizard/dtos/WizardLinePromotionFields.ts +4 -0
  74. package/src/retailWizard/dtos/WizardSession.ts +3 -0
  75. package/src/retailWizard/dtos/requests/ApplyLinePromotionRequest.ts +18 -2
  76. package/src/retailWizard/dtos/requests/ApplySalePromotionRequest.ts +11 -2
  77. package/src/retailWizard/dtos/responses/PrivateWizardSessionResponse.ts +3 -0
  78. package/src/retailWizard/events/RetailPromotionInboundMessage.ts +22 -0
  79. package/src/retailWizard/index.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.499.0",
3
+ "version": "3.501.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -0,0 +1,16 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsNotEmpty, IsString } from 'class-validator';
3
+
4
+ /**
5
+ * Request del login de un solo flujo (SRP dentro de CUSTOM_AUTH). El connector
6
+ * hace InitiateAuth(CUSTOM_AUTH, CHALLENGE_NAME=SRP_A) y responde PASSWORD_VERIFIER
7
+ * firmado en la misma operacion. La contraseña nunca viaja en claro a Cognito: SRP
8
+ * la convierte en prueba. El caller (rbac) no conoce el mecanismo.
9
+ */
10
+ export class AuthStartLoginRequest {
11
+ @Expose() @IsString() @IsNotEmpty() userPoolId!: string;
12
+ @Expose() @IsString() @IsNotEmpty() region!: string;
13
+ @Expose() @IsString() @IsNotEmpty() clientId!: string;
14
+ @Expose() @IsString() @IsNotEmpty() username!: string;
15
+ @Expose() @IsString() @IsNotEmpty() password!: string;
16
+ }
@@ -0,0 +1,29 @@
1
+ import { Expose, Type } from 'class-transformer';
2
+ import { IsEnum, IsNumber, IsObject, IsOptional, IsString, ValidateNested } from 'class-validator';
3
+ import { CognitoChallengeType } from '../enums/CognitoChallengeType';
4
+
5
+ export class AuthStartLoginTokens {
6
+ @Expose() @IsString() accessToken!: string;
7
+ @Expose() @IsString() idToken!: string;
8
+ @Expose() @IsString() refreshToken!: string;
9
+ @Expose() @IsNumber() expiresIn!: number;
10
+ @Expose() @IsString() tokenType!: string;
11
+ }
12
+
13
+ /**
14
+ * Respuesta de `authStartLogin`. Tras verificar el password via SRP
15
+ * (PASSWORD_VERIFIER), Cognito devuelve una de dos cosas: `tokens` si el flujo
16
+ * quedo completo (2FA apagado), o `challengeType` + `session` + `challengeParameters`
17
+ * si falta un paso (NEW_PASSWORD_REQUIRED, o el CUSTOM_CHALLENGE del 2do factor con
18
+ * el sub-challenge real en `challengeParameters.challengeName`). Todos los campos son
19
+ * opcionales: el caller (rbac) decide por que rama sigue.
20
+ */
21
+ export class AuthStartLoginResponse {
22
+ @Expose() @IsOptional() @ValidateNested() @Type(() => AuthStartLoginTokens) tokens?: AuthStartLoginTokens;
23
+
24
+ @Expose() @IsOptional() @IsEnum(CognitoChallengeType) challengeType?: CognitoChallengeType;
25
+
26
+ @Expose() @IsOptional() @IsString() session?: string;
27
+
28
+ @Expose() @IsOptional() @IsObject() challengeParameters?: Record<string, string>;
29
+ }
@@ -21,6 +21,8 @@ export * from './dtos/UserDetailResponse';
21
21
  export * from './dtos/AuthEventsRequest';
22
22
  export * from './dtos/AuthEventResponse';
23
23
  export * from './dtos/AuthTokensResponse';
24
+ export * from './dtos/AuthStartLoginRequest';
25
+ export * from './dtos/AuthStartLoginResponse';
24
26
  export * from './dtos/InitiateAuthRequest';
25
27
  export * from './dtos/InitiateAuthResponse';
26
28
  export * from './dtos/VerifyPasswordRequest';
@@ -1,6 +1,6 @@
1
1
  import 'reflect-metadata';
2
2
  import { Type } from 'class-transformer';
3
- import { IsArray, IsBoolean, IsEnum, IsString, ValidateNested } from 'class-validator';
3
+ import { IsArray, IsBoolean, IsEnum, IsOptional, IsString, ValidateNested } from 'class-validator';
4
4
  import { ChallengeNameEnum } from '../enums/ChallengeNameEnum';
5
5
 
6
6
  export class ChallengeResultEntry {
@@ -22,4 +22,10 @@ export class DefineNextChallengeRequest {
22
22
  @ValidateNested({ each: true })
23
23
  @Type(() => ChallengeResultEntry)
24
24
  session!: ChallengeResultEntry[];
25
+
26
+ // cognito:user_status del evento de Define (C-06). El orquestador decide
27
+ // NEW_PASSWORD_REQUIRED por este valor, no por el fail-safe de getUser.
28
+ @IsOptional()
29
+ @IsString()
30
+ userStatus?: string;
25
31
  }
@@ -5,4 +5,8 @@ export enum ChallengeNameEnum {
5
5
  NEW_PASSWORD_REQUIRED = 'NEW_PASSWORD_REQUIRED',
6
6
  RESET_PASSWORD = 'RESET_PASSWORD',
7
7
  SMS_OTP = 'SMS_OTP',
8
+ // SRP dentro de CUSTOM_AUTH: entries nativos que Cognito deja en session[] y que
9
+ // el orquestador del rbac lee para decidir el siguiente paso del login de un solo flujo.
10
+ SRP_A = 'SRP_A',
11
+ PASSWORD_VERIFIER = 'PASSWORD_VERIFIER',
8
12
  }
@@ -32,4 +32,5 @@ export const RETAIL_PROMOTIONS_ERROR_HTTP_STATUS: Readonly<Record<C, 400 | 403 |
32
32
  [C.INVALID_NEXT_TOKEN]: 400,
33
33
  [C.CATALOG_SKU_NOT_FOUND]: 404,
34
34
  [C.SPLIT_FINANCING_DISABLED]: 409,
35
+ [C.PROMOTION_NOT_STACKABLE]: 422,
35
36
  };
@@ -0,0 +1,20 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
3
+
4
+ /** Descuento que una promo le aplica a una línea; la suma de la lista + el código da el total. */
5
+ export class LinePromotionAmount {
6
+ @Expose()
7
+ @IsString()
8
+ @IsNotEmpty()
9
+ promotionId!: string;
10
+
11
+ @Expose()
12
+ @IsInt()
13
+ @Min(1)
14
+ promotionVersion!: number;
15
+
16
+ @Expose()
17
+ @IsInt()
18
+ @Min(0)
19
+ discountCents!: number;
20
+ }
@@ -1,3 +1,5 @@
1
+ import type { LinePromotionAmount } from './LinePromotionAmount';
2
+
1
3
  /** Desglose por línea de la sesión: qué promo o código la tocó y cuánto bajó. */
2
4
  export interface LinePromotionDiscount {
3
5
  lineId: string;
@@ -7,4 +9,7 @@ export interface LinePromotionDiscount {
7
9
  originalPriceCents: number;
8
10
  discountCents: number;
9
11
  finalPriceCents: number;
12
+ /** Descuento de cada promo en la línea; sin el campo, la de `promotionId` lleva todo menos el código. */
13
+ promotionDiscounts?: LinePromotionAmount[];
14
+ codeDiscountCents?: number;
10
15
  }
@@ -22,6 +22,8 @@ export interface SalePromotion {
22
22
  /** `null` = permanente. */
23
23
  validUntil: string | null;
24
24
  oncePerCustomer: boolean;
25
+ /** Si se puede sumar con otras promos en la misma línea. */
26
+ stackable: boolean;
25
27
  status: SalePromotionStatusEnum;
26
28
  /** Derivada; `null` cuando el estado no es `ACTIVE`. */
27
29
  phase: SalePromotionPhaseEnum | null;
@@ -0,0 +1,15 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsInt, IsNotEmpty, IsString, Min } from 'class-validator';
3
+
4
+ /** Promo de venta fijada en la sesión o en la venta, con su versión. */
5
+ export class SalePromotionRef {
6
+ @Expose()
7
+ @IsString()
8
+ @IsNotEmpty()
9
+ promotionId!: string;
10
+
11
+ @Expose()
12
+ @IsInt()
13
+ @Min(1)
14
+ promotionVersion!: number;
15
+ }
@@ -70,4 +70,10 @@ export class CreateSalePromotionRequest {
70
70
  @IsOptional()
71
71
  @IsBoolean()
72
72
  oncePerCustomer?: boolean;
73
+
74
+ /** Si se puede sumar con otras promos en la misma línea. */
75
+ @Expose()
76
+ @IsOptional()
77
+ @IsBoolean()
78
+ stackable?: boolean;
73
79
  }
@@ -0,0 +1,30 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsArray, IsInt, IsNotEmpty, IsOptional, IsString, Min } from 'class-validator';
3
+
4
+ /** Promo apartada en la lista nueva: a qué líneas aplica y qué regalos eligió el cliente. */
5
+ export class HoldPromotionInput {
6
+ @Expose()
7
+ @IsString()
8
+ @IsNotEmpty()
9
+ promotionId!: string;
10
+
11
+ @Expose()
12
+ @IsInt()
13
+ @Min(1)
14
+ promotionVersion!: number;
15
+
16
+ /** Líneas que toca la promo; ausente = las que resuelva el lambda. */
17
+ @Expose()
18
+ @IsOptional()
19
+ @IsArray()
20
+ @IsString({ each: true })
21
+ @IsNotEmpty({ each: true })
22
+ lineIds?: string[];
23
+
24
+ @Expose()
25
+ @IsOptional()
26
+ @IsArray()
27
+ @IsString({ each: true })
28
+ @IsNotEmpty({ each: true })
29
+ giftSkus?: string[];
30
+ }
@@ -1,6 +1,18 @@
1
1
  import { Expose, Type } from 'class-transformer';
2
- import { IsArray, IsInt, IsNotEmpty, IsObject, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator';
2
+ import {
3
+ IsArray,
4
+ IsInt,
5
+ IsNotEmpty,
6
+ IsObject,
7
+ IsOptional,
8
+ IsString,
9
+ Validate,
10
+ ValidateIf,
11
+ ValidateNested,
12
+ } from 'class-validator';
13
+ import { IsHoldModeExclusiveConstraint } from '../../validators/IsHoldModeExclusiveConstraint';
3
14
  import { HoldGiftPromotionInput } from './HoldGiftPromotionInput';
15
+ import { HoldPromotionInput } from './HoldPromotionInput';
4
16
 
5
17
  /**
6
18
  * Body de publicHoldSalePromotion (PUT, `sessionId` en el path): hasta una promo de regalo, una de venta
@@ -9,7 +21,7 @@ import { HoldGiftPromotionInput } from './HoldGiftPromotionInput';
9
21
  export class HoldSalePromotionRequest {
10
22
  /**
11
23
  * Promo de regalo del teléfono (su única palanca es el regalo).
12
- * El lambda rechaza con PROMOTION_KIND_MISMATCH una promo de venta mandada aquí.
24
+ * @deprecated Usa `promotions`; no se combina con él.
13
25
  */
14
26
  @Expose()
15
27
  @IsOptional()
@@ -20,7 +32,7 @@ export class HoldSalePromotionRequest {
20
32
 
21
33
  /**
22
34
  * Promo de venta; `null` = «Ninguna».
23
- * El lambda rechaza con PROMOTION_KIND_MISMATCH una promo de solo regalo mandada aquí.
35
+ * @deprecated Usa `promotions`; no se combina con él.
24
36
  */
25
37
  @Expose()
26
38
  @IsOptional()
@@ -33,7 +45,10 @@ export class HoldSalePromotionRequest {
33
45
  @IsInt()
34
46
  promotionVersion?: number | null;
35
47
 
36
- /** Regalo incluido en la promo de venta, solo si esa promo además trae otras palancas. */
48
+ /**
49
+ * Regalo incluido en la promo de venta, solo si esa promo además trae otras palancas.
50
+ * @deprecated Usa `giftSkus` en `promotions`.
51
+ */
37
52
  @Expose()
38
53
  @IsOptional()
39
54
  @IsArray()
@@ -45,4 +60,13 @@ export class HoldSalePromotionRequest {
45
60
  @IsString()
46
61
  @IsNotEmpty()
47
62
  discountCode?: string | null;
63
+
64
+ /** Promos apartadas, cada una con sus líneas y regalos; excluyente con la forma vieja. */
65
+ @Expose()
66
+ @IsOptional()
67
+ @IsArray()
68
+ @ValidateNested({ each: true })
69
+ @Type(() => HoldPromotionInput)
70
+ @Validate(IsHoldModeExclusiveConstraint)
71
+ promotions?: HoldPromotionInput[] | null;
48
72
  }
@@ -71,4 +71,10 @@ export class UpdateSalePromotionRequest {
71
71
  @IsOptional()
72
72
  @IsBoolean()
73
73
  oncePerCustomer?: boolean;
74
+
75
+ /** Si se puede sumar con otras promos en la misma línea. */
76
+ @Expose()
77
+ @IsOptional()
78
+ @IsBoolean()
79
+ stackable?: boolean;
74
80
  }
@@ -29,6 +29,10 @@ export interface StoreSalePromotionItem {
29
29
  scope: SalePromotionScope;
30
30
  /** SUREKEEP_ONLY = solo con crédito SureKeep; ANY_FINANCIER = también contado o derivada. */
31
31
  appliesTo: PromotionAppliesToEnum;
32
+ /** Si se puede sumar con otras promos en la misma línea. */
33
+ stackable: boolean;
34
+ /** Ocupa el lugar de la línea ancla (teléfono o kit), aunque no la toque directo. */
35
+ anchorBound: boolean;
32
36
  conditions?: SalePromotionConditions;
33
37
  /** Si viene, todavía no se puede decidir para este cliente (ej. CUSTOMER_NOT_IDENTIFIED). */
34
38
  blockedBy?: RetailPromotionsErrorCodeEnum | null;
@@ -69,6 +73,10 @@ export interface EvaluatedSalePromotion {
69
73
  afterSale: AfterSaleBenefit[];
70
74
  /** SUREKEEP_ONLY = solo con crédito SureKeep; ANY_FINANCIER = también contado o derivada. */
71
75
  appliesTo: PromotionAppliesToEnum;
76
+ /** Si se puede sumar con otras promos en la misma línea. */
77
+ stackable: boolean;
78
+ /** Ocupa el lugar de la línea ancla (teléfono o kit), aunque no la toque directo. */
79
+ anchorBound: boolean;
72
80
  conditions?: SalePromotionConditions;
73
81
  /** Si viene, la promo se muestra pero no se puede elegir; dice por qué (ej. CUSTOMER_NOT_IDENTIFIED). */
74
82
  blockedBy?: RetailPromotionsErrorCodeEnum | null;
@@ -95,6 +103,16 @@ export interface HeldGiftPromotion {
95
103
  skus: string[];
96
104
  }
97
105
 
106
+ /** Promo apartada en la lista nueva, con las líneas que toca y los regalos elegidos. */
107
+ export interface HeldPromotion {
108
+ promotionId: string;
109
+ promotionVersion: number;
110
+ kind: SalePromotionKindEnum;
111
+ stackable: boolean;
112
+ lineIds: string[];
113
+ giftSkus: string[];
114
+ }
115
+
98
116
  /** Lo que quedó apartado y escrito en la sesión: la promo de regalo, la de venta y el código. */
99
117
  export interface HoldSalePromotionResponse {
100
118
  sessionId: string;
@@ -106,6 +124,8 @@ export interface HoldSalePromotionResponse {
106
124
  saleGiftSkus: string[];
107
125
  lines: LinePromotionDiscount[];
108
126
  totalDiscountCents: number;
127
+ /** Todas las promos apartadas; los campos de arriba se conservan por compatibilidad. */
128
+ promotions: HeldPromotion[];
109
129
  }
110
130
 
111
131
  /** `released: false` = no había apartado; soltar es idempotente. */
@@ -142,6 +162,7 @@ export interface SaleAppliedPromotion {
142
162
  version: number;
143
163
  name: string;
144
164
  kind: SalePromotionKindEnum;
165
+ stackable?: boolean;
145
166
  }
146
167
 
147
168
  /** Cupón y «lo que se lleva» de una venta cerrada (publicGetSaleCoupon). */
@@ -45,4 +45,6 @@ export enum RetailPromotionsErrorCodeEnum {
45
45
  CATALOG_SKU_NOT_FOUND = 'CATALOG_SKU_NOT_FOUND',
46
46
  /** Con el reparto apagado no se aplican palancas de precio ni código; los regalos sí. */
47
47
  SPLIT_FINANCING_DISABLED = 'SPLIT_FINANCING_DISABLED',
48
+ /** La promo no acumulable choca con otra en la misma línea. */
49
+ PROMOTION_NOT_STACKABLE = 'PROMOTION_NOT_STACKABLE',
48
50
  }
@@ -34,6 +34,11 @@ export * from './dtos/SalePromotion';
34
34
  export * from './dtos/SalePromotionLogEntry';
35
35
  export * from './dtos/DiscountCode';
36
36
  export * from './dtos/LinePromotionDiscount';
37
+ export * from './dtos/LinePromotionAmount';
38
+ export * from './dtos/SalePromotionRef';
39
+
40
+ // Validadores
41
+ export * from './validators/IsHoldModeExclusiveConstraint';
37
42
 
38
43
  // Request DTOs
39
44
  export * from './dtos/requests/DiscountValueInput';
@@ -50,6 +55,7 @@ export * from './dtos/requests/ChangeDiscountCodeStatusRequest';
50
55
  export * from './dtos/requests/ListStoreSalePromotionsQuery';
51
56
  export * from './dtos/requests/EvaluateSalePromotionsRequest';
52
57
  export * from './dtos/requests/HoldGiftPromotionInput';
58
+ export * from './dtos/requests/HoldPromotionInput';
53
59
  export * from './dtos/requests/HoldSalePromotionRequest';
54
60
  export * from './dtos/requests/GetSaleCouponQuery';
55
61
  export * from './dtos/requests/SkuSalePromotionsQuery';
@@ -0,0 +1,18 @@
1
+ import {
2
+ ValidatorConstraint,
3
+ type ValidationArguments,
4
+ type ValidatorConstraintInterface,
5
+ } from 'class-validator';
6
+
7
+ /** Rechaza mandar `promotions` junto con `gift`, `promotionId` o `saleGiftSkus` (forma vieja). */
8
+ @ValidatorConstraint({ name: 'isHoldModeExclusive', async: false })
9
+ export class IsHoldModeExclusiveConstraint implements ValidatorConstraintInterface {
10
+ validate(_value: unknown, args: ValidationArguments): boolean {
11
+ const o = args.object as { gift?: unknown; promotionId?: unknown; saleGiftSkus?: unknown };
12
+ return o.gift == null && o.promotionId == null && o.saleGiftSkus == null;
13
+ }
14
+
15
+ defaultMessage(): string {
16
+ return 'promotions no se combina con gift, promotionId ni saleGiftSkus';
17
+ }
18
+ }
@@ -5,6 +5,7 @@ import type { ExternalProviderEnum } from '../enums/ExternalProviderEnum';
5
5
  import type { CentralSyncStatusEnum } from '../enums/CentralSyncStatusEnum';
6
6
  import type { SalePaymentDetail } from './SalePaymentDetail';
7
7
  import type { SaleLine } from './SaleLine';
8
+ import type { SalePromotionRef } from '../../retailPromotions/dtos/SalePromotionRef';
8
9
 
9
10
  /**
10
11
  * Venta (modelo-datos §3). pk `SALE#<saleId>` · sk `META` · 4 GSIs (`retailerId-soldAt-index`,
@@ -32,6 +33,8 @@ export interface Sale {
32
33
  /** Promo de venta congelada al cerrar; `null` = ninguna. */
33
34
  salePromotionId?: string | null;
34
35
  salePromotionVersion?: number | null;
36
+ /** Todas las promos de venta congeladas al cerrar. */
37
+ salePromotions?: SalePromotionRef[] | null;
35
38
  /** IMEI consumido del silo también en contado (local-first); null en EXTERNAL_REFERRAL (D15). */
36
39
  imei: string | null;
37
40
  /** Lo cobrado en caja al cerrar: enganche + lo no financiado; 0 en EXTERNAL_REFERRAL. Contrato de `SaleCompletedV1`. */
@@ -1,3 +1,4 @@
1
+ import type { LinePromotionAmount } from '../../retailPromotions/dtos/LinePromotionAmount';
1
2
  import type { WizardLineRoleEnum } from '../enums/WizardLineRoleEnum';
2
3
 
3
4
  /** Campos de Promociones en la línea de sesión y de venta; solo los escribe su endpoint M2M. */
@@ -13,4 +14,7 @@ export interface WizardLinePromotionFields {
13
14
  discountCents?: number;
14
15
  /** Precio antes del descuento; en un regalo, el valor de lista con la línea a $0. */
15
16
  originalPriceCents?: number;
17
+ /** Descuento de cada promo en la línea; sin el campo, la de `promotionId` lleva todo menos el código. */
18
+ promotionDiscounts?: LinePromotionAmount[];
19
+ codeDiscountCents?: number;
16
20
  }
@@ -1,3 +1,4 @@
1
+ import type { SalePromotionRef } from '../../retailPromotions/dtos/SalePromotionRef';
1
2
  import type { WizardStateEnum } from '../enums/WizardStateEnum';
2
3
  import type { WizardStepEnum } from '../enums/WizardStepEnum';
3
4
  import type { FiadoPhoneStatusEnum } from '../enums/FiadoPhoneStatusEnum';
@@ -126,6 +127,8 @@ export interface WizardSession {
126
127
  /** Promo de venta que fijó Promociones; `null` = ninguna. */
127
128
  salePromotionId?: string | null;
128
129
  salePromotionVersion?: number | null;
130
+ /** Todas las promos de venta fijadas; `salePromotionId` queda como la principal. */
131
+ salePromotions?: SalePromotionRef[] | null;
129
132
  /** Enganche cobrado que queda por reembolsar tras cancelar. */
130
133
  refundPendingCents?: number;
131
134
  paymentMethod: PaymentMethodEnum | null;
@@ -1,5 +1,6 @@
1
- import { Expose } from 'class-transformer';
2
- import { IsInt, IsNotEmpty, IsOptional, IsString, Min, ValidateIf } from 'class-validator';
1
+ import { Expose, Type } from 'class-transformer';
2
+ import { IsArray, IsInt, IsNotEmpty, IsOptional, IsString, Min, ValidateIf, ValidateNested } from 'class-validator';
3
+ import { LinePromotionAmount } from '../../../retailPromotions/dtos/LinePromotionAmount';
3
4
 
4
5
  const hasPromotion = (o: ApplyLinePromotionRequest): boolean =>
5
6
  o.promotionId !== undefined || o.promotionVersion !== undefined;
@@ -41,4 +42,19 @@ export class ApplyLinePromotionRequest {
41
42
  @IsInt()
42
43
  @Min(0)
43
44
  originalPriceCents?: number;
45
+
46
+ /** Descuento de cada promo en la línea; `promotionId` queda como la principal. */
47
+ @Expose()
48
+ @IsOptional()
49
+ @IsArray()
50
+ @ValidateNested({ each: true })
51
+ @Type(() => LinePromotionAmount)
52
+ promotionDiscounts?: LinePromotionAmount[];
53
+
54
+ /** Parte de `discountCents` que viene del código. */
55
+ @Expose()
56
+ @IsOptional()
57
+ @IsInt()
58
+ @Min(0)
59
+ codeDiscountCents?: number;
44
60
  }
@@ -1,5 +1,6 @@
1
- import { Expose } from 'class-transformer';
2
- import { IsInt, IsNotEmpty, IsString, Min, ValidateIf } from 'class-validator';
1
+ import { Expose, Type } from 'class-transformer';
2
+ import { IsArray, IsInt, IsNotEmpty, IsOptional, IsString, Min, ValidateIf, ValidateNested } from 'class-validator';
3
+ import { SalePromotionRef } from '../../../retailPromotions/dtos/SalePromotionRef';
3
4
 
4
5
  /**
5
6
  * PUT /private/wizard/sessions/{id}/sale-promotion (privateApplySalePromotion, M2M de Promociones).
@@ -21,4 +22,12 @@ export class ApplySalePromotionRequest {
21
22
  @IsInt()
22
23
  @Min(1)
23
24
  salePromotionVersion!: number | null;
25
+
26
+ /** Todas las promos de venta fijadas; `salePromotionId` queda como la principal. */
27
+ @Expose()
28
+ @IsOptional()
29
+ @IsArray()
30
+ @ValidateNested({ each: true })
31
+ @Type(() => SalePromotionRef)
32
+ salePromotions?: SalePromotionRef[] | null;
24
33
  }
@@ -3,6 +3,7 @@ import type { WizardStateEnum } from '../../enums/WizardStateEnum';
3
3
  import type { WizardStepEnum } from '../../enums/WizardStepEnum';
4
4
  import type { WizardPayment } from '../WizardPayment';
5
5
  import type { WizardReservedLine } from '../WizardReservedLine';
6
+ import type { SalePromotionRef } from '../../../retailPromotions/dtos/SalePromotionRef';
6
7
 
7
8
  /** GET /private/wizard/sessions/{id} (privateGetWizardSession): lo que Promociones lee de la sesión. */
8
9
  export interface PrivateWizardSessionResponse {
@@ -19,6 +20,8 @@ export interface PrivateWizardSessionResponse {
19
20
  anchorLineId: string | null;
20
21
  salePromotionId: string | null;
21
22
  salePromotionVersion: number | null;
23
+ /** Todas las promos de venta fijadas; `salePromotionId` queda como la principal. */
24
+ salePromotions?: SalePromotionRef[] | null;
22
25
  /** Líneas vivas con los campos de Promociones y financedCents. */
23
26
  lines: WizardReservedLine[];
24
27
  fiadoDirectoryId?: string | null;
@@ -12,6 +12,8 @@ import {
12
12
  Min,
13
13
  ValidateNested,
14
14
  } from 'class-validator';
15
+ import { LinePromotionAmount } from '../../retailPromotions/dtos/LinePromotionAmount';
16
+ import { SalePromotionRef } from '../../retailPromotions/dtos/SalePromotionRef';
15
17
  import { RetailPromotionInboundMessageTypeEnum } from '../enums/RetailPromotionInboundMessageTypeEnum';
16
18
  import { WizardLineRoleEnum } from '../enums/WizardLineRoleEnum';
17
19
 
@@ -59,6 +61,19 @@ export class RetailPromotionInboundLine {
59
61
  @Min(0)
60
62
  discountCents?: number;
61
63
 
64
+ /** Descuento de cada promo en la línea; `promotionId` queda como la principal. */
65
+ @IsOptional()
66
+ @IsArray()
67
+ @ValidateNested({ each: true })
68
+ @Type(() => LinePromotionAmount)
69
+ promotionDiscounts?: LinePromotionAmount[];
70
+
71
+ /** Parte de `discountCents` que viene del código. */
72
+ @IsOptional()
73
+ @IsInt()
74
+ @Min(0)
75
+ codeDiscountCents?: number;
76
+
62
77
  /** Si la línea no lo trae, basePriceCents × qty. */
63
78
  @IsInt()
64
79
  @Min(0)
@@ -132,6 +147,13 @@ export class RetailPromotionInboundMessage {
132
147
  @Min(1)
133
148
  salePromotionVersion?: number | null;
134
149
 
150
+ /** Todas las promos de venta de la venta; `salePromotionId` queda como la principal. */
151
+ @IsOptional()
152
+ @IsArray()
153
+ @ValidateNested({ each: true })
154
+ @Type(() => SalePromotionRef)
155
+ salePromotions?: SalePromotionRef[] | null;
156
+
135
157
  @IsArray()
136
158
  @ArrayMinSize(1)
137
159
  @ValidateNested({ each: true })
@@ -46,6 +46,8 @@ export * from './dtos/WizardBlocked';
46
46
  export * from './dtos/WizardLineProduct';
47
47
  export * from './dtos/WizardLineIdentifiers';
48
48
  export * from './dtos/WizardLinePromotionFields';
49
+ export { LinePromotionAmount } from '../retailPromotions/dtos/LinePromotionAmount';
50
+ export { SalePromotionRef } from '../retailPromotions/dtos/SalePromotionRef';
49
51
  export * from './dtos/WizardFinancingPreview';
50
52
  export * from './dtos/WizardReservedLine';
51
53
  export * from './dtos/WizardGiftLine';