@fiado/type-kit 3.275.0 → 3.277.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 (52) hide show
  1. package/bin/biometrics/dtos/BiometricDelivery.d.ts +36 -4
  2. package/bin/biometrics/dtos/BiometricDelivery.js +40 -1
  3. package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.d.ts +18 -0
  4. package/bin/biometrics/dtos/requests/CreateBiometricVerificationRequest.js +7 -0
  5. package/bin/biometrics/enums/BiometricDeliveryModeEnum.d.ts +14 -1
  6. package/bin/biometrics/enums/BiometricDeliveryModeEnum.js +14 -1
  7. package/bin/helpdesk/dtos/ZendeskInternalAlertRequest.d.ts +8 -2
  8. package/bin/helpdesk/dtos/ZendeskInternalAlertRequest.js +5 -3
  9. package/bin/retailOrg/dtos/GoalAmounts.d.ts +18 -0
  10. package/bin/retailOrg/dtos/GoalAmounts.js +2 -0
  11. package/bin/retailOrg/dtos/requests/GoalListQuery.d.ts +16 -0
  12. package/bin/retailOrg/dtos/requests/GoalListQuery.js +35 -0
  13. package/bin/retailOrg/dtos/requests/GoalPathParams.d.ts +14 -0
  14. package/bin/retailOrg/dtos/requests/GoalPathParams.js +41 -0
  15. package/bin/retailOrg/dtos/requests/GoalUpsertRequest.d.ts +34 -0
  16. package/bin/retailOrg/dtos/requests/GoalUpsertRequest.js +60 -0
  17. package/bin/retailOrg/dtos/requests/KpiQuery.d.ts +9 -0
  18. package/bin/retailOrg/dtos/requests/KpiQuery.js +29 -0
  19. package/bin/retailOrg/dtos/requests/PrivateSellerListQuery.d.ts +27 -0
  20. package/bin/retailOrg/dtos/requests/PrivateSellerListQuery.js +45 -0
  21. package/bin/retailOrg/dtos/responses/GoalListResponse.d.ts +18 -0
  22. package/bin/retailOrg/dtos/responses/GoalListResponse.js +2 -0
  23. package/bin/retailOrg/dtos/responses/GoalUpsertResponse.d.ts +10 -0
  24. package/bin/retailOrg/dtos/responses/GoalUpsertResponse.js +2 -0
  25. package/bin/retailOrg/dtos/responses/SellerKpiResponse.d.ts +27 -0
  26. package/bin/retailOrg/dtos/responses/SellerKpiResponse.js +2 -0
  27. package/bin/retailOrg/dtos/responses/StoreKpiResponse.d.ts +34 -0
  28. package/bin/retailOrg/dtos/responses/StoreKpiResponse.js +2 -0
  29. package/bin/retailOrg/dtos/validation/PrivateSellerListResponse.d.ts +30 -0
  30. package/bin/retailOrg/dtos/validation/PrivateSellerListResponse.js +2 -0
  31. package/bin/retailOrg/enums/GoalTargetTypeEnum.d.ts +11 -0
  32. package/bin/retailOrg/enums/GoalTargetTypeEnum.js +15 -0
  33. package/bin/retailOrg/index.d.ts +12 -0
  34. package/bin/retailOrg/index.js +14 -0
  35. package/package.json +1 -1
  36. package/src/biometrics/dtos/BiometricDelivery.ts +48 -4
  37. package/src/biometrics/dtos/requests/CreateBiometricVerificationRequest.ts +20 -0
  38. package/src/biometrics/enums/BiometricDeliveryModeEnum.ts +15 -1
  39. package/src/helpdesk/dtos/ZendeskInternalAlertRequest.ts +12 -4
  40. package/src/retailOrg/dtos/GoalAmounts.ts +18 -0
  41. package/src/retailOrg/dtos/requests/GoalListQuery.ts +25 -0
  42. package/src/retailOrg/dtos/requests/GoalPathParams.ts +25 -0
  43. package/src/retailOrg/dtos/requests/GoalUpsertRequest.ts +66 -0
  44. package/src/retailOrg/dtos/requests/KpiQuery.ts +15 -0
  45. package/src/retailOrg/dtos/requests/PrivateSellerListQuery.ts +43 -0
  46. package/src/retailOrg/dtos/responses/GoalListResponse.ts +20 -0
  47. package/src/retailOrg/dtos/responses/GoalUpsertResponse.ts +10 -0
  48. package/src/retailOrg/dtos/responses/SellerKpiResponse.ts +29 -0
  49. package/src/retailOrg/dtos/responses/StoreKpiResponse.ts +36 -0
  50. package/src/retailOrg/dtos/validation/PrivateSellerListResponse.ts +32 -0
  51. package/src/retailOrg/enums/GoalTargetTypeEnum.ts +11 -0
  52. package/src/retailOrg/index.ts +14 -0
@@ -0,0 +1,27 @@
1
+ import type { GoalAmounts } from '../GoalAmounts';
2
+ import type { SoldAggregate } from './StoreKpiResponse';
3
+ /** La tienda de casa del vendedor, resuelta para el drawer. */
4
+ export interface SellerStoreRef {
5
+ storeId: string;
6
+ name: string;
7
+ zoneId: string | null;
8
+ }
9
+ /**
10
+ * Respuesta de `GET /backoffice/sellers/{sellerId}/kpis` — el drawer de un vendedor.
11
+ *
12
+ * ⚠️ `store` es la tienda de CASA (`homeStoreId`), no la única donde puede vender: `RetailUser_GT`
13
+ * también lleva `coverageStoreIds`. El agregado del lake **no filtra por tienda** — si un vendedor
14
+ * de la Tienda A cubrió un turno en la B, esa venta cuenta en su avance. Es lo correcto: la cuota es
15
+ * suya, no de la tienda. Se deja escrito porque es justo el tipo de cosa que después parece un bug.
16
+ *
17
+ * Misma regla que el gemelo de tiendas: `null`, nunca `0`.
18
+ */
19
+ export interface SellerKpiResponse {
20
+ sellerId: string;
21
+ displayName: string | null;
22
+ /** `null` si el vendedor no tiene `homeStoreId`, o si esa tienda ya no existe. */
23
+ store: SellerStoreRef | null;
24
+ goal: GoalAmounts | null;
25
+ sold: SoldAggregate | null;
26
+ goalPct: number | null;
27
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,34 @@
1
+ import type { GoalAmounts } from '../GoalAmounts';
2
+ /**
3
+ * Volumen vendido en el periodo, según el data lake.
4
+ *
5
+ * 🔴 `soldCents` NO es `amountCents` de la venta. Es `COALESCE(equipmentPriceCents, amountCents)` —
6
+ * volumen **colocado**, por DEC-032. En una venta a crédito `amountCents` es solo el enganche: un
7
+ * equipo de $5,000 financiado aportaría $1,300 y el vendedor cobraría comisión sobre eso.
8
+ */
9
+ export interface SoldAggregate {
10
+ pieces: number;
11
+ soldCents: number;
12
+ }
13
+ /**
14
+ * Respuesta de `GET /backoffice/stores/{storeId}/kpis` — el drawer de una tienda.
15
+ *
16
+ * 🔴 **`null`, nunca `0`.** Un `0` dice *«no vendió»*; un `null` dice *«no sé todavía»*, y la UI lo
17
+ * pinta `—`. Un 0 % de meta a media tarde manda a alguien a corregir algo que nadie fijó.
18
+ *
19
+ * `sellersActive` es la excepción: sale del padrón (`RetailUser_GT`), siempre se puede contar, y `0`
20
+ * ahí SÍ es un dato — una tienda sin vendedores asignados.
21
+ */
22
+ export interface StoreKpiResponse {
23
+ storeId: string;
24
+ name: string;
25
+ zoneId: string | null;
26
+ /** Vendedores ACTIVE con esta tienda como `homeStoreId`. `0` es válido. */
27
+ sellersActive: number;
28
+ /** `null` si no se fijó meta para el periodo. */
29
+ goal: GoalAmounts | null;
30
+ /** `null` si el lake no contestó (o si está apagado por flag). NUNCA ceros. */
31
+ sold: SoldAggregate | null;
32
+ /** Avance sobre la meta mensual, 1 decimal. `null` si falta cualquiera de los dos. */
33
+ goalPct: number | null;
34
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,30 @@
1
+ import type { RetailUserStatusEnum } from '../../enums/RetailUserStatusEnum';
2
+ /**
3
+ * Shape mínimo de un vendedor para el motor de comisiones (F3b): a quién pagarle, de qué tienda es y
4
+ * en qué tier va.
5
+ *
6
+ * **Sin PII** — nada de teléfono ni `cognitoSub`. `displayName` es el rótulo de conveniencia con el
7
+ * que el motor arma su reporte de devengo; no es identidad (la identidad vive en RBAC).
8
+ *
9
+ * `commissionTier` no es adorno: es lo que el motor usa para resolver CUÁNTO le toca a cada vendedor
10
+ * según el plan de comisiones vigente.
11
+ */
12
+ export interface PrivateSellerDto {
13
+ sellerId: string;
14
+ displayName: string | null;
15
+ homeStoreId: string | null;
16
+ commissionTier: string;
17
+ status: RetailUserStatusEnum;
18
+ }
19
+ /**
20
+ * Respuesta de `GET /private/sellers?retailerId=…` — el padrón de una cadena, para máquinas.
21
+ *
22
+ * ⚠️ `status` viene en cada fila a propósito. Un vendedor dado de baja a mitad de periodo **sí tiene
23
+ * comisión** por lo que vendió antes, así que el filtro `?status=` es una conveniencia y el motor de
24
+ * comisiones probablemente tenga que pedir TODOS. Pedir solo activos y perder a alguien que renunció
25
+ * el día 20 sería un error caro.
26
+ */
27
+ export interface PrivateSellerListResponse {
28
+ retailerId: string;
29
+ sellers: PrivateSellerDto[];
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A quién se le fija una meta de venta del periodo (SureKeep F3, Portal Admin VentasLuga).
3
+ *
4
+ * Es parte de la SORT KEY de `RetailGoal_GT` (`GOAL#<periodId>#<targetType>#<targetId>`), así que
5
+ * los valores son contrato de datos, no solo de API: cambiar un literal deja huérfanas las filas ya
6
+ * escritas. Solo hay dos targets y no se prevén más — la meta de una zona sale de sumar sus tiendas.
7
+ */
8
+ export declare enum GoalTargetTypeEnum {
9
+ STORE = "STORE",
10
+ SELLER = "SELLER"
11
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GoalTargetTypeEnum = void 0;
4
+ /**
5
+ * A quién se le fija una meta de venta del periodo (SureKeep F3, Portal Admin VentasLuga).
6
+ *
7
+ * Es parte de la SORT KEY de `RetailGoal_GT` (`GOAL#<periodId>#<targetType>#<targetId>`), así que
8
+ * los valores son contrato de datos, no solo de API: cambiar un literal deja huérfanas las filas ya
9
+ * escritas. Solo hay dos targets y no se prevén más — la meta de una zona sale de sumar sus tiendas.
10
+ */
11
+ var GoalTargetTypeEnum;
12
+ (function (GoalTargetTypeEnum) {
13
+ GoalTargetTypeEnum["STORE"] = "STORE";
14
+ GoalTargetTypeEnum["SELLER"] = "SELLER";
15
+ })(GoalTargetTypeEnum || (exports.GoalTargetTypeEnum = GoalTargetTypeEnum = {}));
@@ -5,7 +5,9 @@ export * from './enums/StoreStatusEnum';
5
5
  export * from './enums/RetailUserStatusEnum';
6
6
  export * from './enums/EmploymentTypeEnum';
7
7
  export * from './enums/CollectorSeniorityEnum';
8
+ export * from './enums/GoalTargetTypeEnum';
8
9
  export * from './dtos/GeoLocation';
10
+ export * from './dtos/GoalAmounts';
9
11
  export * from './dtos/Retailer';
10
12
  export * from './dtos/Zone';
11
13
  export * from './dtos/Store';
@@ -14,6 +16,7 @@ export * from './dtos/validation/RetailerValidationDto';
14
16
  export * from './dtos/validation/StoreValidationDto';
15
17
  export * from './dtos/validation/RetailUserValidationDto';
16
18
  export * from './dtos/validation/CollectorValidationDto';
19
+ export * from './dtos/validation/PrivateSellerListResponse';
17
20
  export * from './dtos/PaginatedResult';
18
21
  export * from './dtos/requests/RetailAddressInput';
19
22
  export * from './dtos/requests/CreateRetailerRequest';
@@ -31,4 +34,13 @@ export * from './dtos/requests/ProvisionRetailUserRequest';
31
34
  export * from './dtos/requests/UpdateRetailUserRequest';
32
35
  export * from './dtos/requests/ChangeRetailUserStatusRequest';
33
36
  export * from './dtos/requests/TransferRetailUserRequest';
37
+ export * from './dtos/requests/GoalListQuery';
38
+ export * from './dtos/requests/GoalUpsertRequest';
39
+ export * from './dtos/requests/GoalPathParams';
40
+ export * from './dtos/requests/KpiQuery';
41
+ export * from './dtos/requests/PrivateSellerListQuery';
34
42
  export * from './dtos/responses/RetailerHierarchyResponse';
43
+ export * from './dtos/responses/GoalListResponse';
44
+ export * from './dtos/responses/GoalUpsertResponse';
45
+ export * from './dtos/responses/StoreKpiResponse';
46
+ export * from './dtos/responses/SellerKpiResponse';
@@ -21,8 +21,11 @@ __exportStar(require("./enums/StoreStatusEnum"), exports);
21
21
  __exportStar(require("./enums/RetailUserStatusEnum"), exports);
22
22
  __exportStar(require("./enums/EmploymentTypeEnum"), exports);
23
23
  __exportStar(require("./enums/CollectorSeniorityEnum"), exports);
24
+ // F3 — Portal Admin VentasLuga (metas de venta del periodo)
25
+ __exportStar(require("./enums/GoalTargetTypeEnum"), exports);
24
26
  // Entity DTOs
25
27
  __exportStar(require("./dtos/GeoLocation"), exports);
28
+ __exportStar(require("./dtos/GoalAmounts"), exports);
26
29
  __exportStar(require("./dtos/Retailer"), exports);
27
30
  __exportStar(require("./dtos/Zone"), exports);
28
31
  __exportStar(require("./dtos/Store"), exports);
@@ -32,6 +35,7 @@ __exportStar(require("./dtos/validation/RetailerValidationDto"), exports);
32
35
  __exportStar(require("./dtos/validation/StoreValidationDto"), exports);
33
36
  __exportStar(require("./dtos/validation/RetailUserValidationDto"), exports);
34
37
  __exportStar(require("./dtos/validation/CollectorValidationDto"), exports);
38
+ __exportStar(require("./dtos/validation/PrivateSellerListResponse"), exports);
35
39
  // Paginación genérica
36
40
  __exportStar(require("./dtos/PaginatedResult"), exports);
37
41
  // Request DTOs (input validado por endpoint)
@@ -51,5 +55,15 @@ __exportStar(require("./dtos/requests/ProvisionRetailUserRequest"), exports);
51
55
  __exportStar(require("./dtos/requests/UpdateRetailUserRequest"), exports);
52
56
  __exportStar(require("./dtos/requests/ChangeRetailUserStatusRequest"), exports);
53
57
  __exportStar(require("./dtos/requests/TransferRetailUserRequest"), exports);
58
+ // F3 — metas y KPIs del Portal Admin VL
59
+ __exportStar(require("./dtos/requests/GoalListQuery"), exports);
60
+ __exportStar(require("./dtos/requests/GoalUpsertRequest"), exports);
61
+ __exportStar(require("./dtos/requests/GoalPathParams"), exports);
62
+ __exportStar(require("./dtos/requests/KpiQuery"), exports);
63
+ __exportStar(require("./dtos/requests/PrivateSellerListQuery"), exports);
54
64
  // Response DTOs
55
65
  __exportStar(require("./dtos/responses/RetailerHierarchyResponse"), exports);
66
+ __exportStar(require("./dtos/responses/GoalListResponse"), exports);
67
+ __exportStar(require("./dtos/responses/GoalUpsertResponse"), exports);
68
+ __exportStar(require("./dtos/responses/StoreKpiResponse"), exports);
69
+ __exportStar(require("./dtos/responses/SellerKpiResponse"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/type-kit",
3
- "version": "3.275.0",
3
+ "version": "3.277.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "types": "bin/index.d.ts",
@@ -30,6 +30,45 @@ export class RedirectDelivery {
30
30
  expiresAt!: string;
31
31
  }
32
32
 
33
+ /**
34
+ * `deliveryMode = EMBEDDED_SDK`. El caller monta el SDK del proveedor **en su propia pantalla** con
35
+ * estos parámetros, en vez de mandar al usuario a un link hospedado.
36
+ *
37
+ * Para qué: hay flujos donde el usuario está PRESENTE frente a un dispositivo que no es el suyo —el
38
+ * vendedor le pasa la tablet de la tienda— y sacarlo a un navegador externo rompe la atención.
39
+ * Mismo biométrico y mismo veredicto que `REDIRECT`; lo único distinto es dónde corre.
40
+ *
41
+ * ⚠️ **Ninguno de los tres campos es secreto.** El `clientId` y el `flowId` viajan al navegador como
42
+ * parámetros del widget y se leen en el inspector; el `metadata` lo devuelve el proveedor tal cual
43
+ * en cada webhook. Lo que SÍ es secreto —el Client Secret con el que se firma el
44
+ * `userPhotoLinkHash`— nunca sale de `metamap-connector` y no viaja acá.
45
+ */
46
+ export class EmbeddedSdkDelivery {
47
+ /** `merchantToken` del proveedor. Público: viaja al navegador. */
48
+ @Expose() @IsString() @IsNotEmpty()
49
+ clientId!: string;
50
+
51
+ /** El flow del proveedor a ejecutar. Sale del catálogo, no de una env var del caller. */
52
+ @Expose() @IsString() @IsNotEmpty()
53
+ flowId!: string;
54
+
55
+ /**
56
+ * El `metadata` ya serializado, **listo para pasárselo al SDK tal cual**.
57
+ *
58
+ * 🔴 Es un JSON string, NO un objeto y NO base64. El proveedor lo devuelve literal en cada
59
+ * webhook y es el único canal de correlación que existe. Si el caller lo re-serializa o lo
60
+ * codifica, el `userPhotoLink` que el proveedor reconstruye deja de ser idéntico al que se
61
+ * firmó, el hash del facematch no cuadra, y rechaza la verificación con «Invalid hash or url»
62
+ * DESPUÉS de que el usuario ya se tomó la selfie. Pasarlo sin tocarlo.
63
+ */
64
+ @Expose() @IsString() @IsNotEmpty()
65
+ metadata!: string;
66
+
67
+ /** Hasta cuándo sirven estos parámetros. ISO-8601 UTC con `Z`. */
68
+ @Expose() @IsISO8601({ strict: true })
69
+ expiresAt!: string;
70
+ }
71
+
33
72
  /**
34
73
  * `deliveryMode = CHALLENGE`. El biométrico corre en el dispositivo (Apple Face ID, biometría de
35
74
  * Android) y el cliente devuelve una attestation firmada.
@@ -72,10 +111,15 @@ export class ImmediateDelivery {
72
111
  *
73
112
  * ```ts
74
113
  * switch (res.deliveryMode) {
75
- * case BiometricDeliveryModeEnum.REDIRECT: abrir((res.delivery as RedirectDelivery).link); break;
76
- * case BiometricDeliveryModeEnum.CHALLENGE: firmar(res.delivery as ChallengeDelivery); break;
77
- * case BiometricDeliveryModeEnum.IMMEDIATE: leer(res.result); break;
114
+ * case BiometricDeliveryModeEnum.REDIRECT: abrir((res.delivery as RedirectDelivery).link); break;
115
+ * case BiometricDeliveryModeEnum.EMBEDDED_SDK: montarSdk(res.delivery as EmbeddedSdkDelivery); break;
116
+ * case BiometricDeliveryModeEnum.CHALLENGE: firmar(res.delivery as ChallengeDelivery); break;
117
+ * case BiometricDeliveryModeEnum.IMMEDIATE: leer(res.result); break;
78
118
  * }
79
119
  * ```
80
120
  */
81
- export type BiometricDelivery = RedirectDelivery | ChallengeDelivery | ImmediateDelivery;
121
+ export type BiometricDelivery =
122
+ | RedirectDelivery
123
+ | EmbeddedSdkDelivery
124
+ | ChallengeDelivery
125
+ | ImmediateDelivery;
@@ -10,6 +10,7 @@ import {
10
10
  Min,
11
11
  ValidateNested,
12
12
  } from 'class-validator';
13
+ import { BiometricDeliveryModeEnum } from '../../enums/BiometricDeliveryModeEnum';
13
14
  import { BiometricProviderEnum } from '../../enums/BiometricProviderEnum';
14
15
  import { BiometricTypeEnum } from '../../enums/BiometricTypeEnum';
15
16
  import { BiometricSubject } from '../BiometricSubject';
@@ -98,4 +99,23 @@ export class CreateBiometricVerificationRequest {
98
99
  */
99
100
  @Expose() @IsOptional() @IsString() @MaxLength(256)
100
101
  callerContext?: string;
102
+
103
+ /**
104
+ * CÓMO quieres recibir lo que hace falta para ejecutar el biométrico. Default `REDIRECT`.
105
+ *
106
+ * Es una **preferencia, no una orden**: el servicio responde con el modo que el proveedor
107
+ * elegido soporta de verdad, y el modo real siempre viene en `deliveryMode` de la respuesta.
108
+ * Nunca asumas que te tocó el que pediste — discrimina por el de la respuesta.
109
+ *
110
+ * Con `METAMAP` hoy:
111
+ * - `REDIRECT` → link hospedado y acortado. Para mandárselo al usuario a SU dispositivo.
112
+ * - `EMBEDDED_SDK` → los parámetros para montar el widget en TU pantalla. Para cuando el
113
+ * usuario está presente frente a un dispositivo que no es el suyo.
114
+ *
115
+ * Pedir un modo que el proveedor no soporta responde `422 DELIVERY_MODE_NOT_SUPPORTED` — no se
116
+ * degrada en silencio al default, porque el caller que pidió SDK y recibe un link no tiene
117
+ * dónde montarlo y se entera hasta que la pantalla queda en blanco.
118
+ */
119
+ @Expose() @IsOptional() @IsEnum(BiometricDeliveryModeEnum)
120
+ deliveryMode?: BiometricDeliveryModeEnum;
101
121
  }
@@ -17,12 +17,26 @@
17
17
  *
18
18
  * El `GET` de polling NO cambia entre modos: devuelve estado, no mecanismo.
19
19
  *
20
- * `biometrics-business` — Entrega 1 (solo `REDIRECT` produce valores reales).
20
+ * `biometrics-business` — Entrega 1: `REDIRECT` y `EMBEDDED_SDK` producen valores reales.
21
21
  */
22
22
  export enum BiometricDeliveryModeEnum {
23
23
  /** `delivery` trae `{ link, expiresAt }`. El usuario abre el link y ejecuta ahí. */
24
24
  REDIRECT = 'REDIRECT',
25
25
 
26
+ /**
27
+ * `delivery` trae `{ clientId, flowId, metadata, expiresAt }`. El caller monta el SDK del
28
+ * proveedor **en su propia pantalla** con esos parámetros, en vez de mandar al usuario a un
29
+ * link hospedado.
30
+ *
31
+ * Existe porque hay flujos donde el usuario está PRESENTE frente a un dispositivo que no es el
32
+ * suyo — el vendedor le pasa la tablet de la tienda — y sacarlo a un navegador externo rompe la
33
+ * atención. Mismo biométrico, mismo veredicto, distinta superficie.
34
+ *
35
+ * Es exactamente lo que hoy arma a mano `retail-wizard-business` para su modalidad
36
+ * `STORE_DEVICE`; este modo existe para que deje de armarlo.
37
+ */
38
+ EMBEDDED_SDK = 'EMBEDDED_SDK',
39
+
26
40
  /** `delivery` trae `{ challenge, nonce, expiresAt }`. El dispositivo firma y devuelve. */
27
41
  CHALLENGE = 'CHALLENGE',
28
42
 
@@ -1,4 +1,4 @@
1
- import { IsEnum, IsNotEmpty, IsOptional, IsString } from "class-validator";
1
+ import { ArrayNotEmpty, IsArray, IsEnum, IsNotEmpty, IsOptional, IsString } from "class-validator";
2
2
  import { InternalAlertChannel } from "../enums/InternalAlertChannelEnum";
3
3
 
4
4
  /**
@@ -20,9 +20,17 @@ export class ZendeskInternalAlertRequest {
20
20
  @IsString()
21
21
  note: string;
22
22
 
23
- /** Decide el ruteo en Zendesk (tag, vista y equipo). */
24
- @IsEnum(InternalAlertChannel)
25
- channel: InternalAlertChannel;
23
+ /**
24
+ * Equipos que deben ver la alerta. Un mismo hecho puede requerir a los dos: por ejemplo un
25
+ * status de verificación que Compliance evalúa y el equipo interno corrige.
26
+ *
27
+ * Los hilos se agrupan por combinación exacta: una alerta `[COMPLIANCE]` no cae en el ticket
28
+ * de `[INTERNAL, COMPLIANCE]`, para que restringir un canal por rol siga siendo posible.
29
+ */
30
+ @IsArray()
31
+ @ArrayNotEmpty()
32
+ @IsEnum(InternalAlertChannel, { each: true })
33
+ channels: InternalAlertChannel[];
26
34
 
27
35
  /** Id del evento origen, para rastrear la alerta de vuelta. */
28
36
  @IsString()
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Las tres cifras de una meta — y las tres salen de UNA SOLA captura.
3
+ *
4
+ * 🔴 Solo `monthCents` se guarda. `weekCents` (`mes/4`) y `dayCents` (`mes/28`) las DERIVA el backend
5
+ * en cada lectura, y por eso `PUT /backoffice/goals` las RECHAZA si vienen en el request: aceptarlas
6
+ * del cliente abre la puerta a que lleguen desincronizadas entre sí y a que dos pantallas midan el
7
+ * mismo avance contra números distintos.
8
+ *
9
+ * Se devuelven ya calculadas para que ninguna pantalla repita la división.
10
+ */
11
+ export interface GoalAmounts {
12
+ /** La meta mensual en centavos — lo único persistido. */
13
+ monthCents: number;
14
+ /** Derivada: `monthCents / 4`, redondeada. */
15
+ weekCents: number;
16
+ /** Derivada: `monthCents / 28`, redondeada. */
17
+ dayCents: number;
18
+ }
@@ -0,0 +1,25 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsEnum, IsOptional, Matches } from 'class-validator';
3
+ import { GoalTargetTypeEnum } from '../../enums/GoalTargetTypeEnum';
4
+
5
+ /**
6
+ * Query de `GET /backoffice/goals` — las metas de un periodo.
7
+ *
8
+ * ⚠️ `retailerId` NO es parámetro: sale del token (D10). Un `?retailerId=` en el request se ignora a
9
+ * propósito — aceptarlo dejaría que un Admin VL mire las metas de la cadena de al lado.
10
+ */
11
+ export class GoalListQuery {
12
+ /**
13
+ * Mes calendario `YYYY-MM`. El regex acota el mes a 01-12: `\d{2}` a secas aceptaría `2026-13`,
14
+ * que escribiría una partición que ninguna consulta vuelve a encontrar.
15
+ */
16
+ @Expose()
17
+ @Matches(/^\d{4}-(0[1-9]|1[0-2])$/, { message: 'periodId debe tener formato YYYY-MM (mes 01-12)' })
18
+ periodId!: string;
19
+
20
+ /** Si falta, devuelve los dos tipos — el `begins_with` del periodo ya los trae juntos. */
21
+ @Expose()
22
+ @IsOptional()
23
+ @IsEnum(GoalTargetTypeEnum)
24
+ targetType?: GoalTargetTypeEnum;
25
+ }
@@ -0,0 +1,25 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsEnum, IsString, Matches } from 'class-validator';
3
+ import { GoalTargetTypeEnum } from '../../enums/GoalTargetTypeEnum';
4
+
5
+ /**
6
+ * Path params de `DELETE /backoffice/goals/{periodId}/{targetType}/{targetId}`.
7
+ *
8
+ * Una meta NO tiene id propio: se identifica por la terna *(periodo, tipo, target)*, que es
9
+ * exactamente su sort key. Por eso los tres van en el path y por eso hay un DTO para validarlos —
10
+ * un `targetType` fuera del enum arma una sk que nunca matchea y devolvería un 404 mentiroso en vez
11
+ * del 400 que corresponde.
12
+ */
13
+ export class GoalPathParams {
14
+ @Expose()
15
+ @Matches(/^\d{4}-(0[1-9]|1[0-2])$/, { message: 'periodId debe tener formato YYYY-MM (mes 01-12)' })
16
+ periodId!: string;
17
+
18
+ @Expose()
19
+ @IsEnum(GoalTargetTypeEnum)
20
+ targetType!: GoalTargetTypeEnum;
21
+
22
+ @Expose()
23
+ @IsString()
24
+ targetId!: string;
25
+ }
@@ -0,0 +1,66 @@
1
+ import { Expose, Type } from 'class-transformer';
2
+ import {
3
+ ArrayMaxSize,
4
+ ArrayMinSize,
5
+ IsArray,
6
+ IsEnum,
7
+ IsInt,
8
+ IsString,
9
+ Matches,
10
+ Min,
11
+ ValidateNested,
12
+ } from 'class-validator';
13
+ import { GoalTargetTypeEnum } from '../../enums/GoalTargetTypeEnum';
14
+
15
+ /** Una meta del lote. */
16
+ export class GoalUpsertTarget {
17
+ /** ULID de la tienda o del vendedor. */
18
+ @Expose()
19
+ @IsString()
20
+ targetId!: string;
21
+
22
+ @Expose()
23
+ @IsEnum(GoalTargetTypeEnum)
24
+ targetType!: GoalTargetTypeEnum;
25
+
26
+ /**
27
+ * La meta MENSUAL en centavos — lo único que se guarda.
28
+ *
29
+ * 🔴 `0` NO borra: cero significa «la meta es cero» y rompe el `goalPct` con una división entre
30
+ * cero. Para quitar una meta está `DELETE /backoffice/goals/{periodId}/{targetType}/{targetId}`;
31
+ * sin fila significa «no se fijó» y la UI pinta `—`.
32
+ */
33
+ @Expose()
34
+ @IsInt()
35
+ @Min(0)
36
+ monthCents!: number;
37
+ }
38
+
39
+ /**
40
+ * Body de `PUT /backoffice/goals` — upsert POR LOTE de las metas de un periodo.
41
+ *
42
+ * ⚠️ `weekCents` y `dayCents` NO están declarados a propósito, y el manager los RECHAZA con 400 si
43
+ * llegan: son derivadas (`mes/4`, `mes/28`). El `@Expose()` + `excludeExtraneousValues` los
44
+ * descartaría en silencio, y un descarte silencioso sobre un número contra el que se le paga a la
45
+ * gente es peor que un error — el cliente creería que los guardó.
46
+ */
47
+ export class GoalUpsertRequest {
48
+ /** Mes calendario `YYYY-MM`, mes acotado a 01-12. */
49
+ @Expose()
50
+ @Matches(/^\d{4}-(0[1-9]|1[0-2])$/, { message: 'periodId debe tener formato YYYY-MM (mes 01-12)' })
51
+ periodId!: string;
52
+
53
+ /**
54
+ * El lote: 1..50 metas.
55
+ *
56
+ * 🔴 El tope es 50, no 100: `TransactWriteItems` acepta 100 ítems y **cada meta consume dos** (la
57
+ * fila de la meta + su fila de auditoría, que va en la MISMA transacción).
58
+ */
59
+ @Expose()
60
+ @IsArray()
61
+ @ArrayMinSize(1)
62
+ @ArrayMaxSize(50)
63
+ @ValidateNested({ each: true })
64
+ @Type(() => GoalUpsertTarget)
65
+ targets!: GoalUpsertTarget[];
66
+ }
@@ -0,0 +1,15 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsOptional, Matches } from 'class-validator';
3
+
4
+ /**
5
+ * Query compartido por los dos drawers de KPIs del Portal Admin VL
6
+ * (`GET /backoffice/stores/{storeId}/kpis` y `GET /backoffice/sellers/{sellerId}/kpis`).
7
+ *
8
+ * `periodId` es OPCIONAL: sin él se usa el mes en curso — el drawer se abre sin elegir periodo.
9
+ */
10
+ export class KpiQuery {
11
+ @Expose()
12
+ @IsOptional()
13
+ @Matches(/^\d{4}-(0[1-9]|1[0-2])$/, { message: 'periodId debe tener formato YYYY-MM (mes 01-12)' })
14
+ periodId?: string;
15
+ }
@@ -0,0 +1,43 @@
1
+ import { Expose } from 'class-transformer';
2
+ import { IsEnum, IsOptional, Matches } from 'class-validator';
3
+ import { RetailUserStatusEnum } from '../../enums/RetailUserStatusEnum';
4
+
5
+ /** ULID canónico: 26 chars Crockford base32, primer char 0-7 (el timestamp cabe en 48 bits). */
6
+ const ULID = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/;
7
+
8
+ /**
9
+ * Query de `GET /private/sellers` — el padrón de una cadena, M2M.
10
+ *
11
+ * 🔴 Acá el `retailerId` **viene en el request**, al revés que en los `/backoffice/` (donde se impone
12
+ * desde el token). Es el modelo M2M de la casa: el endpoint es VPC-only y su seguridad es de RED, no
13
+ * de identidad. Cualquier lambda dentro de la VPC puede pedir cualquier retailer — decisión
14
+ * consciente, no un descuido.
15
+ */
16
+ export class PrivateSellerListQuery {
17
+ @Expose()
18
+ @Matches(ULID, { message: 'retailerId debe ser un ULID' })
19
+ retailerId!: string;
20
+
21
+ /**
22
+ * ⚠️ **Sin `status` vienen TODOS los estados, no solo los activos.**
23
+ *
24
+ * El diseño original proponía default `ACTIVE`, pero con este enum eso deja al motor de comisiones
25
+ * sin ninguna forma de pedir el padrón completo — y un vendedor dado de baja el día 20 sí tiene
26
+ * comisión por lo que vendió hasta el 19. Filtrar por default sería perderlo en silencio.
27
+ */
28
+ @Expose()
29
+ @IsOptional()
30
+ @IsEnum(RetailUserStatusEnum)
31
+ status?: RetailUserStatusEnum;
32
+
33
+ /**
34
+ * Quién llama. `Feature.ANONIMUS` no trae actor, y este endpoint exporta la nómina completa de una
35
+ * cadena: sin esto el registro de acceso a PII quedaría sin nadie a quien atribuírselo. Lo llena el
36
+ * publisher de `@fiado/api-invoker`; si falta, el lambda registra `m2m:unknown` y loguea un WARN —
37
+ * nunca omite el registro.
38
+ */
39
+ @Expose()
40
+ @IsOptional()
41
+ @Matches(/^[a-z0-9-]{1,64}$/, { message: 'callerService debe ser el nombre del lambda (kebab-case)' })
42
+ callerService?: string;
43
+ }
@@ -0,0 +1,20 @@
1
+ import type { GoalAmounts } from '../GoalAmounts';
2
+ import type { GoalTargetTypeEnum } from '../../enums/GoalTargetTypeEnum';
3
+
4
+ /** Una meta del periodo, con las tres cifras ya derivadas. */
5
+ export interface GoalDto extends GoalAmounts {
6
+ targetId: string;
7
+ targetType: GoalTargetTypeEnum;
8
+ }
9
+
10
+ /**
11
+ * Respuesta de `GET /backoffice/goals`.
12
+ *
13
+ * ⚠️ Un periodo sin metas devuelve `goals: []`, **no un 404**: que no se hayan fijado es un estado
14
+ * válido del negocio, no una ruta que no existe. Y una meta borrada simplemente no viene en la
15
+ * lista — no hay estado «borrada», la fila deja de existir.
16
+ */
17
+ export interface GoalListResponse {
18
+ periodId: string;
19
+ goals: GoalDto[];
20
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Respuesta de `PUT /backoffice/goals` — cuántas metas quedaron escritas.
3
+ *
4
+ * `updated` siempre iguala `targets.length` del request: el lote es todo-o-nada (una sola
5
+ * `TransactWriteItems`). Nunca hay un lote parcial que explicar.
6
+ */
7
+ export interface GoalUpsertResponse {
8
+ periodId: string;
9
+ updated: number;
10
+ }
@@ -0,0 +1,29 @@
1
+ import type { GoalAmounts } from '../GoalAmounts';
2
+ import type { SoldAggregate } from './StoreKpiResponse';
3
+
4
+ /** La tienda de casa del vendedor, resuelta para el drawer. */
5
+ export interface SellerStoreRef {
6
+ storeId: string;
7
+ name: string;
8
+ zoneId: string | null;
9
+ }
10
+
11
+ /**
12
+ * Respuesta de `GET /backoffice/sellers/{sellerId}/kpis` — el drawer de un vendedor.
13
+ *
14
+ * ⚠️ `store` es la tienda de CASA (`homeStoreId`), no la única donde puede vender: `RetailUser_GT`
15
+ * también lleva `coverageStoreIds`. El agregado del lake **no filtra por tienda** — si un vendedor
16
+ * de la Tienda A cubrió un turno en la B, esa venta cuenta en su avance. Es lo correcto: la cuota es
17
+ * suya, no de la tienda. Se deja escrito porque es justo el tipo de cosa que después parece un bug.
18
+ *
19
+ * Misma regla que el gemelo de tiendas: `null`, nunca `0`.
20
+ */
21
+ export interface SellerKpiResponse {
22
+ sellerId: string;
23
+ displayName: string | null;
24
+ /** `null` si el vendedor no tiene `homeStoreId`, o si esa tienda ya no existe. */
25
+ store: SellerStoreRef | null;
26
+ goal: GoalAmounts | null;
27
+ sold: SoldAggregate | null;
28
+ goalPct: number | null;
29
+ }