@fiado/api-invoker 5.23.0 → 5.24.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 (30) hide show
  1. package/bin/membership/MembershipApi.d.ts +12 -0
  2. package/bin/membership/MembershipApi.js +32 -0
  3. package/bin/membership/index.d.ts +2 -0
  4. package/bin/membership/index.js +2 -0
  5. package/bin/membership/interfaces/IMembershipApi.d.ts +15 -0
  6. package/bin/membership/interfaces/IMembershipApi.js +1 -0
  7. package/bin/notificationWebsockets/NotificationWebsocketApi.d.ts +12 -0
  8. package/bin/notificationWebsockets/NotificationWebsocketApi.js +32 -0
  9. package/bin/notificationWebsockets/interfaces/INotificationWebsocketApi.d.ts +15 -0
  10. package/bin/notificationWebsockets/interfaces/INotificationWebsocketApi.js +1 -0
  11. package/bin/performance-profile/PerformanceProfileApi.d.ts +12 -0
  12. package/bin/performance-profile/PerformanceProfileApi.js +32 -0
  13. package/bin/performance-profile/index.d.ts +2 -0
  14. package/bin/performance-profile/index.js +2 -0
  15. package/bin/performance-profile/interfaces/IPerformanceProfileApi.d.ts +15 -0
  16. package/bin/performance-profile/interfaces/IPerformanceProfileApi.js +1 -0
  17. package/bin/platformRbac/resolver/ApiInvokerPermissionResolver.js +12 -1
  18. package/bin/presence-websocket/PresenceWebsocketApi.d.ts +12 -0
  19. package/bin/presence-websocket/PresenceWebsocketApi.js +32 -0
  20. package/bin/presence-websocket/index.d.ts +2 -0
  21. package/bin/presence-websocket/index.js +2 -0
  22. package/bin/presence-websocket/interfaces/IPresenceWebsocketApi.d.ts +15 -0
  23. package/bin/presence-websocket/interfaces/IPresenceWebsocketApi.js +1 -0
  24. package/bin/retailOrg/api/RetailOrgBusinessApi.d.ts +9 -2
  25. package/bin/retailOrg/api/RetailOrgBusinessApi.js +16 -1
  26. package/bin/retailOrg/api/interfaces/IRetailOrgBusinessApi.d.ts +45 -3
  27. package/package.json +1 -1
  28. package/src/platformRbac/resolver/ApiInvokerPermissionResolver.ts +12 -1
  29. package/src/retailOrg/api/RetailOrgBusinessApi.ts +24 -2
  30. package/src/retailOrg/api/interfaces/IRetailOrgBusinessApi.ts +51 -2
@@ -0,0 +1,12 @@
1
+ import { IHttpRequest } from "@fiado/http-client";
2
+ import { IMembershipApi } from "./interfaces/IMembershipApi.js";
3
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
4
+ export declare class MembershipApi implements IMembershipApi {
5
+ private httpRequest;
6
+ private readonly baseUrl;
7
+ constructor(httpRequest: IHttpRequest);
8
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
9
+ deletedCount: number;
10
+ }>>;
11
+ }
12
+ export {};
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { inject, injectable } from "inversify";
14
+ import dotenv from 'dotenv';
15
+ dotenv.config();
16
+ let MembershipApi = class MembershipApi {
17
+ httpRequest;
18
+ baseUrl = process.env.MEMBERSHIP_BUSINESS_LAMBDA_URL || "";
19
+ constructor(httpRequest) {
20
+ this.httpRequest = httpRequest;
21
+ }
22
+ async deleteByDirectoryId(directoryId) {
23
+ const url = `${this.baseUrl}private/directory/${directoryId}`;
24
+ return await this.httpRequest.delete(url);
25
+ }
26
+ };
27
+ MembershipApi = __decorate([
28
+ injectable(),
29
+ __param(0, inject("IHttpRequest")),
30
+ __metadata("design:paramtypes", [Object])
31
+ ], MembershipApi);
32
+ export { MembershipApi };
@@ -0,0 +1,2 @@
1
+ export * from './interfaces/IMembershipApi.js';
2
+ export * from './MembershipApi.js';
@@ -0,0 +1,2 @@
1
+ export * from './interfaces/IMembershipApi.js';
2
+ export * from './MembershipApi.js';
@@ -0,0 +1,15 @@
1
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
2
+ /**
3
+ * Interfaz para el servicio Membership (membership-business).
4
+ */
5
+ export interface IMembershipApi {
6
+ /**
7
+ * Borra en cascada las membresías asociadas a un directoryId.
8
+ * @param directoryId ID del directorio.
9
+ * @throws {Error} Lanza un error si los parámetros de entrada son inválidos.
10
+ */
11
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
12
+ deletedCount: number;
13
+ }>>;
14
+ }
15
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { IHttpRequest } from "@fiado/http-client";
2
+ import { INotificationWebsocketApi } from "./interfaces/INotificationWebsocketApi.js";
3
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
4
+ export declare class NotificationWebsocketApi implements INotificationWebsocketApi {
5
+ private httpRequest;
6
+ private readonly baseUrl;
7
+ constructor(httpRequest: IHttpRequest);
8
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
9
+ deletedCount: number;
10
+ }>>;
11
+ }
12
+ export {};
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { inject, injectable } from "inversify";
14
+ import dotenv from 'dotenv';
15
+ dotenv.config();
16
+ let NotificationWebsocketApi = class NotificationWebsocketApi {
17
+ httpRequest;
18
+ baseUrl = process.env.NOTIFICATIONS_WEBSOCKET_LAMBDA_URL || "";
19
+ constructor(httpRequest) {
20
+ this.httpRequest = httpRequest;
21
+ }
22
+ async deleteByDirectoryId(directoryId) {
23
+ const url = `${this.baseUrl}private/directory/${directoryId}`;
24
+ return await this.httpRequest.delete(url);
25
+ }
26
+ };
27
+ NotificationWebsocketApi = __decorate([
28
+ injectable(),
29
+ __param(0, inject("IHttpRequest")),
30
+ __metadata("design:paramtypes", [Object])
31
+ ], NotificationWebsocketApi);
32
+ export { NotificationWebsocketApi };
@@ -0,0 +1,15 @@
1
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
2
+ /**
3
+ * Interfaz para el servicio Notifications Websocket (notifications-websocket).
4
+ */
5
+ export interface INotificationWebsocketApi {
6
+ /**
7
+ * Borra en cascada las conexiones/notificaciones WS asociadas a un directoryId.
8
+ * @param directoryId ID del directorio.
9
+ * @throws {Error} Lanza un error si los parámetros de entrada son inválidos.
10
+ */
11
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
12
+ deletedCount: number;
13
+ }>>;
14
+ }
15
+ export {};
@@ -0,0 +1,12 @@
1
+ import { IHttpRequest } from "@fiado/http-client";
2
+ import { IPerformanceProfileApi } from "./interfaces/IPerformanceProfileApi.js";
3
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
4
+ export declare class PerformanceProfileApi implements IPerformanceProfileApi {
5
+ private httpRequest;
6
+ private readonly baseUrl;
7
+ constructor(httpRequest: IHttpRequest);
8
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
9
+ deletedCount: number;
10
+ }>>;
11
+ }
12
+ export {};
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { inject, injectable } from "inversify";
14
+ import dotenv from 'dotenv';
15
+ dotenv.config();
16
+ let PerformanceProfileApi = class PerformanceProfileApi {
17
+ httpRequest;
18
+ baseUrl = process.env.PERFORMANCE_PROFILE_BUSINESS_LAMBDA_URL || "";
19
+ constructor(httpRequest) {
20
+ this.httpRequest = httpRequest;
21
+ }
22
+ async deleteByDirectoryId(directoryId) {
23
+ const url = `${this.baseUrl}private/directory/${directoryId}`;
24
+ return await this.httpRequest.delete(url);
25
+ }
26
+ };
27
+ PerformanceProfileApi = __decorate([
28
+ injectable(),
29
+ __param(0, inject("IHttpRequest")),
30
+ __metadata("design:paramtypes", [Object])
31
+ ], PerformanceProfileApi);
32
+ export { PerformanceProfileApi };
@@ -0,0 +1,2 @@
1
+ export * from './interfaces/IPerformanceProfileApi.js';
2
+ export * from './PerformanceProfileApi.js';
@@ -0,0 +1,2 @@
1
+ export * from './interfaces/IPerformanceProfileApi.js';
2
+ export * from './PerformanceProfileApi.js';
@@ -0,0 +1,15 @@
1
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
2
+ /**
3
+ * Interfaz para el servicio Performance Profile (performance-profile-business).
4
+ */
5
+ export interface IPerformanceProfileApi {
6
+ /**
7
+ * Borra en cascada los perfiles de desempeño asociados a un directoryId.
8
+ * @param directoryId ID del directorio.
9
+ * @throws {Error} Lanza un error si los parámetros de entrada son inválidos.
10
+ */
11
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
12
+ deletedCount: number;
13
+ }>>;
14
+ }
15
+ export {};
@@ -38,7 +38,18 @@ let ApiInvokerPermissionResolver = class ApiInvokerPermissionResolver {
38
38
  }
39
39
  async resolve(input) {
40
40
  const cacheable = input.roleAssignmentsFromToken === undefined;
41
- const key = `${input.issuer ?? ""}#${input.cognitoSub}`;
41
+ // DEC-RBAC-124: el tipo de principal entra en la cache key. NO es defensa contra colisión
42
+ // —los subs de integración llevan prefijo `INT#` y no chocan con un UUID de Cognito— sino
43
+ // para que el canal quede explícito y un cambio futuro de formato no cause aliasing
44
+ // silencioso entre un usuario y una integración.
45
+ //
46
+ // Se lee con cast en vez de tiparlo desde `ResolveInput` A PROPÓSITO: `principalType` recién
47
+ // existe en `@fiado/gateway-adapter` 4.x, y esta lib la consume toda la flota. Atarla al
48
+ // prerelease obligaría a todos a subir de major. Así compila igual contra 3.x y 4.x, y el
49
+ // reenvío al endpoint privado funciona en runtime porque `resolvePermissions(input)` manda
50
+ // el objeto entero sin filtrar campos.
51
+ const principalType = input.principalType ?? "USER";
52
+ const key = `${principalType}#${input.issuer ?? ""}#${input.cognitoSub}`;
42
53
  if (cacheable) {
43
54
  const hit = this._cache.get(key);
44
55
  if (hit)
@@ -0,0 +1,12 @@
1
+ import { IHttpRequest } from "@fiado/http-client";
2
+ import { IPresenceWebsocketApi } from "./interfaces/IPresenceWebsocketApi.js";
3
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
4
+ export declare class PresenceWebsocketApi implements IPresenceWebsocketApi {
5
+ private httpRequest;
6
+ private readonly baseUrl;
7
+ constructor(httpRequest: IHttpRequest);
8
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
9
+ deletedCount: number;
10
+ }>>;
11
+ }
12
+ export {};
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ import { inject, injectable } from "inversify";
14
+ import dotenv from 'dotenv';
15
+ dotenv.config();
16
+ let PresenceWebsocketApi = class PresenceWebsocketApi {
17
+ httpRequest;
18
+ baseUrl = process.env.PRESENCE_WEBSOCKET_LAMBDA_URL || "";
19
+ constructor(httpRequest) {
20
+ this.httpRequest = httpRequest;
21
+ }
22
+ async deleteByDirectoryId(directoryId) {
23
+ const url = `${this.baseUrl}private/directory/${directoryId}`;
24
+ return await this.httpRequest.delete(url);
25
+ }
26
+ };
27
+ PresenceWebsocketApi = __decorate([
28
+ injectable(),
29
+ __param(0, inject("IHttpRequest")),
30
+ __metadata("design:paramtypes", [Object])
31
+ ], PresenceWebsocketApi);
32
+ export { PresenceWebsocketApi };
@@ -0,0 +1,2 @@
1
+ export * from './interfaces/IPresenceWebsocketApi.js';
2
+ export * from './PresenceWebsocketApi.js';
@@ -0,0 +1,2 @@
1
+ export * from './interfaces/IPresenceWebsocketApi.js';
2
+ export * from './PresenceWebsocketApi.js';
@@ -0,0 +1,15 @@
1
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
2
+ /**
3
+ * Interfaz para el servicio Presence Websocket (presence-websocket).
4
+ */
5
+ export interface IPresenceWebsocketApi {
6
+ /**
7
+ * Borra en cascada los registros de presencia asociados a un directoryId.
8
+ * @param directoryId ID del directorio.
9
+ * @throws {Error} Lanza un error si los parámetros de entrada son inválidos.
10
+ */
11
+ deleteByDirectoryId(directoryId: string): Promise<FiadoApiResponse<{
12
+ deletedCount: number;
13
+ }>>;
14
+ }
15
+ export {};
@@ -1,9 +1,9 @@
1
1
  import type { IHttpRequest } from "@fiado/http-client";
2
2
  import { StandardResponse } from "@fiado/gateway-adapter";
3
3
  import { RetailerValidationDto, StoreValidationDto, RetailUserValidationDto, CollectorValidationDto, PrivateSellerListResponse, RetailUserStatusEnum } from "@fiado/type-kit/bin/retailOrg/index.js";
4
- import { IRetailOrgBusinessApi } from "./interfaces/IRetailOrgBusinessApi.js";
4
+ import { IRetailOrgBusinessApi, StoreRosterStatusFilter } from "./interfaces/IRetailOrgBusinessApi.js";
5
5
  /**
6
- * Publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1) para sus 6
6
+ * Publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1) para sus 8
7
7
  * endpoints privados de validación. Contrato y semántica completos → `IRetailOrgBusinessApi`.
8
8
  *
9
9
  * Los paths y el `?tenantId=` fueron verificados contra dev (200 + negativos 400/404) el
@@ -23,6 +23,13 @@ export default class RetailOrgBusinessApi implements IRetailOrgBusinessApi {
23
23
  getRetailer(retailerId: string, tenantId: string): Promise<StandardResponse<RetailerValidationDto>>;
24
24
  getRetailerByStore(storeId: string, tenantId: string): Promise<StandardResponse<RetailerValidationDto>>;
25
25
  getStore(retailerId: string, storeId: string, tenantId: string): Promise<StandardResponse<StoreValidationDto>>;
26
+ /**
27
+ * SureKeep F3 — el padrón de tiendas que le faltaba al tablero de cohorte de «Análisis de
28
+ * ventas»: sin él, la tienda que lleva 60 días sin vender no existe para el tablero. Contrato y
29
+ * semántica completos (incluido por qué acá `status` SÍ tiene default y en `listSellers` no)
30
+ * → `IRetailOrgBusinessApi.listStoresByRetailer`.
31
+ */
32
+ listStoresByRetailer(retailerId: string, tenantId: string, status?: StoreRosterStatusFilter): Promise<StandardResponse<StoreValidationDto[]>>;
26
33
  getStoreUsers(retailerId: string, storeId: string, tenantId: string): Promise<StandardResponse<RetailUserValidationDto[]>>;
27
34
  getUserByCognitoSub(cognitoSub: string, tenantId: string): Promise<StandardResponse<RetailUserValidationDto>>;
28
35
  listActiveCollectorsByRetailer(retailerId: string, tenantId: string): Promise<StandardResponse<CollectorValidationDto[]>>;
@@ -12,7 +12,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  };
13
13
  import { inject, injectable } from "inversify";
14
14
  /**
15
- * Publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1) para sus 6
15
+ * Publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1) para sus 8
16
16
  * endpoints privados de validación. Contrato y semántica completos → `IRetailOrgBusinessApi`.
17
17
  *
18
18
  * Los paths y el `?tenantId=` fueron verificados contra dev (200 + negativos 400/404) el
@@ -43,6 +43,21 @@ let RetailOrgBusinessApi = class RetailOrgBusinessApi {
43
43
  const url = `${this.baseUrl}/private/retailers/${encodeURIComponent(retailerId)}/stores/${encodeURIComponent(storeId)}?tenantId=${encodeURIComponent(tenantId)}`;
44
44
  return await this.httpRequest.get(url);
45
45
  }
46
+ /**
47
+ * SureKeep F3 — el padrón de tiendas que le faltaba al tablero de cohorte de «Análisis de
48
+ * ventas»: sin él, la tienda que lleva 60 días sin vender no existe para el tablero. Contrato y
49
+ * semántica completos (incluido por qué acá `status` SÍ tiene default y en `listSellers` no)
50
+ * → `IRetailOrgBusinessApi.listStoresByRetailer`.
51
+ */
52
+ async listStoresByRetailer(retailerId, tenantId, status) {
53
+ // `URLSearchParams` en vez de concatenar: `status` es opcional y armar el `&status=` a mano
54
+ // deja un `&status=undefined` en cuanto alguien se distrae.
55
+ const query = new URLSearchParams({ tenantId });
56
+ if (status)
57
+ query.set("status", status);
58
+ const path = `/private/retailers/${encodeURIComponent(retailerId)}/stores`;
59
+ return await this.httpRequest.get(`${this.baseUrl}${path}?${query.toString()}`);
60
+ }
46
61
  async getStoreUsers(retailerId, storeId, tenantId) {
47
62
  const url = `${this.baseUrl}/private/retailers/${encodeURIComponent(retailerId)}/users/by-store/${encodeURIComponent(storeId)}?tenantId=${encodeURIComponent(tenantId)}`;
48
63
  return await this.httpRequest.get(url);
@@ -1,8 +1,14 @@
1
1
  import { StandardResponse } from "@fiado/gateway-adapter";
2
- import { RetailerValidationDto, StoreValidationDto, RetailUserValidationDto, CollectorValidationDto, PrivateSellerListResponse, RetailUserStatusEnum } from "@fiado/type-kit/bin/retailOrg/index.js";
2
+ import { RetailerValidationDto, StoreValidationDto, RetailUserValidationDto, CollectorValidationDto, PrivateSellerListResponse, RetailUserStatusEnum, StoreStatusEnum } from "@fiado/type-kit/bin/retailOrg/index.js";
3
+ /**
4
+ * Filtro de `listStoresByRetailer`. `"ALL"` no es un estado de tienda: es la ausencia de filtro, y
5
+ * va en el MISMO parámetro que los estados porque los dos hablan del mismo eje — un segundo flag
6
+ * tipo `includeInactive` admitiría combinaciones sin sentido que alguien tendría que resolver.
7
+ */
8
+ export type StoreRosterStatusFilter = StoreStatusEnum | "ALL";
3
9
  /**
4
10
  * Contrato del publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1)
5
- * para sus 6 endpoints privados de VALIDACIÓN (service-to-service, VPC-only).
11
+ * para sus 8 endpoints privados de VALIDACIÓN (service-to-service, VPC-only).
6
12
  *
7
13
  * Consumidores previstos (regla R5 — cruces entre lambdas son SOLO lecturas de validación;
8
14
  * ningún lambda escribe en tabla ajena):
@@ -12,7 +18,7 @@ import { RetailerValidationDto, StoreValidationDto, RetailUserValidationDto, Col
12
18
  * - `loan-collector-assignment-business` → lista cobradores ACTIVE del retailer para asignar cartera
13
19
  *
14
20
  * ─────────────────────────────────────────────────────────────────────────────
15
- * `tenantId` es OBLIGATORIO en los 4 métodos — no es un detalle de firma.
21
+ * `tenantId` es OBLIGATORIO en TODOS los métodos — no es un detalle de firma.
16
22
  * ─────────────────────────────────────────────────────────────────────────────
17
23
  * Los privados NO llevan token de usuario (`Feature.ANONIMUS`; la protección es la red VPC),
18
24
  * así que el lambda destino no tiene `AuthContext.issuer` y NO puede inferir el silo. El caller
@@ -75,6 +81,42 @@ export interface IRetailOrgBusinessApi {
75
81
  * @throws si la tienda no existe o es de OTRO retailer (`404 STORE_NOT_FOUND` — verificado en dev).
76
82
  */
77
83
  getStore(retailerId: string, storeId: string, tenantId: string): Promise<StandardResponse<StoreValidationDto>>;
84
+ /**
85
+ * `GET /private/retailers/{retailerId}/stores` (SureKeep F3) — el **padrón de tiendas** de una
86
+ * cadena.
87
+ *
88
+ * ── 🔴 Por qué este método existe ────────────────────────────────────────────────────────────
89
+ * Lo consume el tablero de cohorte de «Análisis de ventas» (`retail-wizard-business`), que arma
90
+ * la lista de tiendas a partir de las VENTAS que leyó. Con eso, **la tienda que lleva 60 días sin
91
+ * vender no existe para el tablero** — y es exactamente la que hay que mirar. Este es el único
92
+ * endpoint que sirve el padrón completo: `getStore` resuelve UNA tienda y `publicListStores` va
93
+ * detrás del `W4Authorizer` (token de usuario, no invocable entre lambdas).
94
+ *
95
+ * NO se deriva de `listSellers`: ese endpoint escribe en `SharedPiiAccessLog_GT` y esa bitácora
96
+ * es la única traza de una exportación masiva de datos de empleados. Pedirle ids de tienda la
97
+ * llenaría de filas con una intención falsa.
98
+ *
99
+ * Devuelve el mismo `StoreValidationDto` que `getStore` — `storeId`, `retailerId`, `zoneId`,
100
+ * `zoneName`, `code`, `name`, `status` — con el `status` EFECTIVO (cascada del retailer padre ya
101
+ * aplicada). El `zoneName` sale de la tabla de zonas del propio retail-org: el consumidor no
102
+ * puede resolver un `zoneId` de otro dominio, y hoy lo saca de un snapshot congelado en la venta,
103
+ * así que una tienda re-zonificada muestra la zona vieja.
104
+ *
105
+ * ── ⚠️ `status` SÍ tiene default, al revés que `listSellers` ─────────────────────────────────
106
+ * Omitirlo devuelve **solo las `ACTIVE`**. La asimetría es deliberada: allá un vendedor dado de
107
+ * baja el día 20 sí tiene comisión por lo que vendió hasta el 19, así que filtrar lo perdería.
108
+ * Acá el consumidor cruza este padrón con ventas que YA leyó, o sea que la tienda cerrada que
109
+ * vendió dentro de la ventana entra igual por el lado de las ventas — filtrar no puede perder
110
+ * una tienda que vendió, solo evita agregar un cero permanente a un tablero cuyo trabajo es que
111
+ * las filas en cero llamen la atención. Pasa `"ALL"` para el padrón completo.
112
+ *
113
+ * @throws `404 RETAILER_NOT_FOUND` si el retailer no existe en ese silo. Un `[]` no distinguiría
114
+ * "esta cadena no tiene tiendas" de "preguntaste por una cadena que no existe", y lo segundo
115
+ * dejaría el tablero mudo sin que nadie sospeche del request.
116
+ * @throws `400 STORE_STATUS_FILTER_INVALID` si `status` no es del catálogo · `400 UNKNOWN_TENANT`
117
+ * sin `tenantId`.
118
+ */
119
+ listStoresByRetailer(retailerId: string, tenantId: string, status?: StoreRosterStatusFilter): Promise<StandardResponse<StoreValidationDto[]>>;
78
120
  /**
79
121
  * GET /private/retailers/{retailerId}/users/by-store/{storeId} — usuarios retail cuya tienda base
80
122
  * (`homeStoreId`) es esa. Devuelve `[]` (no 404) si la tienda no tiene usuarios asignados.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/api-invoker",
3
- "version": "5.23.0",
3
+ "version": "5.24.0",
4
4
  "description": "Sirve como un puente entre diferentes funciones lambda, facilitando la comunicación entre ellas a través de invocaciones http",
5
5
  "type": "module",
6
6
  "main": "bin/index.js",
@@ -31,7 +31,18 @@ export class ApiInvokerPermissionResolver {
31
31
  }
32
32
  async resolve(input: ResolveInput): Promise<ResolvedPermissions> {
33
33
  const cacheable = input.roleAssignmentsFromToken === undefined;
34
- const key = `${input.issuer ?? ""}#${input.cognitoSub}`;
34
+ // DEC-RBAC-124: el tipo de principal entra en la cache key. NO es defensa contra colisión
35
+ // —los subs de integración llevan prefijo `INT#` y no chocan con un UUID de Cognito— sino
36
+ // para que el canal quede explícito y un cambio futuro de formato no cause aliasing
37
+ // silencioso entre un usuario y una integración.
38
+ //
39
+ // Se lee con cast en vez de tiparlo desde `ResolveInput` A PROPÓSITO: `principalType` recién
40
+ // existe en `@fiado/gateway-adapter` 4.x, y esta lib la consume toda la flota. Atarla al
41
+ // prerelease obligaría a todos a subir de major. Así compila igual contra 3.x y 4.x, y el
42
+ // reenvío al endpoint privado funciona en runtime porque `resolvePermissions(input)` manda
43
+ // el objeto entero sin filtrar campos.
44
+ const principalType = (input as { principalType?: string }).principalType ?? "USER";
45
+ const key = `${principalType}#${input.issuer ?? ""}#${input.cognitoSub}`;
35
46
  if (cacheable) {
36
47
  const hit = this._cache.get(key);
37
48
  if (hit) return hit;
@@ -9,10 +9,13 @@ import {
9
9
  PrivateSellerListResponse,
10
10
  RetailUserStatusEnum,
11
11
  } from "@fiado/type-kit/bin/retailOrg/index.js";
12
- import { IRetailOrgBusinessApi } from "./interfaces/IRetailOrgBusinessApi.js";
12
+ import {
13
+ IRetailOrgBusinessApi,
14
+ StoreRosterStatusFilter,
15
+ } from "./interfaces/IRetailOrgBusinessApi.js";
13
16
 
14
17
  /**
15
- * Publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1) para sus 6
18
+ * Publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1) para sus 8
16
19
  * endpoints privados de validación. Contrato y semántica completos → `IRetailOrgBusinessApi`.
17
20
  *
18
21
  * Los paths y el `?tenantId=` fueron verificados contra dev (200 + negativos 400/404) el
@@ -56,6 +59,25 @@ export default class RetailOrgBusinessApi implements IRetailOrgBusinessApi {
56
59
  return await this.httpRequest.get(url);
57
60
  }
58
61
 
62
+ /**
63
+ * SureKeep F3 — el padrón de tiendas que le faltaba al tablero de cohorte de «Análisis de
64
+ * ventas»: sin él, la tienda que lleva 60 días sin vender no existe para el tablero. Contrato y
65
+ * semántica completos (incluido por qué acá `status` SÍ tiene default y en `listSellers` no)
66
+ * → `IRetailOrgBusinessApi.listStoresByRetailer`.
67
+ */
68
+ async listStoresByRetailer(
69
+ retailerId: string,
70
+ tenantId: string,
71
+ status?: StoreRosterStatusFilter,
72
+ ): Promise<StandardResponse<StoreValidationDto[]>> {
73
+ // `URLSearchParams` en vez de concatenar: `status` es opcional y armar el `&status=` a mano
74
+ // deja un `&status=undefined` en cuanto alguien se distrae.
75
+ const query = new URLSearchParams({ tenantId });
76
+ if (status) query.set("status", status);
77
+ const path = `/private/retailers/${encodeURIComponent(retailerId)}/stores`;
78
+ return await this.httpRequest.get(`${this.baseUrl}${path}?${query.toString()}`);
79
+ }
80
+
59
81
  async getStoreUsers(
60
82
  retailerId: string,
61
83
  storeId: string,
@@ -6,11 +6,19 @@ import {
6
6
  CollectorValidationDto,
7
7
  PrivateSellerListResponse,
8
8
  RetailUserStatusEnum,
9
+ StoreStatusEnum,
9
10
  } from "@fiado/type-kit/bin/retailOrg/index.js";
10
11
 
12
+ /**
13
+ * Filtro de `listStoresByRetailer`. `"ALL"` no es un estado de tienda: es la ausencia de filtro, y
14
+ * va en el MISMO parámetro que los estados porque los dos hablan del mismo eje — un segundo flag
15
+ * tipo `includeInactive` admitiría combinaciones sin sentido que alguien tendría que resolver.
16
+ */
17
+ export type StoreRosterStatusFilter = StoreStatusEnum | "ALL";
18
+
11
19
  /**
12
20
  * Contrato del publisher HTTP del lambda `retail-org-business` (componente 04 SureKeep Fase 1)
13
- * para sus 6 endpoints privados de VALIDACIÓN (service-to-service, VPC-only).
21
+ * para sus 8 endpoints privados de VALIDACIÓN (service-to-service, VPC-only).
14
22
  *
15
23
  * Consumidores previstos (regla R5 — cruces entre lambdas son SOLO lecturas de validación;
16
24
  * ningún lambda escribe en tabla ajena):
@@ -20,7 +28,7 @@ import {
20
28
  * - `loan-collector-assignment-business` → lista cobradores ACTIVE del retailer para asignar cartera
21
29
  *
22
30
  * ─────────────────────────────────────────────────────────────────────────────
23
- * `tenantId` es OBLIGATORIO en los 4 métodos — no es un detalle de firma.
31
+ * `tenantId` es OBLIGATORIO en TODOS los métodos — no es un detalle de firma.
24
32
  * ─────────────────────────────────────────────────────────────────────────────
25
33
  * Los privados NO llevan token de usuario (`Feature.ANONIMUS`; la protección es la red VPC),
26
34
  * así que el lambda destino no tiene `AuthContext.issuer` y NO puede inferir el silo. El caller
@@ -96,6 +104,47 @@ export interface IRetailOrgBusinessApi {
96
104
  tenantId: string,
97
105
  ): Promise<StandardResponse<StoreValidationDto>>;
98
106
 
107
+ /**
108
+ * `GET /private/retailers/{retailerId}/stores` (SureKeep F3) — el **padrón de tiendas** de una
109
+ * cadena.
110
+ *
111
+ * ── 🔴 Por qué este método existe ────────────────────────────────────────────────────────────
112
+ * Lo consume el tablero de cohorte de «Análisis de ventas» (`retail-wizard-business`), que arma
113
+ * la lista de tiendas a partir de las VENTAS que leyó. Con eso, **la tienda que lleva 60 días sin
114
+ * vender no existe para el tablero** — y es exactamente la que hay que mirar. Este es el único
115
+ * endpoint que sirve el padrón completo: `getStore` resuelve UNA tienda y `publicListStores` va
116
+ * detrás del `W4Authorizer` (token de usuario, no invocable entre lambdas).
117
+ *
118
+ * NO se deriva de `listSellers`: ese endpoint escribe en `SharedPiiAccessLog_GT` y esa bitácora
119
+ * es la única traza de una exportación masiva de datos de empleados. Pedirle ids de tienda la
120
+ * llenaría de filas con una intención falsa.
121
+ *
122
+ * Devuelve el mismo `StoreValidationDto` que `getStore` — `storeId`, `retailerId`, `zoneId`,
123
+ * `zoneName`, `code`, `name`, `status` — con el `status` EFECTIVO (cascada del retailer padre ya
124
+ * aplicada). El `zoneName` sale de la tabla de zonas del propio retail-org: el consumidor no
125
+ * puede resolver un `zoneId` de otro dominio, y hoy lo saca de un snapshot congelado en la venta,
126
+ * así que una tienda re-zonificada muestra la zona vieja.
127
+ *
128
+ * ── ⚠️ `status` SÍ tiene default, al revés que `listSellers` ─────────────────────────────────
129
+ * Omitirlo devuelve **solo las `ACTIVE`**. La asimetría es deliberada: allá un vendedor dado de
130
+ * baja el día 20 sí tiene comisión por lo que vendió hasta el 19, así que filtrar lo perdería.
131
+ * Acá el consumidor cruza este padrón con ventas que YA leyó, o sea que la tienda cerrada que
132
+ * vendió dentro de la ventana entra igual por el lado de las ventas — filtrar no puede perder
133
+ * una tienda que vendió, solo evita agregar un cero permanente a un tablero cuyo trabajo es que
134
+ * las filas en cero llamen la atención. Pasa `"ALL"` para el padrón completo.
135
+ *
136
+ * @throws `404 RETAILER_NOT_FOUND` si el retailer no existe en ese silo. Un `[]` no distinguiría
137
+ * "esta cadena no tiene tiendas" de "preguntaste por una cadena que no existe", y lo segundo
138
+ * dejaría el tablero mudo sin que nadie sospeche del request.
139
+ * @throws `400 STORE_STATUS_FILTER_INVALID` si `status` no es del catálogo · `400 UNKNOWN_TENANT`
140
+ * sin `tenantId`.
141
+ */
142
+ listStoresByRetailer(
143
+ retailerId: string,
144
+ tenantId: string,
145
+ status?: StoreRosterStatusFilter,
146
+ ): Promise<StandardResponse<StoreValidationDto[]>>;
147
+
99
148
  /**
100
149
  * GET /private/retailers/{retailerId}/users/by-store/{storeId} — usuarios retail cuya tienda base
101
150
  * (`homeStoreId`) es esa. Devuelve `[]` (no 404) si la tienda no tiene usuarios asignados.