@fiado/api-invoker 5.115.0 → 5.117.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 (48) hide show
  1. package/bin/container.config.js +14 -0
  2. package/bin/datalake-reports-business/api/DatalakeReportsApi.d.ts +13 -0
  3. package/bin/datalake-reports-business/api/DatalakeReportsApi.js +39 -0
  4. package/bin/datalake-reports-business/api/interfaces/IDatalakeReportsApi.d.ts +58 -0
  5. package/bin/datalake-reports-business/api/interfaces/IDatalakeReportsApi.js +1 -0
  6. package/bin/datalake-reports-business/index.d.ts +2 -0
  7. package/bin/datalake-reports-business/index.js +2 -0
  8. package/bin/index.d.ts +3 -0
  9. package/bin/index.js +3 -0
  10. package/bin/rewards-business/api/RewardsBusinessApi.d.ts +12 -0
  11. package/bin/rewards-business/api/RewardsBusinessApi.js +38 -0
  12. package/bin/rewards-business/api/interfaces/IRewardsBusinessApi.d.ts +33 -0
  13. package/bin/rewards-business/api/interfaces/IRewardsBusinessApi.js +1 -0
  14. package/bin/rewards-business/grantIdFor.d.ts +17 -0
  15. package/bin/rewards-business/grantIdFor.js +30 -0
  16. package/bin/rewards-business/index.d.ts +5 -0
  17. package/bin/rewards-business/index.js +5 -0
  18. package/bin/rewards-business/queue/RewardsGrantPublisher.d.ts +7 -0
  19. package/bin/rewards-business/queue/RewardsGrantPublisher.js +83 -0
  20. package/bin/rewards-business/queue/interfaces/IRewardsGrantPublisher.d.ts +32 -0
  21. package/bin/rewards-business/queue/interfaces/IRewardsGrantPublisher.js +1 -0
  22. package/bin/rewards-eligibility-business/api/RewardsEligibilityBusinessApi.d.ts +12 -0
  23. package/bin/rewards-eligibility-business/api/RewardsEligibilityBusinessApi.js +37 -0
  24. package/bin/rewards-eligibility-business/api/interfaces/IRewardsEligibilityBusinessApi.d.ts +34 -0
  25. package/bin/rewards-eligibility-business/api/interfaces/IRewardsEligibilityBusinessApi.js +1 -0
  26. package/bin/rewards-eligibility-business/index.d.ts +4 -0
  27. package/bin/rewards-eligibility-business/index.js +4 -0
  28. package/bin/rewards-eligibility-business/queue/RewardsEligibilityPublisher.d.ts +6 -0
  29. package/bin/rewards-eligibility-business/queue/RewardsEligibilityPublisher.js +34 -0
  30. package/bin/rewards-eligibility-business/queue/interfaces/IRewardsEligibilityPublisher.d.ts +15 -0
  31. package/bin/rewards-eligibility-business/queue/interfaces/IRewardsEligibilityPublisher.js +1 -0
  32. package/package.json +2 -2
  33. package/src/container.config.ts +17 -0
  34. package/src/datalake-reports-business/api/DatalakeReportsApi.ts +30 -0
  35. package/src/datalake-reports-business/api/interfaces/IDatalakeReportsApi.ts +27 -0
  36. package/src/datalake-reports-business/index.ts +2 -0
  37. package/src/index.ts +3 -0
  38. package/src/rewards-business/api/RewardsBusinessApi.ts +27 -0
  39. package/src/rewards-business/api/interfaces/IRewardsBusinessApi.ts +35 -0
  40. package/src/rewards-business/grantIdFor.ts +36 -0
  41. package/src/rewards-business/index.ts +5 -0
  42. package/src/rewards-business/queue/RewardsGrantPublisher.ts +80 -0
  43. package/src/rewards-business/queue/interfaces/IRewardsGrantPublisher.ts +35 -0
  44. package/src/rewards-eligibility-business/api/RewardsEligibilityBusinessApi.ts +26 -0
  45. package/src/rewards-eligibility-business/api/interfaces/IRewardsEligibilityBusinessApi.ts +36 -0
  46. package/src/rewards-eligibility-business/index.ts +4 -0
  47. package/src/rewards-eligibility-business/queue/RewardsEligibilityPublisher.ts +28 -0
  48. package/src/rewards-eligibility-business/queue/interfaces/IRewardsEligibilityPublisher.ts +16 -0
@@ -74,6 +74,7 @@ import { EventHistoryApi } from "./event-history-business/index.js";
74
74
  import ReportProcessorBusinessApi from "./report-processor-business/api/ReportProcessorBusinessApi.js";
75
75
  import NotificationWSMessagePublisher from "./notificationWebsockets/queue/NotificationWSMessagePublisher.js";
76
76
  import PeopleBusinessApi from "./people-business/api/PeopleBusinessApi.js";
77
+ import DatalakeReportsApi from "./datalake-reports-business/api/DatalakeReportsApi.js";
77
78
  import PlacesBusinessApi from "./places-business/api/PlacesBusinessApi.js";
78
79
  import STPBusinessApi from "./stp-business/api/STPBusinessApi.js";
79
80
  import MulticommServicePaymentApi from "./multicommServicePayment/api/MulticommServicePaymentApi.js";
@@ -139,6 +140,10 @@ import { KycVerificationsBusinessApi } from "./kyc/index.js";
139
140
  // Webhook business — publisher de la cola compartida de entrega de eventos a suscriptores externos
140
141
  import { WebhookBusinessPublisher } from "./webhook-business/index.js";
141
142
  import { CentralPaymentsWebhookApi } from "./central-payments-webhook/index.js";
143
+ // Rewards — publisher de pedidos de entrega + cliente de lecturas privadas (rewards-business) y
144
+ // publisher de GRANT_FAILED + cliente del validador (rewards-eligibility-business)
145
+ import { RewardsGrantPublisher, RewardsBusinessApi } from "./rewards-business/index.js";
146
+ import { RewardsEligibilityPublisher, RewardsEligibilityBusinessApi } from "./rewards-eligibility-business/index.js";
142
147
  import CollectionEngineApi from "./collection-engine/api/CollectionEngineApi.js";
143
148
  import DomainCallbackApi from "./domain-callback/api/DomainCallbackApi.js";
144
149
  export const apiInvokerBindings = new ContainerModule(({ bind }) => {
@@ -221,6 +226,7 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
221
226
  bind("IEventHistoryApi").to(EventHistoryApi);
222
227
  bind("IReportProcessorBusinessApi").to(ReportProcessorBusinessApi);
223
228
  bind("IPeopleBusinessApi").to(PeopleBusinessApi);
229
+ bind("IDatalakeReportsApi").to(DatalakeReportsApi);
224
230
  bind("IPlacesBusinessApi").to(PlacesBusinessApi);
225
231
  bind("ISTPBusinessApi").to(STPBusinessApi);
226
232
  bind("IProductPaymentApi").to(ProductPaymentApi);
@@ -298,6 +304,14 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
298
304
  // El productor necesita WEBHOOK_BUSINESS_QUEUE_URL y sqs:SendMessage sobre la cola.
299
305
  bind("IWebhookBusinessPublisher").to(WebhookBusinessPublisher);
300
306
  bind("ICentralPaymentsWebhookApi").to(CentralPaymentsWebhookApi);
307
+ // Rewards — cola compartida RewardsGrantQueue (pedidos de entrega) y lecturas privadas de rewards-business.
308
+ // El productor necesita REWARDS_GRANT_QUEUE_URL y sqs:SendMessage sobre la cola.
309
+ bind("IRewardsGrantPublisher").to(RewardsGrantPublisher);
310
+ bind("IRewardsBusinessApi").to(RewardsBusinessApi);
311
+ // Rewards eligibility — GRANT_FAILED hacia la cola del validador y cliente de POST /events.
312
+ // El productor necesita REWARDS_ELIGIBILITY_QUEUE_URL y sqs:SendMessage sobre la cola.
313
+ bind("IRewardsEligibilityPublisher").to(RewardsEligibilityPublisher);
314
+ bind("IRewardsEligibilityBusinessApi").to(RewardsEligibilityBusinessApi);
301
315
  // Collection engine — motor de cobro central multitenant (collect/deregister)
302
316
  bind("ICollectionEngineApi").to(CollectionEngineApi);
303
317
  // Domain callback — el motor notifica al dominio dueño del cargo (applyCollectionResult)
@@ -0,0 +1,13 @@
1
+ import type { IHttpRequest } from "@fiado/http-client";
2
+ import { DatalakeReportDefinition, DatalakeReportResult, IDatalakeReportsApi } from "./interfaces/IDatalakeReportsApi.js";
3
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
4
+ export default class DatalakeReportsApi implements IDatalakeReportsApi {
5
+ private httpRequest;
6
+ private readonly baseUrl;
7
+ constructor(httpRequest: IHttpRequest);
8
+ listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>>;
9
+ getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>>;
10
+ runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>>;
11
+ getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>>;
12
+ }
13
+ export {};
@@ -0,0 +1,39 @@
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
+ let DatalakeReportsApi = class DatalakeReportsApi {
15
+ httpRequest;
16
+ baseUrl = process.env.DATALAKE_REPORTS_BUSINESS_LAMBDA_URL || "";
17
+ constructor(httpRequest) {
18
+ this.httpRequest = httpRequest;
19
+ }
20
+ listReports() {
21
+ return this.httpRequest.get(`${this.baseUrl}private/reports`);
22
+ }
23
+ getReport(id) {
24
+ return this.httpRequest.get(`${this.baseUrl}private/reports/${encodeURIComponent(id)}`);
25
+ }
26
+ runReport(id, params) {
27
+ return this.httpRequest.post(`${this.baseUrl}private/reports/${encodeURIComponent(id)}/run`, { params });
28
+ }
29
+ getJob(jobId, nextToken) {
30
+ const qs = nextToken ? `?nextToken=${encodeURIComponent(nextToken)}` : "";
31
+ return this.httpRequest.get(`${this.baseUrl}private/reports/jobs/${encodeURIComponent(jobId)}${qs}`);
32
+ }
33
+ };
34
+ DatalakeReportsApi = __decorate([
35
+ injectable(),
36
+ __param(0, inject("IHttpRequest")),
37
+ __metadata("design:paramtypes", [Object])
38
+ ], DatalakeReportsApi);
39
+ export default DatalakeReportsApi;
@@ -0,0 +1,58 @@
1
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
2
+ /** Contrato local hasta que @fiado/type-kit publique el dominio DatalakeReports (misma forma). */
3
+ export interface DatalakeReportDefinition {
4
+ id: string;
5
+ version: number;
6
+ title: string;
7
+ description: string;
8
+ source: {
9
+ layer: "gold" | "silver";
10
+ databases: string[];
11
+ };
12
+ pii: boolean;
13
+ audiences: string[];
14
+ permission: string;
15
+ mode: "sync" | "async";
16
+ cacheSeconds: number;
17
+ params: Record<string, {
18
+ type: string;
19
+ required?: boolean;
20
+ default?: unknown;
21
+ values?: string[];
22
+ max?: number;
23
+ min?: number;
24
+ }>;
25
+ principalParams: string[];
26
+ columns: Array<{
27
+ name: string;
28
+ type: string;
29
+ label: string;
30
+ }>;
31
+ }
32
+ export interface DatalakeReportResult {
33
+ status: "RUNNING" | "DONE" | "FAILED";
34
+ reportId?: string;
35
+ version?: number;
36
+ jobId: string;
37
+ columns?: Array<{
38
+ name: string;
39
+ type: string;
40
+ label: string;
41
+ }>;
42
+ rows?: Array<Record<string, string | number | boolean | null>>;
43
+ rowCount?: number;
44
+ nextToken?: string;
45
+ computedAt?: string;
46
+ fromCache?: boolean;
47
+ bytesScanned?: number;
48
+ reason?: string;
49
+ }
50
+ /** Canal privado de datalake-reports-business (reportes predefinidos sobre Gold y Silver). */
51
+ export interface IDatalakeReportsApi {
52
+ listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>>;
53
+ getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>>;
54
+ /** 200 con filas, o 200 con status RUNNING y jobId si la corrida cae a asíncrono. */
55
+ runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>>;
56
+ getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>>;
57
+ }
58
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from './api/DatalakeReportsApi.js';
2
+ export * from './api/interfaces/IDatalakeReportsApi.js';
@@ -0,0 +1,2 @@
1
+ export * from './api/DatalakeReportsApi.js';
2
+ export * from './api/interfaces/IDatalakeReportsApi.js';
package/bin/index.d.ts CHANGED
@@ -65,6 +65,7 @@ export * from "./event-history-business/index.js";
65
65
  export * from "./report-processor-business/index.js";
66
66
  export * from "./notificationWebsockets/index.js";
67
67
  export * from "./people-business/index.js";
68
+ export * from "./datalake-reports-business/index.js";
68
69
  export * from "./places-business/index.js";
69
70
  export * from "./stp-business/index.js";
70
71
  export * from "./multicommServicePayment/index.js";
@@ -122,3 +123,5 @@ export * from "./domain-callback/index.js";
122
123
  export * from "./datacultrConnector/index.js";
123
124
  export * from "./webhook-business/index.js";
124
125
  export * from "./trustonicConnector/index.js";
126
+ export * from "./rewards-business/index.js";
127
+ export * from "./rewards-eligibility-business/index.js";
package/bin/index.js CHANGED
@@ -65,6 +65,7 @@ export * from "./event-history-business/index.js";
65
65
  export * from "./report-processor-business/index.js";
66
66
  export * from "./notificationWebsockets/index.js";
67
67
  export * from "./people-business/index.js";
68
+ export * from "./datalake-reports-business/index.js";
68
69
  export * from "./places-business/index.js";
69
70
  export * from "./stp-business/index.js";
70
71
  export * from "./multicommServicePayment/index.js";
@@ -122,3 +123,5 @@ export * from "./domain-callback/index.js";
122
123
  export * from "./datacultrConnector/index.js";
123
124
  export * from "./webhook-business/index.js";
124
125
  export * from "./trustonicConnector/index.js";
126
+ export * from "./rewards-business/index.js";
127
+ export * from "./rewards-eligibility-business/index.js";
@@ -0,0 +1,12 @@
1
+ import type { StandardResponse } from "@fiado/gateway-adapter";
2
+ import type { IHttpRequest } from "@fiado/http-client";
3
+ import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
4
+ import type { IRewardsBusinessApi } from "./interfaces/IRewardsBusinessApi.js";
5
+ /** Contrato y semántica completos → `IRewardsBusinessApi` (`StandardResponse` → leer `result.data`). */
6
+ export declare class RewardsBusinessApi implements IRewardsBusinessApi {
7
+ private httpRequest;
8
+ private readonly baseUrl;
9
+ constructor(httpRequest: IHttpRequest);
10
+ getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>>;
11
+ getGrant(grantId: string): Promise<StandardResponse<GrantResponse>>;
12
+ }
@@ -0,0 +1,38 @@
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
+ /** Contrato y semántica completos → `IRewardsBusinessApi` (`StandardResponse` → leer `result.data`). */
15
+ let RewardsBusinessApi = class RewardsBusinessApi {
16
+ httpRequest;
17
+ // El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//packages".
18
+ baseUrl = (process.env.REWARDS_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
19
+ constructor(httpRequest) {
20
+ this.httpRequest = httpRequest;
21
+ }
22
+ async getPackage(packageId) {
23
+ // PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA de rewards-business).
24
+ // La ruta base exacta (con o sin `/private`) la fija el plan de rewards-business; si cambia, ajustar aquí.
25
+ const url = `${this.baseUrl}/packages/${encodeURIComponent(packageId)}`;
26
+ return await this.httpRequest.get(url);
27
+ }
28
+ async getGrant(grantId) {
29
+ const url = `${this.baseUrl}/grants/${encodeURIComponent(grantId)}`;
30
+ return await this.httpRequest.get(url);
31
+ }
32
+ };
33
+ RewardsBusinessApi = __decorate([
34
+ injectable(),
35
+ __param(0, inject("IHttpRequest")),
36
+ __metadata("design:paramtypes", [Object])
37
+ ], RewardsBusinessApi);
38
+ export { RewardsBusinessApi };
@@ -0,0 +1,33 @@
1
+ import type { StandardResponse } from "@fiado/gateway-adapter";
2
+ import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
3
+ /**
4
+ * Cliente de la API privada de `rewards-business`. SOLO LECTURA: no hay endpoint privado de
5
+ * escritura (spec 2026-10-05, D11); los pedidos de entrega se publican con `IRewardsGrantPublisher`.
6
+ *
7
+ * Devuelve `StandardResponse<T>` → leer `result.data`. NO uses `FiadoApiResponse`: los dos tipos no
8
+ * son intercambiables en TypeScript (`code`/`date` admiten `null` en uno y no en el otro).
9
+ *
10
+ * Env var requerida en el consumer: `REWARDS_BUSINESS_LAMBDA_URL`. El template.yml del consumer la
11
+ * setea con:
12
+ *
13
+ * REWARDS_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardsbusiness-lambda}}'
14
+ *
15
+ * El nombre del parámetro SSM es el `stack_name` del `samconfig.toml` de rewards-business (el
16
+ * buildspec registra el SSM con ese nombre): debe coincidir con el de arriba.
17
+ */
18
+ export interface IRewardsBusinessApi {
19
+ /**
20
+ * Contrato de entrada de un paquete: por `itemKey`, las `dependencies` de su recompensa, más
21
+ * nombre, descripción, costo y `totalCost` por moneda. El backoffice del validador lo usa para
22
+ * no dejar activar una evaluación con un paquete inactivo o con inputs que no cubren el contrato.
23
+ *
24
+ * @throws propaga el error del destino (404 `PACKAGE_NOT_FOUND`); nunca devuelve fallback.
25
+ */
26
+ getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>>;
27
+ /**
28
+ * Estado de una entrega y de cada ítem. El `grantId` se calcula con `grantIdFor`.
29
+ *
30
+ * @throws propaga el error del destino (404 `GRANT_NOT_FOUND`); nunca devuelve fallback.
31
+ */
32
+ getGrant(grantId: string): Promise<StandardResponse<GrantResponse>>;
33
+ }
@@ -0,0 +1,17 @@
1
+ /** Largo (hex) del `grantId`. 128 bits: colisión despreciable. */
2
+ export declare const REWARDS_GRANT_ID_LENGTH = 32;
3
+ /** Límite de `BenefitPaymentRequest.transactionNumber`. */
4
+ export declare const REWARDS_TRANSACTION_NUMBER_MAX_LENGTH = 64;
5
+ /**
6
+ * Largo máximo del `itemKey` de un paquete para que `RW-{grantId}-{itemKey}` quepa en 64:
7
+ * 64 − len('RW-') − len('-') − 32 = 28. rewards-business debe validarlo al guardar un paquete.
8
+ */
9
+ export declare const REWARDS_MAX_ITEM_KEY_LENGTH: number;
10
+ /**
11
+ * `grantId` determinístico de una entrega de rewards (spec A 5.3, D11). Lo calcula el llamador al
12
+ * publicar `GRANT_REQUESTED` (para conocerlo sin esperar respuesta) y el worker de rewards al
13
+ * consumirlo: tienen que dar lo mismo, por eso es UNA función compartida.
14
+ *
15
+ * El largo de `service` va como prefijo para que ('a:b','c') y ('a','b:c') no colisionen.
16
+ */
17
+ export declare function grantIdFor(service: string, idempotencyKey: string): string;
@@ -0,0 +1,30 @@
1
+ import { createHash } from 'node:crypto';
2
+ /** Largo (hex) del `grantId`. 128 bits: colisión despreciable. */
3
+ export const REWARDS_GRANT_ID_LENGTH = 32;
4
+ /** Límite de `BenefitPaymentRequest.transactionNumber`. */
5
+ export const REWARDS_TRANSACTION_NUMBER_MAX_LENGTH = 64;
6
+ const TRANSACTION_NUMBER_PREFIX = 'RW-';
7
+ /**
8
+ * Largo máximo del `itemKey` de un paquete para que `RW-{grantId}-{itemKey}` quepa en 64:
9
+ * 64 − len('RW-') − len('-') − 32 = 28. rewards-business debe validarlo al guardar un paquete.
10
+ */
11
+ export const REWARDS_MAX_ITEM_KEY_LENGTH = REWARDS_TRANSACTION_NUMBER_MAX_LENGTH - TRANSACTION_NUMBER_PREFIX.length - 1 - REWARDS_GRANT_ID_LENGTH;
12
+ /**
13
+ * `grantId` determinístico de una entrega de rewards (spec A 5.3, D11). Lo calcula el llamador al
14
+ * publicar `GRANT_REQUESTED` (para conocerlo sin esperar respuesta) y el worker de rewards al
15
+ * consumirlo: tienen que dar lo mismo, por eso es UNA función compartida.
16
+ *
17
+ * El largo de `service` va como prefijo para que ('a:b','c') y ('a','b:c') no colisionen.
18
+ */
19
+ export function grantIdFor(service, idempotencyKey) {
20
+ if (!service) {
21
+ throw new Error('grantIdFor: service es obligatorio');
22
+ }
23
+ if (!idempotencyKey) {
24
+ throw new Error('grantIdFor: idempotencyKey es obligatoria');
25
+ }
26
+ return createHash('sha256')
27
+ .update(`${service.length}:${service}:${idempotencyKey}`)
28
+ .digest('hex')
29
+ .slice(0, REWARDS_GRANT_ID_LENGTH);
30
+ }
@@ -0,0 +1,5 @@
1
+ export * from './grantIdFor.js';
2
+ export * from './queue/interfaces/IRewardsGrantPublisher.js';
3
+ export * from './queue/RewardsGrantPublisher.js';
4
+ export * from './api/interfaces/IRewardsBusinessApi.js';
5
+ export * from './api/RewardsBusinessApi.js';
@@ -0,0 +1,5 @@
1
+ export * from './grantIdFor.js';
2
+ export * from './queue/interfaces/IRewardsGrantPublisher.js';
3
+ export * from './queue/RewardsGrantPublisher.js';
4
+ export * from './api/interfaces/IRewardsBusinessApi.js';
5
+ export * from './api/RewardsBusinessApi.js';
@@ -0,0 +1,7 @@
1
+ import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
2
+ import { IRewardsGrantPublisher, RewardsGrantBatchResult } from "./interfaces/IRewardsGrantPublisher.js";
3
+ export declare class RewardsGrantPublisher implements IRewardsGrantPublisher {
4
+ private readonly REWARDS_GRANT_QUEUE_URL;
5
+ publish(message: GrantRequestedMessage): Promise<void>;
6
+ publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult>;
7
+ }
@@ -0,0 +1,83 @@
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
+ import { SendMessageBatchCommand, SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
8
+ import { injectable } from "inversify";
9
+ const SQS_BATCH_MAX_ENTRIES = 10;
10
+ // Límite SQS: 262 144 bytes por SendMessageBatch. Margen para no rozarlo.
11
+ const SQS_BATCH_MAX_BYTES = 250_000;
12
+ let RewardsGrantPublisher = class RewardsGrantPublisher {
13
+ REWARDS_GRANT_QUEUE_URL = process.env.REWARDS_GRANT_QUEUE_URL;
14
+ async publish(message) {
15
+ if (!this.REWARDS_GRANT_QUEUE_URL) {
16
+ throw new Error("Error publishing message to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
17
+ }
18
+ try {
19
+ const client = new SQSClient();
20
+ // Cola ESTÁNDAR (no FIFO): sin MessageGroupId/MessageDeduplicationId ni orden garantizado.
21
+ // La idempotencia la hace el consumidor con el `grantId` derivado de (source.service +
22
+ // idempotencyKey) y un PutItem condicional.
23
+ const sendMessageRequest = {
24
+ QueueUrl: this.REWARDS_GRANT_QUEUE_URL,
25
+ MessageBody: JSON.stringify(message),
26
+ };
27
+ await client.send(new SendMessageCommand(sendMessageRequest));
28
+ }
29
+ catch (error) {
30
+ const errorMessage = error instanceof Error ? error.message : String(error);
31
+ throw new Error(`Error publishing message to RewardsGrantQueue: ${errorMessage}`);
32
+ }
33
+ }
34
+ async publishBatch(messages) {
35
+ const failedIdempotencyKeys = [];
36
+ const senderFaultIdempotencyKeys = [];
37
+ if (messages.length === 0) {
38
+ return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
39
+ }
40
+ if (!this.REWARDS_GRANT_QUEUE_URL) {
41
+ throw new Error("Error publishing batch to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
42
+ }
43
+ try {
44
+ const client = new SQSClient();
45
+ const keyByEntryId = new Map();
46
+ let current = [];
47
+ let currentBytes = 0;
48
+ const flush = async () => {
49
+ if (current.length === 0)
50
+ return;
51
+ const result = await client.send(new SendMessageBatchCommand({ QueueUrl: this.REWARDS_GRANT_QUEUE_URL, Entries: current }));
52
+ for (const failed of result.Failed ?? []) {
53
+ const key = failed.Id ? keyByEntryId.get(failed.Id) : undefined;
54
+ if (key)
55
+ (failed.SenderFault ? senderFaultIdempotencyKeys : failedIdempotencyKeys).push(key);
56
+ }
57
+ current = [];
58
+ currentBytes = 0;
59
+ };
60
+ for (let index = 0; index < messages.length; index += 1) {
61
+ const body = JSON.stringify(messages[index]);
62
+ const bytes = Buffer.byteLength(body, "utf8");
63
+ if (current.length >= SQS_BATCH_MAX_ENTRIES || (current.length > 0 && currentBytes + bytes > SQS_BATCH_MAX_BYTES)) {
64
+ await flush();
65
+ }
66
+ const id = `m${index}`;
67
+ keyByEntryId.set(id, messages[index].idempotencyKey);
68
+ current.push({ Id: id, MessageBody: body });
69
+ currentBytes += bytes;
70
+ }
71
+ await flush();
72
+ return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
73
+ }
74
+ catch (error) {
75
+ const errorMessage = error instanceof Error ? error.message : String(error);
76
+ throw new Error(`Error publishing batch to RewardsGrantQueue: ${errorMessage}`);
77
+ }
78
+ }
79
+ };
80
+ RewardsGrantPublisher = __decorate([
81
+ injectable()
82
+ ], RewardsGrantPublisher);
83
+ export { RewardsGrantPublisher };
@@ -0,0 +1,32 @@
1
+ import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
2
+ /**
3
+ * Resultado de `publishBatch`: las `idempotencyKey` que SQS NO aceptó.
4
+ * - `failedIdempotencyKeys`: `SenderFault=false` (falla transitoria de SQS) → el llamador PUEDE reintentar.
5
+ * - `senderFaultIdempotencyKeys`: `SenderFault=true` (el mensaje es inválido para SQS) → NO reintentar:
6
+ * volver a mandarlo falla igual.
7
+ */
8
+ export interface RewardsGrantBatchResult {
9
+ failedIdempotencyKeys: string[];
10
+ senderFaultIdempotencyKeys: string[];
11
+ }
12
+ /**
13
+ * Publisher de pedidos de entrega hacia `RewardsGrantQueue`, que consume `rewards-business`
14
+ * (spec 2026-10-05, D11). La cola es COMPARTIDA entre lambdas: el estándar Fiado publica a una cola
15
+ * compartida con un publisher de `@fiado/api-invoker` (mismo patrón que `ITransactionPublisher`).
16
+ *
17
+ * Que `publish` funcione ES la aceptación del pedido: no hay respuesta síncrona. El `grantId` no
18
+ * viaja en el mensaje; el llamador lo conoce con `grantIdFor(source.service, idempotencyKey)`.
19
+ *
20
+ * Env var requerida en el consumer: `REWARDS_GRANT_QUEUE_URL`
21
+ * (`!ImportValue SqsRewardsGrantQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
22
+ */
23
+ export interface IRewardsGrantPublisher {
24
+ publish(message: GrantRequestedMessage): Promise<void>;
25
+ /**
26
+ * Publica varios pedidos con `SendMessageBatch` (tandas de 10 y de ≤ 250 000 bytes). El validador
27
+ * suelta lo masivo por tramos con esto (spec B 7.1 y 7.6). Un fallo parcial NO lanza: se devuelve
28
+ * en `failedIdempotencyKeys` (reintentables) o `senderFaultIdempotencyKeys` (NO reintentar). Si la
29
+ * llamada entera falla, lanza (reintento idempotente por `grantId`).
30
+ */
31
+ publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult>;
32
+ }
@@ -0,0 +1,12 @@
1
+ import type { StandardResponse } from "@fiado/gateway-adapter";
2
+ import type { IHttpRequest } from "@fiado/http-client";
3
+ import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
4
+ import type { IRewardsEligibilityBusinessApi } from "./interfaces/IRewardsEligibilityBusinessApi.js";
5
+ /** Contrato y semántica completos → `IRewardsEligibilityBusinessApi` (`StandardResponse` → leer `result.data`). */
6
+ export declare class RewardsEligibilityBusinessApi implements IRewardsEligibilityBusinessApi {
7
+ private httpRequest;
8
+ private readonly baseUrl;
9
+ constructor(httpRequest: IHttpRequest);
10
+ postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>>;
11
+ getRun(runId: string): Promise<StandardResponse<GetRunResponse>>;
12
+ }
@@ -0,0 +1,37 @@
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
+ /** Contrato y semántica completos → `IRewardsEligibilityBusinessApi` (`StandardResponse` → leer `result.data`). */
15
+ let RewardsEligibilityBusinessApi = class RewardsEligibilityBusinessApi {
16
+ httpRequest;
17
+ // El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//events".
18
+ baseUrl = (process.env.REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
19
+ constructor(httpRequest) {
20
+ this.httpRequest = httpRequest;
21
+ }
22
+ async postEvent(request) {
23
+ // PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA).
24
+ // La ruta base exacta (con o sin `/private`) la fija el plan del validador; si cambia, ajustar aquí.
25
+ return await this.httpRequest.post(`${this.baseUrl}/events`, request);
26
+ }
27
+ async getRun(runId) {
28
+ const url = `${this.baseUrl}/runs/${encodeURIComponent(runId)}`;
29
+ return await this.httpRequest.get(url);
30
+ }
31
+ };
32
+ RewardsEligibilityBusinessApi = __decorate([
33
+ injectable(),
34
+ __param(0, inject("IHttpRequest")),
35
+ __metadata("design:paramtypes", [Object])
36
+ ], RewardsEligibilityBusinessApi);
37
+ export { RewardsEligibilityBusinessApi };
@@ -0,0 +1,34 @@
1
+ import type { StandardResponse } from "@fiado/gateway-adapter";
2
+ import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
3
+ /**
4
+ * Cliente de la API privada de `rewards-eligibility-business` (spec 2026-10-06, §6).
5
+ *
6
+ * Devuelve `StandardResponse<T>` → leer `result.data`. Env var requerida en el consumer:
7
+ * `REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL`, seteada con
8
+ *
9
+ * REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardseligibilitybusiness-lambda}}'
10
+ *
11
+ * (el nombre del SSM es el `stack_name` del `samconfig.toml` del validador).
12
+ *
13
+ * El emisor NO decide la elegibilidad: informa qué pasó y quién participó (E3). La llamada es NO
14
+ * bloqueante para el flujo del emisor: si falla, el emisor sigue y reintenta con el mismo
15
+ * `idempotencyKey`.
16
+ *
17
+ * RUTA BASE PROVISIONAL: se fija en el plan del validador (`rewards-eligibility-business`); si cambia
18
+ * (con o sin `/private`), se ajusta este cliente y se republica.
19
+ */
20
+ export interface IRewardsEligibilityBusinessApi {
21
+ /**
22
+ * Dispara las evaluaciones suscritas al `eventKey`. Responde 202 con una corrida por evaluación
23
+ * (`runs` vacío si nadie escucha el evento).
24
+ *
25
+ * @throws propaga el error del destino (400 `INPUT_INVALID`, 404 `EVENT_TYPE_NOT_FOUND`).
26
+ */
27
+ postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>>;
28
+ /**
29
+ * Estado de una corrida y una página de sus resultados.
30
+ *
31
+ * @throws propaga el error del destino (404 `RUN_NOT_FOUND`); nunca devuelve fallback.
32
+ */
33
+ getRun(runId: string): Promise<StandardResponse<GetRunResponse>>;
34
+ }
@@ -0,0 +1,4 @@
1
+ export * from './queue/interfaces/IRewardsEligibilityPublisher.js';
2
+ export * from './queue/RewardsEligibilityPublisher.js';
3
+ export * from './api/interfaces/IRewardsEligibilityBusinessApi.js';
4
+ export * from './api/RewardsEligibilityBusinessApi.js';
@@ -0,0 +1,4 @@
1
+ export * from './queue/interfaces/IRewardsEligibilityPublisher.js';
2
+ export * from './queue/RewardsEligibilityPublisher.js';
3
+ export * from './api/interfaces/IRewardsEligibilityBusinessApi.js';
4
+ export * from './api/RewardsEligibilityBusinessApi.js';
@@ -0,0 +1,6 @@
1
+ import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
2
+ import { IRewardsEligibilityPublisher } from "./interfaces/IRewardsEligibilityPublisher.js";
3
+ export declare class RewardsEligibilityPublisher implements IRewardsEligibilityPublisher {
4
+ private readonly REWARDS_ELIGIBILITY_QUEUE_URL;
5
+ publish(message: GrantFailedMessage): Promise<void>;
6
+ }
@@ -0,0 +1,34 @@
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
+ import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
8
+ import { injectable } from "inversify";
9
+ let RewardsEligibilityPublisher = class RewardsEligibilityPublisher {
10
+ REWARDS_ELIGIBILITY_QUEUE_URL = process.env.REWARDS_ELIGIBILITY_QUEUE_URL;
11
+ async publish(message) {
12
+ if (!this.REWARDS_ELIGIBILITY_QUEUE_URL) {
13
+ throw new Error("Error publishing message to RewardsEligibilityQueue: REWARDS_ELIGIBILITY_QUEUE_URL no está configurada");
14
+ }
15
+ try {
16
+ const client = new SQSClient();
17
+ // Cola ESTÁNDAR: el consumidor descarta avisos repetidos o atrasados comparando el `n`
18
+ // de la idempotencyKey con el del ledger (spec B 7.7), así que no hace falta dedup en la cola.
19
+ const sendMessageRequest = {
20
+ QueueUrl: this.REWARDS_ELIGIBILITY_QUEUE_URL,
21
+ MessageBody: JSON.stringify(message),
22
+ };
23
+ await client.send(new SendMessageCommand(sendMessageRequest));
24
+ }
25
+ catch (error) {
26
+ const errorMessage = error instanceof Error ? error.message : String(error);
27
+ throw new Error(`Error publishing message to RewardsEligibilityQueue: ${errorMessage}`);
28
+ }
29
+ }
30
+ };
31
+ RewardsEligibilityPublisher = __decorate([
32
+ injectable()
33
+ ], RewardsEligibilityPublisher);
34
+ export { RewardsEligibilityPublisher };
@@ -0,0 +1,15 @@
1
+ import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
2
+ /**
3
+ * Publisher del aviso `GRANT_FAILED` hacia la cola del validador (`RewardsEligibilityQueue`), que
4
+ * consume `rewards-eligibility-business` para liberar ledger y cupo (spec A 6.5, spec B 7.7).
5
+ *
6
+ * Lo produce `rewards-business` cuando una entrega termina SIN ningún ítem entregado (FAILED o
7
+ * REJECTED). Una entrega PARTIAL no avisa. El tipo es una unión cerrada a propósito: meter otro
8
+ * mensaje en una cola compartida obliga a tocar también al consumidor.
9
+ *
10
+ * Env var requerida en el consumer: `REWARDS_ELIGIBILITY_QUEUE_URL`
11
+ * (`!ImportValue SqsRewardsEligibilityQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
12
+ */
13
+ export interface IRewardsEligibilityPublisher {
14
+ publish(message: GrantFailedMessage): Promise<void>;
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/api-invoker",
3
- "version": "5.115.0",
3
+ "version": "5.117.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",
@@ -34,7 +34,7 @@
34
34
  "@fiado/gateway-adapter": "^3.14.0",
35
35
  "@fiado/http-client": "^2.0.1",
36
36
  "@fiado/logger": "^1.1.3",
37
- "@fiado/type-kit": "^3.509.0",
37
+ "@fiado/type-kit": "^3.510.0",
38
38
  "dotenv": "^16.4.7"
39
39
  },
40
40
  "peerDependencies": {
@@ -107,6 +107,8 @@ import { IReportProcessorBusinessApi } from "./report-processor-business/index.j
107
107
  import { INotificationWSMessagesPublisher } from "./notificationWebsockets/index.js";
108
108
  import NotificationWSMessagePublisher from "./notificationWebsockets/queue/NotificationWSMessagePublisher.js";
109
109
  import PeopleBusinessApi from "./people-business/api/PeopleBusinessApi.js";
110
+ import DatalakeReportsApi from "./datalake-reports-business/api/DatalakeReportsApi.js";
111
+ import { IDatalakeReportsApi } from "./datalake-reports-business/index.js";
110
112
  import { IPeopleBusinessApi } from "./people-business/index.js";
111
113
  import PlacesBusinessApi from "./places-business/api/PlacesBusinessApi.js";
112
114
  import { IPlacesBusinessApi } from "./places-business/index.js";
@@ -204,6 +206,10 @@ import { IKycVerificationsBusinessApi, KycVerificationsBusinessApi } from "./kyc
204
206
  // Webhook business — publisher de la cola compartida de entrega de eventos a suscriptores externos
205
207
  import { IWebhookBusinessPublisher, WebhookBusinessPublisher } from "./webhook-business/index.js";
206
208
  import { ICentralPaymentsWebhookApi, CentralPaymentsWebhookApi } from "./central-payments-webhook/index.js";
209
+ // Rewards — publisher de pedidos de entrega + cliente de lecturas privadas (rewards-business) y
210
+ // publisher de GRANT_FAILED + cliente del validador (rewards-eligibility-business)
211
+ import { IRewardsGrantPublisher, IRewardsBusinessApi, RewardsGrantPublisher, RewardsBusinessApi } from "./rewards-business/index.js";
212
+ import { IRewardsEligibilityPublisher, IRewardsEligibilityBusinessApi, RewardsEligibilityPublisher, RewardsEligibilityBusinessApi } from "./rewards-eligibility-business/index.js";
207
213
  // Collection engine — cliente del motor de cobro central (collect/deregister)
208
214
  import { ICollectionEngineApi } from "./collection-engine/index.js";
209
215
  import CollectionEngineApi from "./collection-engine/api/CollectionEngineApi.js";
@@ -293,6 +299,7 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
293
299
  bind<IEventHistoryApi>("IEventHistoryApi").to(EventHistoryApi);
294
300
  bind<IReportProcessorBusinessApi>("IReportProcessorBusinessApi").to(ReportProcessorBusinessApi);
295
301
  bind<IPeopleBusinessApi>("IPeopleBusinessApi").to(PeopleBusinessApi);
302
+ bind<IDatalakeReportsApi>("IDatalakeReportsApi").to(DatalakeReportsApi);
296
303
  bind<IPlacesBusinessApi>("IPlacesBusinessApi").to(PlacesBusinessApi);
297
304
  bind<ISTPBusinessApi>("ISTPBusinessApi").to(STPBusinessApi);
298
305
  bind<IProductPaymentApi>("IProductPaymentApi").to(ProductPaymentApi);
@@ -395,6 +402,16 @@ export const apiInvokerBindings = new ContainerModule(({ bind }) => {
395
402
  bind<IWebhookBusinessPublisher>("IWebhookBusinessPublisher").to(WebhookBusinessPublisher);
396
403
  bind<ICentralPaymentsWebhookApi>("ICentralPaymentsWebhookApi").to(CentralPaymentsWebhookApi);
397
404
 
405
+ // Rewards — cola compartida RewardsGrantQueue (pedidos de entrega) y lecturas privadas de rewards-business.
406
+ // El productor necesita REWARDS_GRANT_QUEUE_URL y sqs:SendMessage sobre la cola.
407
+ bind<IRewardsGrantPublisher>("IRewardsGrantPublisher").to(RewardsGrantPublisher);
408
+ bind<IRewardsBusinessApi>("IRewardsBusinessApi").to(RewardsBusinessApi);
409
+
410
+ // Rewards eligibility — GRANT_FAILED hacia la cola del validador y cliente de POST /events.
411
+ // El productor necesita REWARDS_ELIGIBILITY_QUEUE_URL y sqs:SendMessage sobre la cola.
412
+ bind<IRewardsEligibilityPublisher>("IRewardsEligibilityPublisher").to(RewardsEligibilityPublisher);
413
+ bind<IRewardsEligibilityBusinessApi>("IRewardsEligibilityBusinessApi").to(RewardsEligibilityBusinessApi);
414
+
398
415
  // Collection engine — motor de cobro central multitenant (collect/deregister)
399
416
  bind<ICollectionEngineApi>("ICollectionEngineApi").to(CollectionEngineApi);
400
417
 
@@ -0,0 +1,30 @@
1
+ import { inject, injectable } from "inversify";
2
+ import type { IHttpRequest } from "@fiado/http-client";
3
+ import { DatalakeReportDefinition, DatalakeReportResult, IDatalakeReportsApi } from "./interfaces/IDatalakeReportsApi.js";
4
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
5
+
6
+ @injectable()
7
+ export default class DatalakeReportsApi implements IDatalakeReportsApi {
8
+
9
+ private readonly baseUrl = process.env.DATALAKE_REPORTS_BUSINESS_LAMBDA_URL || "";
10
+
11
+ constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) {
12
+ }
13
+
14
+ listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>> {
15
+ return this.httpRequest.get(`${this.baseUrl}private/reports`);
16
+ }
17
+
18
+ getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>> {
19
+ return this.httpRequest.get(`${this.baseUrl}private/reports/${encodeURIComponent(id)}`);
20
+ }
21
+
22
+ runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>> {
23
+ return this.httpRequest.post(`${this.baseUrl}private/reports/${encodeURIComponent(id)}/run`, { params });
24
+ }
25
+
26
+ getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>> {
27
+ const qs = nextToken ? `?nextToken=${encodeURIComponent(nextToken)}` : "";
28
+ return this.httpRequest.get(`${this.baseUrl}private/reports/jobs/${encodeURIComponent(jobId)}${qs}`);
29
+ }
30
+ }
@@ -0,0 +1,27 @@
1
+ type FiadoApiResponse<T> = import("@fiado/type-kit/bin/apiResponse/dtos/FiadoApiResponse.js").default<T>;
2
+
3
+ /** Contrato local hasta que @fiado/type-kit publique el dominio DatalakeReports (misma forma). */
4
+ export interface DatalakeReportDefinition {
5
+ id: string; version: number; title: string; description: string;
6
+ source: { layer: "gold" | "silver"; databases: string[] };
7
+ pii: boolean; audiences: string[]; permission: string; mode: "sync" | "async"; cacheSeconds: number;
8
+ params: Record<string, { type: string; required?: boolean; default?: unknown; values?: string[]; max?: number; min?: number }>;
9
+ principalParams: string[];
10
+ columns: Array<{ name: string; type: string; label: string }>;
11
+ }
12
+ export interface DatalakeReportResult {
13
+ status: "RUNNING" | "DONE" | "FAILED";
14
+ reportId?: string; version?: number; jobId: string;
15
+ columns?: Array<{ name: string; type: string; label: string }>;
16
+ rows?: Array<Record<string, string | number | boolean | null>>;
17
+ rowCount?: number; nextToken?: string; computedAt?: string; fromCache?: boolean; bytesScanned?: number; reason?: string;
18
+ }
19
+
20
+ /** Canal privado de datalake-reports-business (reportes predefinidos sobre Gold y Silver). */
21
+ export interface IDatalakeReportsApi {
22
+ listReports(): Promise<FiadoApiResponse<DatalakeReportDefinition[]>>;
23
+ getReport(id: string): Promise<FiadoApiResponse<DatalakeReportDefinition>>;
24
+ /** 200 con filas, o 200 con status RUNNING y jobId si la corrida cae a asíncrono. */
25
+ runReport(id: string, params: Record<string, unknown>): Promise<FiadoApiResponse<DatalakeReportResult>>;
26
+ getJob(jobId: string, nextToken?: string): Promise<FiadoApiResponse<DatalakeReportResult>>;
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from './api/DatalakeReportsApi.js';
2
+ export * from './api/interfaces/IDatalakeReportsApi.js';
package/src/index.ts CHANGED
@@ -65,6 +65,7 @@ export * from "./event-history-business/index.js";
65
65
  export * from "./report-processor-business/index.js";
66
66
  export * from "./notificationWebsockets/index.js";
67
67
  export * from "./people-business/index.js";
68
+ export * from "./datalake-reports-business/index.js";
68
69
  export * from "./places-business/index.js";
69
70
  export * from "./stp-business/index.js";
70
71
  export * from "./multicommServicePayment/index.js";
@@ -122,3 +123,5 @@ export * from "./domain-callback/index.js";
122
123
  export * from "./datacultrConnector/index.js";
123
124
  export * from "./webhook-business/index.js";
124
125
  export * from "./trustonicConnector/index.js";
126
+ export * from "./rewards-business/index.js";
127
+ export * from "./rewards-eligibility-business/index.js";
@@ -0,0 +1,27 @@
1
+ import { inject, injectable } from "inversify";
2
+ import type { StandardResponse } from "@fiado/gateway-adapter";
3
+ import type { IHttpRequest } from "@fiado/http-client";
4
+ import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
5
+ import type { IRewardsBusinessApi } from "./interfaces/IRewardsBusinessApi.js";
6
+
7
+ /** Contrato y semántica completos → `IRewardsBusinessApi` (`StandardResponse` → leer `result.data`). */
8
+ @injectable()
9
+ export class RewardsBusinessApi implements IRewardsBusinessApi {
10
+
11
+ // El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//packages".
12
+ private readonly baseUrl = (process.env.REWARDS_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
13
+
14
+ constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) { }
15
+
16
+ async getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>> {
17
+ // PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA de rewards-business).
18
+ // La ruta base exacta (con o sin `/private`) la fija el plan de rewards-business; si cambia, ajustar aquí.
19
+ const url = `${this.baseUrl}/packages/${encodeURIComponent(packageId)}`;
20
+ return await this.httpRequest.get<StandardResponse<PackageContractResponse>>(url);
21
+ }
22
+
23
+ async getGrant(grantId: string): Promise<StandardResponse<GrantResponse>> {
24
+ const url = `${this.baseUrl}/grants/${encodeURIComponent(grantId)}`;
25
+ return await this.httpRequest.get<StandardResponse<GrantResponse>>(url);
26
+ }
27
+ }
@@ -0,0 +1,35 @@
1
+ import type { StandardResponse } from "@fiado/gateway-adapter";
2
+ import type { GrantResponse, PackageContractResponse } from "@fiado/type-kit/bin/rewards/index.js";
3
+
4
+ /**
5
+ * Cliente de la API privada de `rewards-business`. SOLO LECTURA: no hay endpoint privado de
6
+ * escritura (spec 2026-10-05, D11); los pedidos de entrega se publican con `IRewardsGrantPublisher`.
7
+ *
8
+ * Devuelve `StandardResponse<T>` → leer `result.data`. NO uses `FiadoApiResponse`: los dos tipos no
9
+ * son intercambiables en TypeScript (`code`/`date` admiten `null` en uno y no en el otro).
10
+ *
11
+ * Env var requerida en el consumer: `REWARDS_BUSINESS_LAMBDA_URL`. El template.yml del consumer la
12
+ * setea con:
13
+ *
14
+ * REWARDS_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardsbusiness-lambda}}'
15
+ *
16
+ * El nombre del parámetro SSM es el `stack_name` del `samconfig.toml` de rewards-business (el
17
+ * buildspec registra el SSM con ese nombre): debe coincidir con el de arriba.
18
+ */
19
+ export interface IRewardsBusinessApi {
20
+ /**
21
+ * Contrato de entrada de un paquete: por `itemKey`, las `dependencies` de su recompensa, más
22
+ * nombre, descripción, costo y `totalCost` por moneda. El backoffice del validador lo usa para
23
+ * no dejar activar una evaluación con un paquete inactivo o con inputs que no cubren el contrato.
24
+ *
25
+ * @throws propaga el error del destino (404 `PACKAGE_NOT_FOUND`); nunca devuelve fallback.
26
+ */
27
+ getPackage(packageId: string): Promise<StandardResponse<PackageContractResponse>>;
28
+
29
+ /**
30
+ * Estado de una entrega y de cada ítem. El `grantId` se calcula con `grantIdFor`.
31
+ *
32
+ * @throws propaga el error del destino (404 `GRANT_NOT_FOUND`); nunca devuelve fallback.
33
+ */
34
+ getGrant(grantId: string): Promise<StandardResponse<GrantResponse>>;
35
+ }
@@ -0,0 +1,36 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ /** Largo (hex) del `grantId`. 128 bits: colisión despreciable. */
4
+ export const REWARDS_GRANT_ID_LENGTH = 32;
5
+
6
+ /** Límite de `BenefitPaymentRequest.transactionNumber`. */
7
+ export const REWARDS_TRANSACTION_NUMBER_MAX_LENGTH = 64;
8
+
9
+ const TRANSACTION_NUMBER_PREFIX = 'RW-';
10
+
11
+ /**
12
+ * Largo máximo del `itemKey` de un paquete para que `RW-{grantId}-{itemKey}` quepa en 64:
13
+ * 64 − len('RW-') − len('-') − 32 = 28. rewards-business debe validarlo al guardar un paquete.
14
+ */
15
+ export const REWARDS_MAX_ITEM_KEY_LENGTH =
16
+ REWARDS_TRANSACTION_NUMBER_MAX_LENGTH - TRANSACTION_NUMBER_PREFIX.length - 1 - REWARDS_GRANT_ID_LENGTH;
17
+
18
+ /**
19
+ * `grantId` determinístico de una entrega de rewards (spec A 5.3, D11). Lo calcula el llamador al
20
+ * publicar `GRANT_REQUESTED` (para conocerlo sin esperar respuesta) y el worker de rewards al
21
+ * consumirlo: tienen que dar lo mismo, por eso es UNA función compartida.
22
+ *
23
+ * El largo de `service` va como prefijo para que ('a:b','c') y ('a','b:c') no colisionen.
24
+ */
25
+ export function grantIdFor(service: string, idempotencyKey: string): string {
26
+ if (!service) {
27
+ throw new Error('grantIdFor: service es obligatorio');
28
+ }
29
+ if (!idempotencyKey) {
30
+ throw new Error('grantIdFor: idempotencyKey es obligatoria');
31
+ }
32
+ return createHash('sha256')
33
+ .update(`${service.length}:${service}:${idempotencyKey}`)
34
+ .digest('hex')
35
+ .slice(0, REWARDS_GRANT_ID_LENGTH);
36
+ }
@@ -0,0 +1,5 @@
1
+ export * from './grantIdFor.js';
2
+ export * from './queue/interfaces/IRewardsGrantPublisher.js';
3
+ export * from './queue/RewardsGrantPublisher.js';
4
+ export * from './api/interfaces/IRewardsBusinessApi.js';
5
+ export * from './api/RewardsBusinessApi.js';
@@ -0,0 +1,80 @@
1
+ import { SendMessageBatchCommand, SendMessageBatchRequestEntry, SendMessageCommand, SendMessageRequest, SQSClient } from "@aws-sdk/client-sqs";
2
+ import { injectable } from "inversify";
3
+ import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
4
+ import { IRewardsGrantPublisher, RewardsGrantBatchResult } from "./interfaces/IRewardsGrantPublisher.js";
5
+
6
+ const SQS_BATCH_MAX_ENTRIES = 10;
7
+ // Límite SQS: 262 144 bytes por SendMessageBatch. Margen para no rozarlo.
8
+ const SQS_BATCH_MAX_BYTES = 250_000;
9
+
10
+ @injectable()
11
+ export class RewardsGrantPublisher implements IRewardsGrantPublisher {
12
+ private readonly REWARDS_GRANT_QUEUE_URL = process.env.REWARDS_GRANT_QUEUE_URL;
13
+
14
+ async publish(message: GrantRequestedMessage): Promise<void> {
15
+ if (!this.REWARDS_GRANT_QUEUE_URL) {
16
+ throw new Error("Error publishing message to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
17
+ }
18
+ try {
19
+ const client: SQSClient = new SQSClient();
20
+ // Cola ESTÁNDAR (no FIFO): sin MessageGroupId/MessageDeduplicationId ni orden garantizado.
21
+ // La idempotencia la hace el consumidor con el `grantId` derivado de (source.service +
22
+ // idempotencyKey) y un PutItem condicional.
23
+ const sendMessageRequest: SendMessageRequest = {
24
+ QueueUrl: this.REWARDS_GRANT_QUEUE_URL,
25
+ MessageBody: JSON.stringify(message),
26
+ };
27
+ await client.send(new SendMessageCommand(sendMessageRequest));
28
+ } catch (error) {
29
+ const errorMessage = error instanceof Error ? error.message : String(error);
30
+ throw new Error(`Error publishing message to RewardsGrantQueue: ${errorMessage}`);
31
+ }
32
+ }
33
+
34
+ async publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult> {
35
+ const failedIdempotencyKeys: string[] = [];
36
+ const senderFaultIdempotencyKeys: string[] = [];
37
+ if (messages.length === 0) {
38
+ return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
39
+ }
40
+ if (!this.REWARDS_GRANT_QUEUE_URL) {
41
+ throw new Error("Error publishing batch to RewardsGrantQueue: REWARDS_GRANT_QUEUE_URL no está configurada");
42
+ }
43
+ try {
44
+ const client: SQSClient = new SQSClient();
45
+ const keyByEntryId = new Map<string, string>();
46
+ let current: SendMessageBatchRequestEntry[] = [];
47
+ let currentBytes = 0;
48
+
49
+ const flush = async (): Promise<void> => {
50
+ if (current.length === 0) return;
51
+ const result = await client.send(
52
+ new SendMessageBatchCommand({ QueueUrl: this.REWARDS_GRANT_QUEUE_URL, Entries: current }),
53
+ );
54
+ for (const failed of result.Failed ?? []) {
55
+ const key = failed.Id ? keyByEntryId.get(failed.Id) : undefined;
56
+ if (key) (failed.SenderFault ? senderFaultIdempotencyKeys : failedIdempotencyKeys).push(key);
57
+ }
58
+ current = [];
59
+ currentBytes = 0;
60
+ };
61
+
62
+ for (let index = 0; index < messages.length; index += 1) {
63
+ const body = JSON.stringify(messages[index]);
64
+ const bytes = Buffer.byteLength(body, "utf8");
65
+ if (current.length >= SQS_BATCH_MAX_ENTRIES || (current.length > 0 && currentBytes + bytes > SQS_BATCH_MAX_BYTES)) {
66
+ await flush();
67
+ }
68
+ const id = `m${index}`;
69
+ keyByEntryId.set(id, messages[index].idempotencyKey);
70
+ current.push({ Id: id, MessageBody: body });
71
+ currentBytes += bytes;
72
+ }
73
+ await flush();
74
+ return { failedIdempotencyKeys, senderFaultIdempotencyKeys };
75
+ } catch (error) {
76
+ const errorMessage = error instanceof Error ? error.message : String(error);
77
+ throw new Error(`Error publishing batch to RewardsGrantQueue: ${errorMessage}`);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,35 @@
1
+ import type { GrantRequestedMessage } from "@fiado/type-kit/bin/rewards/index.js";
2
+
3
+ /**
4
+ * Resultado de `publishBatch`: las `idempotencyKey` que SQS NO aceptó.
5
+ * - `failedIdempotencyKeys`: `SenderFault=false` (falla transitoria de SQS) → el llamador PUEDE reintentar.
6
+ * - `senderFaultIdempotencyKeys`: `SenderFault=true` (el mensaje es inválido para SQS) → NO reintentar:
7
+ * volver a mandarlo falla igual.
8
+ */
9
+ export interface RewardsGrantBatchResult {
10
+ failedIdempotencyKeys: string[];
11
+ senderFaultIdempotencyKeys: string[];
12
+ }
13
+
14
+ /**
15
+ * Publisher de pedidos de entrega hacia `RewardsGrantQueue`, que consume `rewards-business`
16
+ * (spec 2026-10-05, D11). La cola es COMPARTIDA entre lambdas: el estándar Fiado publica a una cola
17
+ * compartida con un publisher de `@fiado/api-invoker` (mismo patrón que `ITransactionPublisher`).
18
+ *
19
+ * Que `publish` funcione ES la aceptación del pedido: no hay respuesta síncrona. El `grantId` no
20
+ * viaja en el mensaje; el llamador lo conoce con `grantIdFor(source.service, idempotencyKey)`.
21
+ *
22
+ * Env var requerida en el consumer: `REWARDS_GRANT_QUEUE_URL`
23
+ * (`!ImportValue SqsRewardsGrantQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
24
+ */
25
+ export interface IRewardsGrantPublisher {
26
+ publish(message: GrantRequestedMessage): Promise<void>;
27
+
28
+ /**
29
+ * Publica varios pedidos con `SendMessageBatch` (tandas de 10 y de ≤ 250 000 bytes). El validador
30
+ * suelta lo masivo por tramos con esto (spec B 7.1 y 7.6). Un fallo parcial NO lanza: se devuelve
31
+ * en `failedIdempotencyKeys` (reintentables) o `senderFaultIdempotencyKeys` (NO reintentar). Si la
32
+ * llamada entera falla, lanza (reintento idempotente por `grantId`).
33
+ */
34
+ publishBatch(messages: GrantRequestedMessage[]): Promise<RewardsGrantBatchResult>;
35
+ }
@@ -0,0 +1,26 @@
1
+ import { inject, injectable } from "inversify";
2
+ import type { StandardResponse } from "@fiado/gateway-adapter";
3
+ import type { IHttpRequest } from "@fiado/http-client";
4
+ import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
5
+ import type { IRewardsEligibilityBusinessApi } from "./interfaces/IRewardsEligibilityBusinessApi.js";
6
+
7
+ /** Contrato y semántica completos → `IRewardsEligibilityBusinessApi` (`StandardResponse` → leer `result.data`). */
8
+ @injectable()
9
+ export class RewardsEligibilityBusinessApi implements IRewardsEligibilityBusinessApi {
10
+
11
+ // El buildspec publica la URL en SSM con "/" final (`.../v1/`): se normaliza para no armar "//events".
12
+ private readonly baseUrl = (process.env.REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL || "").replace(/\/+$/, "");
13
+
14
+ constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) { }
15
+
16
+ async postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>> {
17
+ // PROVISIONAL: sin prefijo `/private` (la API que resuelve el SSM es la PRIVADA).
18
+ // La ruta base exacta (con o sin `/private`) la fija el plan del validador; si cambia, ajustar aquí.
19
+ return await this.httpRequest.post<StandardResponse<PostEventResponse>>(`${this.baseUrl}/events`, request);
20
+ }
21
+
22
+ async getRun(runId: string): Promise<StandardResponse<GetRunResponse>> {
23
+ const url = `${this.baseUrl}/runs/${encodeURIComponent(runId)}`;
24
+ return await this.httpRequest.get<StandardResponse<GetRunResponse>>(url);
25
+ }
26
+ }
@@ -0,0 +1,36 @@
1
+ import type { StandardResponse } from "@fiado/gateway-adapter";
2
+ import type { GetRunResponse, PostEventRequest, PostEventResponse } from "@fiado/type-kit/bin/rewardsEligibility/index.js";
3
+
4
+ /**
5
+ * Cliente de la API privada de `rewards-eligibility-business` (spec 2026-10-06, §6).
6
+ *
7
+ * Devuelve `StandardResponse<T>` → leer `result.data`. Env var requerida en el consumer:
8
+ * `REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL`, seteada con
9
+ *
10
+ * REWARDS_ELIGIBILITY_BUSINESS_LAMBDA_URL: '{{resolve:ssm:fiado-rewardseligibilitybusiness-lambda}}'
11
+ *
12
+ * (el nombre del SSM es el `stack_name` del `samconfig.toml` del validador).
13
+ *
14
+ * El emisor NO decide la elegibilidad: informa qué pasó y quién participó (E3). La llamada es NO
15
+ * bloqueante para el flujo del emisor: si falla, el emisor sigue y reintenta con el mismo
16
+ * `idempotencyKey`.
17
+ *
18
+ * RUTA BASE PROVISIONAL: se fija en el plan del validador (`rewards-eligibility-business`); si cambia
19
+ * (con o sin `/private`), se ajusta este cliente y se republica.
20
+ */
21
+ export interface IRewardsEligibilityBusinessApi {
22
+ /**
23
+ * Dispara las evaluaciones suscritas al `eventKey`. Responde 202 con una corrida por evaluación
24
+ * (`runs` vacío si nadie escucha el evento).
25
+ *
26
+ * @throws propaga el error del destino (400 `INPUT_INVALID`, 404 `EVENT_TYPE_NOT_FOUND`).
27
+ */
28
+ postEvent(request: PostEventRequest): Promise<StandardResponse<PostEventResponse>>;
29
+
30
+ /**
31
+ * Estado de una corrida y una página de sus resultados.
32
+ *
33
+ * @throws propaga el error del destino (404 `RUN_NOT_FOUND`); nunca devuelve fallback.
34
+ */
35
+ getRun(runId: string): Promise<StandardResponse<GetRunResponse>>;
36
+ }
@@ -0,0 +1,4 @@
1
+ export * from './queue/interfaces/IRewardsEligibilityPublisher.js';
2
+ export * from './queue/RewardsEligibilityPublisher.js';
3
+ export * from './api/interfaces/IRewardsEligibilityBusinessApi.js';
4
+ export * from './api/RewardsEligibilityBusinessApi.js';
@@ -0,0 +1,28 @@
1
+ import { SendMessageCommand, SendMessageRequest, SQSClient } from "@aws-sdk/client-sqs";
2
+ import { injectable } from "inversify";
3
+ import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
4
+ import { IRewardsEligibilityPublisher } from "./interfaces/IRewardsEligibilityPublisher.js";
5
+
6
+ @injectable()
7
+ export class RewardsEligibilityPublisher implements IRewardsEligibilityPublisher {
8
+ private readonly REWARDS_ELIGIBILITY_QUEUE_URL = process.env.REWARDS_ELIGIBILITY_QUEUE_URL;
9
+
10
+ async publish(message: GrantFailedMessage): Promise<void> {
11
+ if (!this.REWARDS_ELIGIBILITY_QUEUE_URL) {
12
+ throw new Error("Error publishing message to RewardsEligibilityQueue: REWARDS_ELIGIBILITY_QUEUE_URL no está configurada");
13
+ }
14
+ try {
15
+ const client: SQSClient = new SQSClient();
16
+ // Cola ESTÁNDAR: el consumidor descarta avisos repetidos o atrasados comparando el `n`
17
+ // de la idempotencyKey con el del ledger (spec B 7.7), así que no hace falta dedup en la cola.
18
+ const sendMessageRequest: SendMessageRequest = {
19
+ QueueUrl: this.REWARDS_ELIGIBILITY_QUEUE_URL,
20
+ MessageBody: JSON.stringify(message),
21
+ };
22
+ await client.send(new SendMessageCommand(sendMessageRequest));
23
+ } catch (error) {
24
+ const errorMessage = error instanceof Error ? error.message : String(error);
25
+ throw new Error(`Error publishing message to RewardsEligibilityQueue: ${errorMessage}`);
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,16 @@
1
+ import type { GrantFailedMessage } from "@fiado/type-kit/bin/rewards/index.js";
2
+
3
+ /**
4
+ * Publisher del aviso `GRANT_FAILED` hacia la cola del validador (`RewardsEligibilityQueue`), que
5
+ * consume `rewards-eligibility-business` para liberar ledger y cupo (spec A 6.5, spec B 7.7).
6
+ *
7
+ * Lo produce `rewards-business` cuando una entrega termina SIN ningún ítem entregado (FAILED o
8
+ * REJECTED). Una entrega PARTIAL no avisa. El tipo es una unión cerrada a propósito: meter otro
9
+ * mensaje en una cola compartida obliga a tocar también al consumidor.
10
+ *
11
+ * Env var requerida en el consumer: `REWARDS_ELIGIBILITY_QUEUE_URL`
12
+ * (`!ImportValue SqsRewardsEligibilityQueueUrl`) y el permiso `sqs:SendMessage` sobre la cola.
13
+ */
14
+ export interface IRewardsEligibilityPublisher {
15
+ publish(message: GrantFailedMessage): Promise<void>;
16
+ }