@fiado/api-invoker 5.34.0 → 5.36.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.
@@ -0,0 +1,31 @@
1
+ name: "Publish to npm"
2
+
3
+ on:
4
+ push:
5
+ branches: [ develop ]
6
+ paths-ignore:
7
+ - '**.md'
8
+ - 'docs/**'
9
+ - '.github/**'
10
+ - '.gitignore'
11
+ - '.npmignore'
12
+ workflow_dispatch:
13
+ inputs:
14
+ dry-run:
15
+ description: 'Corre build, test y npm publish --dry-run sin publicar de verdad'
16
+ required: false
17
+ default: false
18
+ type: boolean
19
+
20
+ concurrency:
21
+ group: npm-publish-${{ github.ref }}
22
+ cancel-in-progress: false
23
+
24
+ jobs:
25
+ publish:
26
+ uses: Fiado-Today/github-workflows/.github/workflows/npm-publish.yml@develop
27
+ permissions:
28
+ contents: write
29
+ with:
30
+ dry-run: ${{ inputs.dry-run == true }}
31
+ secrets: inherit
@@ -0,0 +1,28 @@
1
+ import type { IHttpRequest } from "@fiado/http-client";
2
+ import { StandardResponse } from "@fiado/gateway-adapter";
3
+ import { DeviceAutoLockScheduleRequest, DeviceAutoLockScheduleResponse, DeviceEnrollInitiateResponse, DeviceEnrollRequest, DeviceEnrollStatusRequest, DeviceEnrollStatusResponse, DeviceLastSeenRequest, DeviceLastSeenResponse, DeviceLockRequest, DeviceLockResponse, DeviceStatusRequest, DeviceStatusResponse, DeviceUnlockRequest, DeviceUnlockResponse } from "@fiado/type-kit/bin/mdm/index.js";
4
+ import { DeviceBatchRequest, DeviceBatchResult, IDatacultrConnectorApi } from "./interfaces/IDatacultrConnectorApi.js";
5
+ /**
6
+ * Publisher HTTP del lambda `datacultr-connector` (provider MDM de SureKeep). Contrato y semántica
7
+ * completos → `IDatacultrConnectorApi` (paths sin prefijo `/private`, sin tenant, `StandardResponse`
8
+ * → leer `result.data`, granularidad por ítem, y la limitación de `enrollDevices`).
9
+ *
10
+ * Env var requerida en el consumer: `DATACULTR_CONNECTOR_URL`.
11
+ * El template.yml del consumer la setea con:
12
+ *
13
+ * DATACULTR_CONNECTOR_URL: '{{resolve:ssm:datacultr-connector}}'
14
+ *
15
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
16
+ */
17
+ export default class DatacultrConnectorApi implements IDatacultrConnectorApi {
18
+ private httpRequest;
19
+ private readonly baseUrl;
20
+ constructor(httpRequest: IHttpRequest);
21
+ enrollDevices(input: DeviceBatchRequest<DeviceEnrollRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollInitiateResponse>>>;
22
+ enrollStatus(input: DeviceBatchRequest<DeviceEnrollStatusRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollStatusResponse>>>;
23
+ getStatus(input: DeviceBatchRequest<DeviceStatusRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceStatusResponse>>>;
24
+ getLastSeen(input: DeviceBatchRequest<DeviceLastSeenRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceLastSeenResponse>>>;
25
+ lockDevices(input: DeviceBatchRequest<DeviceLockRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceLockResponse>>>;
26
+ unlockDevices(input: DeviceBatchRequest<DeviceUnlockRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceUnlockResponse>>>;
27
+ autoLockSchedule(input: DeviceBatchRequest<DeviceAutoLockScheduleRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceAutoLockScheduleResponse>>>;
28
+ }
@@ -0,0 +1,60 @@
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
+ /**
15
+ * Publisher HTTP del lambda `datacultr-connector` (provider MDM de SureKeep). Contrato y semántica
16
+ * completos → `IDatacultrConnectorApi` (paths sin prefijo `/private`, sin tenant, `StandardResponse`
17
+ * → leer `result.data`, granularidad por ítem, y la limitación de `enrollDevices`).
18
+ *
19
+ * Env var requerida en el consumer: `DATACULTR_CONNECTOR_URL`.
20
+ * El template.yml del consumer la setea con:
21
+ *
22
+ * DATACULTR_CONNECTOR_URL: '{{resolve:ssm:datacultr-connector}}'
23
+ *
24
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
25
+ */
26
+ let DatacultrConnectorApi = class DatacultrConnectorApi {
27
+ httpRequest;
28
+ // El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//devices".
29
+ baseUrl = (process.env.DATACULTR_CONNECTOR_URL || "").replace(/\/+$/, "");
30
+ constructor(httpRequest) {
31
+ this.httpRequest = httpRequest;
32
+ }
33
+ async enrollDevices(input) {
34
+ return await this.httpRequest.post(`${this.baseUrl}/devices/enroll`, input);
35
+ }
36
+ async enrollStatus(input) {
37
+ return await this.httpRequest.post(`${this.baseUrl}/devices/enroll/status`, input);
38
+ }
39
+ async getStatus(input) {
40
+ return await this.httpRequest.post(`${this.baseUrl}/devices/status`, input);
41
+ }
42
+ async getLastSeen(input) {
43
+ return await this.httpRequest.post(`${this.baseUrl}/devices/last-seen`, input);
44
+ }
45
+ async lockDevices(input) {
46
+ return await this.httpRequest.post(`${this.baseUrl}/devices/lock`, input);
47
+ }
48
+ async unlockDevices(input) {
49
+ return await this.httpRequest.post(`${this.baseUrl}/devices/unlock`, input);
50
+ }
51
+ async autoLockSchedule(input) {
52
+ return await this.httpRequest.post(`${this.baseUrl}/devices/auto-lock-schedule`, input);
53
+ }
54
+ };
55
+ DatacultrConnectorApi = __decorate([
56
+ injectable(),
57
+ __param(0, inject("IHttpRequest")),
58
+ __metadata("design:paramtypes", [Object])
59
+ ], DatacultrConnectorApi);
60
+ export default DatacultrConnectorApi;
@@ -0,0 +1,105 @@
1
+ import { StandardResponse } from "@fiado/gateway-adapter";
2
+ import { DeviceAutoLockScheduleRequest, DeviceAutoLockScheduleResponse, DeviceEnrollInitiateResponse, DeviceEnrollRequest, DeviceEnrollStatusRequest, DeviceEnrollStatusResponse, DeviceLastSeenRequest, DeviceLastSeenResponse, DeviceLockRequest, DeviceLockResponse, DeviceStatusRequest, DeviceStatusResponse, DeviceUnlockRequest, DeviceUnlockResponse } from "@fiado/type-kit/bin/mdm/index.js";
3
+ /**
4
+ * Contrato del publisher HTTP del lambda `datacultr-connector` — el provider MDM que traduce el
5
+ * contrato agnóstico de dispositivo (`@fiado/type-kit/bin/mdm`) a la API de Datacultr. Su consumidor
6
+ * es el módulo `device/` de `loan-credit-business` (`MdmConnectorClient`), que NUNCA le pega por
7
+ * `http-client` directo: entre lambdas Fiado se habla siempre por `@fiado/api-invoker`.
8
+ *
9
+ * ─────────────────────────────────────────────────────────────────────────────
10
+ * PATHS — `/devices/...`, SIN prefijo `/private`.
11
+ * ─────────────────────────────────────────────────────────────────────────────
12
+ * A diferencia de la mayoría de los privados Fiado (`/private/...`), el `PrivateController` de este
13
+ * connector declara los paths como `/devices/enroll`, `/devices/status`, etc. El aislamiento no lo da
14
+ * el prefijo sino la red: la API privada es `EndpointConfiguration: PRIVATE` con ResourcePolicy que
15
+ * deniega todo lo que no venga de los VPC Endpoints de Fiado.
16
+ *
17
+ * ─────────────────────────────────────────────────────────────────────────────
18
+ * SIN TENANT — hoy el connector es efectivamente single-tenant.
19
+ * ─────────────────────────────────────────────────────────────────────────────
20
+ * Ningún método lleva `tenantId` porque el destino NO lo resuelve: su `PrivateController` no lee
21
+ * `X-Tenant-Id` ni `x-tenant-issuer` (cero referencias en el repo). Las credenciales de Datacultr
22
+ * salen de un único secret del lambda. DEUDA a levantar cuando el connector implemente M6
23
+ * (multi-tenant del MDM): ahí este contrato suma el tenant y pasa a ser un cambio MAJOR.
24
+ *
25
+ * ─────────────────────────────────────────────────────────────────────────────
26
+ * AUTORIZACIÓN — `Feature.ANONIMUS`.
27
+ * ─────────────────────────────────────────────────────────────────────────────
28
+ * Los privados del connector son `Feature.ANONIMUS`: sin RBAC y sin claims. La única protección es
29
+ * la VPC, así que el caller no manda `Authorization` ni headers de identidad.
30
+ *
31
+ * ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
32
+ * Errores de transporte (falla el lambda entero, no un ítem): el publisher RECHAZA con el
33
+ * `DomainError` tipado del destino. Nunca fallback: el caller jamás recibe un batch vacío
34
+ * disfrazado de éxito.
35
+ *
36
+ * Granularidad POR ÍTEM — el batch NUNCA falla entero:
37
+ * - Cada `results[i]` trae su propio `status` (`SUCCESS` | `PENDING` | `ERROR`) y un `error` opcional.
38
+ * Un 200 puede traer ítems en `ERROR`: el caller TIENE que recorrer `results` y decidir por ítem.
39
+ * - El `error.code` del ítem es un `MdmErrorCodeEnum` (`PROVIDER_NOT_SUPPORTED`, `DEVICE_NOT_FOUND`,
40
+ * `PROVIDER_TIMEOUT`, …). El `status` no lleva el motivo: el motivo vive solo en `error`.
41
+ *
42
+ * Env var requerida en el consumer: `DATACULTR_CONNECTOR_URL`.
43
+ * El template.yml del consumer la setea con:
44
+ *
45
+ * DATACULTR_CONNECTOR_URL: '{{resolve:ssm:datacultr-connector}}'
46
+ *
47
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
48
+ */
49
+ /**
50
+ * Sobre de entrada de los endpoints batch del connector. No vive en `@fiado/type-kit/bin/mdm`
51
+ * porque los DTOs publicados son de UN imei; el sobre es dominio del líder (gap E8), migrar a
52
+ * type-kit cuando lo publique.
53
+ */
54
+ export interface DeviceBatchRequest<TItem> {
55
+ items: TItem[];
56
+ }
57
+ /** Sobre de salida de los endpoints batch: viaja dentro de `StandardResponse.data`. Mismo gap E8. */
58
+ export interface DeviceBatchResult<TItem> {
59
+ results: TItem[];
60
+ }
61
+ export interface IDatacultrConnectorApi {
62
+ /**
63
+ * POST `/devices/enroll` — pre-registra la expectativa de enrolamiento de cada IMEI.
64
+ *
65
+ * ⚠️ NO TOCA AL PROVEEDOR. Textual del openapi del destino: "Pre-registrar expectativa de
66
+ * enrollment (no toca provider) — DC no acepta enrollment outbound; el webhook inbound (B10) es
67
+ * quien actualiza la row a SUCCESS". Solo persiste una row `PENDING` en `PendingOperation_GT`.
68
+ *
69
+ * ⚠️ Y Datacultr confirmó por correo (2026-08-18) que NO MANDA WEBHOOKS. Sumando las dos: hoy un
70
+ * enrolamiento iniciado por esta vía queda `PENDING` PARA SIEMPRE. El caller NO debe tratar el
71
+ * `PENDING` como "en vuelo, ya se va a resolver" — no hay quien lo resuelva mientras el webhook
72
+ * inbound no exista. El enrolamiento real se hace hoy fuera de banda, en la consola de Datacultr.
73
+ */
74
+ enrollDevices(input: DeviceBatchRequest<DeviceEnrollRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollInitiateResponse>>>;
75
+ /**
76
+ * POST `/devices/enroll/status` — estado del enrolamiento de cada IMEI. Lee DDB, no toca al
77
+ * proveedor.
78
+ *
79
+ * ⚠️ Espeja la limitación de `enrollDevices`: como nadie actualiza la row (Datacultr no manda
80
+ * webhooks), este método devuelve `PENDING` indefinidamente para todo lo iniciado por esa vía.
81
+ * Un `PENDING` acá NO es señal de progreso.
82
+ */
83
+ enrollStatus(input: DeviceBatchRequest<DeviceEnrollStatusRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollStatusResponse>>>;
84
+ /**
85
+ * POST `/devices/status` — estado actual de cada IMEI (bloqueado, libre, sin enrolar).
86
+ * Si el batch trae un solo ítem, el destino cae a Registration Status y enriquece brand/model.
87
+ */
88
+ getStatus(input: DeviceBatchRequest<DeviceStatusRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceStatusResponse>>>;
89
+ /** POST `/devices/last-seen` — último contacto reportado por cada IMEI (señal de vida). */
90
+ getLastSeen(input: DeviceBatchRequest<DeviceLastSeenRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceLastSeenResponse>>>;
91
+ /**
92
+ * POST `/devices/lock` — bloquea cada IMEI.
93
+ * Los ítems con `mode: TEMPORARY` se degradan por ítem con `PROVIDER_NOT_SUPPORTED` sin llegar al
94
+ * proveedor: Datacultr solo soporta el bloqueo firme.
95
+ */
96
+ lockDevices(input: DeviceBatchRequest<DeviceLockRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceLockResponse>>>;
97
+ /** POST `/devices/unlock` — desbloquea cada IMEI. */
98
+ unlockDevices(input: DeviceBatchRequest<DeviceUnlockRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceUnlockResponse>>>;
99
+ /**
100
+ * POST `/devices/auto-lock-schedule` — programa el bloqueo de cada IMEI en una fecha futura.
101
+ * `lockAt` es ISO8601 UTC y el destino valida `lockAt > now` antes de llamar al proveedor.
102
+ * Es la vía real del bloqueo en Datacultr: el inmediato no surte efecto, el programado sí.
103
+ */
104
+ autoLockSchedule(input: DeviceBatchRequest<DeviceAutoLockScheduleRequest>): Promise<StandardResponse<DeviceBatchResult<DeviceAutoLockScheduleResponse>>>;
105
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./api/interfaces/IDatacultrConnectorApi.js";
2
+ export { default as DatacultrConnectorApi } from "./api/DatacultrConnectorApi.js";
@@ -0,0 +1,2 @@
1
+ export * from "./api/interfaces/IDatacultrConnectorApi.js";
2
+ export { default as DatacultrConnectorApi } from "./api/DatacultrConnectorApi.js";
package/bin/index.d.ts CHANGED
@@ -110,3 +110,4 @@ export * from "./kyc/index.js";
110
110
  export * from "./central-payments-webhook/index.js";
111
111
  export * from "./collection-engine/index.js";
112
112
  export * from "./domain-callback/index.js";
113
+ export * from "./datacultrConnector/index.js";
package/bin/index.js CHANGED
@@ -110,3 +110,4 @@ export * from "./kyc/index.js";
110
110
  export * from "./central-payments-webhook/index.js";
111
111
  export * from "./collection-engine/index.js";
112
112
  export * from "./domain-callback/index.js";
113
+ export * from "./datacultrConnector/index.js";
@@ -0,0 +1,27 @@
1
+ import type { IHttpRequest } from "@fiado/http-client";
2
+ import { StandardResponse } from "@fiado/gateway-adapter";
3
+ import { LoanDeviceEnrollRequest, LoanDeviceEnrollResponse, LoanDeviceEnrollStatusRequest, LoanDeviceEnrollStatusResponse, LoanDeviceLastSeenRequest, LoanDeviceLastSeenResponse, LoanDeviceManualLockRequest, LoanDeviceManualLockResponse, LoanDeviceManualUnlockRequest, LoanDeviceManualUnlockResponse, LoanDeviceStatusRequest, LoanDeviceStatusResponse } from "@fiado/type-kit/bin/loanCredit/index.js";
4
+ import { ILoanCreditDeviceApi } from "./interfaces/ILoanCreditDeviceApi.js";
5
+ /**
6
+ * Publisher HTTP de los endpoints de dispositivo/MDM del lambda `loan-credit-business`
7
+ * (componente 09 SureKeep F2). Contrato y semántica completos → `ILoanCreditDeviceApi`
8
+ * (tenantId obligatorio, `StandardResponse` → leer `result.data`, granularidad por ítem).
9
+ *
10
+ * Env var requerida en el consumer: `LOAN_CREDIT_BUSINESS_URL`.
11
+ * El template.yml del consumer la setea con:
12
+ *
13
+ * LOAN_CREDIT_BUSINESS_URL: '{{resolve:ssm:loan-credit-business}}'
14
+ *
15
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
16
+ */
17
+ export default class LoanCreditDeviceApi implements ILoanCreditDeviceApi {
18
+ private httpRequest;
19
+ private readonly baseUrl;
20
+ constructor(httpRequest: IHttpRequest);
21
+ enroll(input: LoanDeviceEnrollRequest, tenantId: string): Promise<StandardResponse<LoanDeviceEnrollResponse>>;
22
+ getEnrollStatus(input: LoanDeviceEnrollStatusRequest, tenantId: string): Promise<StandardResponse<LoanDeviceEnrollStatusResponse>>;
23
+ getStatus(input: LoanDeviceStatusRequest, tenantId: string): Promise<StandardResponse<LoanDeviceStatusResponse>>;
24
+ getLastSeen(input: LoanDeviceLastSeenRequest, tenantId: string): Promise<StandardResponse<LoanDeviceLastSeenResponse>>;
25
+ manualLock(input: LoanDeviceManualLockRequest, tenantId: string): Promise<StandardResponse<LoanDeviceManualLockResponse>>;
26
+ manualUnlock(input: LoanDeviceManualUnlockRequest, tenantId: string): Promise<StandardResponse<LoanDeviceManualUnlockResponse>>;
27
+ }
@@ -0,0 +1,63 @@
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
+ /**
15
+ * Publisher HTTP de los endpoints de dispositivo/MDM del lambda `loan-credit-business`
16
+ * (componente 09 SureKeep F2). Contrato y semántica completos → `ILoanCreditDeviceApi`
17
+ * (tenantId obligatorio, `StandardResponse` → leer `result.data`, granularidad por ítem).
18
+ *
19
+ * Env var requerida en el consumer: `LOAN_CREDIT_BUSINESS_URL`.
20
+ * El template.yml del consumer la setea con:
21
+ *
22
+ * LOAN_CREDIT_BUSINESS_URL: '{{resolve:ssm:loan-credit-business}}'
23
+ *
24
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
25
+ */
26
+ let LoanCreditDeviceApi = class LoanCreditDeviceApi {
27
+ httpRequest;
28
+ // El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//private".
29
+ baseUrl = (process.env.LOAN_CREDIT_BUSINESS_URL || "").replace(/\/+$/, "");
30
+ constructor(httpRequest) {
31
+ this.httpRequest = httpRequest;
32
+ }
33
+ async enroll(input, tenantId) {
34
+ const url = `${this.baseUrl}/private/devices/enroll?tenantId=${encodeURIComponent(tenantId)}`;
35
+ return await this.httpRequest.post(url, input);
36
+ }
37
+ async getEnrollStatus(input, tenantId) {
38
+ const url = `${this.baseUrl}/private/devices/enroll-status?tenantId=${encodeURIComponent(tenantId)}`;
39
+ return await this.httpRequest.post(url, input);
40
+ }
41
+ async getStatus(input, tenantId) {
42
+ const url = `${this.baseUrl}/private/devices/status?tenantId=${encodeURIComponent(tenantId)}`;
43
+ return await this.httpRequest.post(url, input);
44
+ }
45
+ async getLastSeen(input, tenantId) {
46
+ const url = `${this.baseUrl}/private/devices/last-seen?tenantId=${encodeURIComponent(tenantId)}`;
47
+ return await this.httpRequest.post(url, input);
48
+ }
49
+ async manualLock(input, tenantId) {
50
+ const url = `${this.baseUrl}/private/devices/manual-lock?tenantId=${encodeURIComponent(tenantId)}`;
51
+ return await this.httpRequest.post(url, input);
52
+ }
53
+ async manualUnlock(input, tenantId) {
54
+ const url = `${this.baseUrl}/private/devices/manual-unlock?tenantId=${encodeURIComponent(tenantId)}`;
55
+ return await this.httpRequest.post(url, input);
56
+ }
57
+ };
58
+ LoanCreditDeviceApi = __decorate([
59
+ injectable(),
60
+ __param(0, inject("IHttpRequest")),
61
+ __metadata("design:paramtypes", [Object])
62
+ ], LoanCreditDeviceApi);
63
+ export default LoanCreditDeviceApi;
@@ -0,0 +1,57 @@
1
+ import { StandardResponse } from "@fiado/gateway-adapter";
2
+ import { LoanDeviceEnrollRequest, LoanDeviceEnrollResponse, LoanDeviceEnrollStatusRequest, LoanDeviceEnrollStatusResponse, LoanDeviceLastSeenRequest, LoanDeviceLastSeenResponse, LoanDeviceManualLockRequest, LoanDeviceManualLockResponse, LoanDeviceManualUnlockRequest, LoanDeviceManualUnlockResponse, LoanDeviceStatusRequest, LoanDeviceStatusResponse } from "@fiado/type-kit/bin/loanCredit/index.js";
3
+ /**
4
+ * Contrato del publisher de dispositivo/MDM de `loan-credit-business` (componente 09 SureKeep F2).
5
+ * Endpoints PRIVADOS (VPC) que envuelven al motor MDM: enrolamiento del equipo, consulta de estado
6
+ * y bloqueo/desbloqueo manual. El contrato de crédito vive en `ILoanCreditBusinessApi`.
7
+ *
8
+ * Convenciones (idénticas a `ILoanCreditBusinessApi`):
9
+ * - `tenantId` OBLIGATORIO en todos los métodos — viaja por query `?tenantId=` (convención de los
10
+ * privados Fiado). Sin él el lambda responde `400 UNKNOWN_TENANT` (fail-closed, nunca default).
11
+ * - Retorna `StandardResponse<T>` → leer `result.data` (NO `result.body`).
12
+ * - Errores de transporte (falla el lambda entero, no un ítem): el publisher RECHAZA con el
13
+ * `DomainError` tipado del motor — `400 UNKNOWN_TENANT` / `400` de validación del body /
14
+ * `5xx` del motor. Nunca fallback: el caller jamás recibe un batch vacío disfrazado de éxito.
15
+ * - Idempotencia: las 3 mutaciones (`enroll`, `manualLock`, `manualUnlock`) son idempotentes por
16
+ * `input.operationReference` — es el idempotencyKey del caller. Reintentar el mismo lote con la
17
+ * misma referencia no vuelve a enrolar ni a bloquear; sin ella un retry tras timeout duplica.
18
+ *
19
+ * Granularidad POR ÍTEM — el batch NUNCA falla entero:
20
+ * - Cada `results[i]` trae su propio `status` (`SUCCESS` | `PENDING` | `ERROR`) y un `error`
21
+ * opcional. Un `StandardResponse` 200 puede traer ítems en `ERROR`: el caller TIENE que recorrer
22
+ * `results` y decidir por ítem — nunca asumir éxito global por el status HTTP.
23
+ * - El `error.code` del ítem es un `MdmErrorCodeEnum` (`PROVIDER_NOT_SUPPORTED`, `DEVICE_NOT_FOUND`,
24
+ * `PROVIDER_TIMEOUT`, …). El `status` del ítem NO lleva el motivo: el motivo vive solo en `error`.
25
+ * - `status: PENDING` = operación asíncrona en vuelo (típico de `enroll`). La resuelve el polling
26
+ * interno del motor; el caller la consulta después con `getEnrollStatus`.
27
+ *
28
+ * Env var requerida en el consumer:
29
+ *
30
+ * LOAN_CREDIT_BUSINESS_URL: '{{resolve:ssm:loan-credit-business}}'
31
+ */
32
+ export interface ILoanCreditDeviceApi {
33
+ /**
34
+ * Enrola el equipo del crédito en el MDM (paso 09 del wizard). Típicamente devuelve el ítem en
35
+ * `PENDING`: el enrolamiento es asíncrono y se confirma después con `getEnrollStatus`.
36
+ */
37
+ enroll(input: LoanDeviceEnrollRequest, tenantId: string): Promise<StandardResponse<LoanDeviceEnrollResponse>>;
38
+ /**
39
+ * Consulta en qué punto va el enrolamiento de cada crédito.
40
+ * Es POST y no GET porque el lote de `creditIds` puede traer miles de ids y no caben en la URL.
41
+ */
42
+ getEnrollStatus(input: LoanDeviceEnrollStatusRequest, tenantId: string): Promise<StandardResponse<LoanDeviceEnrollStatusResponse>>;
43
+ /**
44
+ * Estado actual del dispositivo de cada crédito (bloqueado, libre, sin enrolar).
45
+ * Es POST y no GET porque el lote de `creditIds` puede traer miles de ids y no caben en la URL.
46
+ */
47
+ getStatus(input: LoanDeviceStatusRequest, tenantId: string): Promise<StandardResponse<LoanDeviceStatusResponse>>;
48
+ /**
49
+ * Último contacto reportado por el dispositivo de cada crédito (señal de vida para cobranza).
50
+ * Es POST y no GET porque el lote de `creditIds` puede traer miles de ids y no caben en la URL.
51
+ */
52
+ getLastSeen(input: LoanDeviceLastSeenRequest, tenantId: string): Promise<StandardResponse<LoanDeviceLastSeenResponse>>;
53
+ /** Bloqueo manual del dispositivo disparado desde el backoffice (acción de un operador). */
54
+ manualLock(input: LoanDeviceManualLockRequest, tenantId: string): Promise<StandardResponse<LoanDeviceManualLockResponse>>;
55
+ /** Desbloqueo manual del dispositivo disparado desde el backoffice (acción de un operador). */
56
+ manualUnlock(input: LoanDeviceManualUnlockRequest, tenantId: string): Promise<StandardResponse<LoanDeviceManualUnlockResponse>>;
57
+ }
@@ -1,2 +1,4 @@
1
1
  export * from "./api/interfaces/ILoanCreditBusinessApi.js";
2
2
  export { default as LoanCreditBusinessApi } from "./api/LoanCreditBusinessApi.js";
3
+ export * from "./api/interfaces/ILoanCreditDeviceApi.js";
4
+ export { default as LoanCreditDeviceApi } from "./api/LoanCreditDeviceApi.js";
@@ -1,2 +1,4 @@
1
1
  export * from "./api/interfaces/ILoanCreditBusinessApi.js";
2
2
  export { default as LoanCreditBusinessApi } from "./api/LoanCreditBusinessApi.js";
3
+ export * from "./api/interfaces/ILoanCreditDeviceApi.js";
4
+ export { default as LoanCreditDeviceApi } from "./api/LoanCreditDeviceApi.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiado/api-invoker",
3
- "version": "5.34.0",
3
+ "version": "5.36.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.9.0",
35
35
  "@fiado/http-client": "^2.0.1",
36
36
  "@fiado/logger": "^1.1.3",
37
- "@fiado/type-kit": "^3.327.0",
37
+ "@fiado/type-kit": "^3.329.0",
38
38
  "dotenv": "^16.4.7"
39
39
  },
40
40
  "peerDependencies": {
@@ -0,0 +1,86 @@
1
+ import { inject, injectable } from "inversify";
2
+ import type { IHttpRequest } from "@fiado/http-client";
3
+ import { StandardResponse } from "@fiado/gateway-adapter";
4
+ import {
5
+ DeviceAutoLockScheduleRequest,
6
+ DeviceAutoLockScheduleResponse,
7
+ DeviceEnrollInitiateResponse,
8
+ DeviceEnrollRequest,
9
+ DeviceEnrollStatusRequest,
10
+ DeviceEnrollStatusResponse,
11
+ DeviceLastSeenRequest,
12
+ DeviceLastSeenResponse,
13
+ DeviceLockRequest,
14
+ DeviceLockResponse,
15
+ DeviceStatusRequest,
16
+ DeviceStatusResponse,
17
+ DeviceUnlockRequest,
18
+ DeviceUnlockResponse,
19
+ } from "@fiado/type-kit/bin/mdm/index.js";
20
+ import {
21
+ DeviceBatchRequest,
22
+ DeviceBatchResult,
23
+ IDatacultrConnectorApi,
24
+ } from "./interfaces/IDatacultrConnectorApi.js";
25
+
26
+ /**
27
+ * Publisher HTTP del lambda `datacultr-connector` (provider MDM de SureKeep). Contrato y semántica
28
+ * completos → `IDatacultrConnectorApi` (paths sin prefijo `/private`, sin tenant, `StandardResponse`
29
+ * → leer `result.data`, granularidad por ítem, y la limitación de `enrollDevices`).
30
+ *
31
+ * Env var requerida en el consumer: `DATACULTR_CONNECTOR_URL`.
32
+ * El template.yml del consumer la setea con:
33
+ *
34
+ * DATACULTR_CONNECTOR_URL: '{{resolve:ssm:datacultr-connector}}'
35
+ *
36
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
37
+ */
38
+ @injectable()
39
+ export default class DatacultrConnectorApi implements IDatacultrConnectorApi {
40
+ // El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//devices".
41
+ private readonly baseUrl = (process.env.DATACULTR_CONNECTOR_URL || "").replace(/\/+$/, "");
42
+
43
+ constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) {}
44
+
45
+ async enrollDevices(
46
+ input: DeviceBatchRequest<DeviceEnrollRequest>,
47
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollInitiateResponse>>> {
48
+ return await this.httpRequest.post(`${this.baseUrl}/devices/enroll`, input);
49
+ }
50
+
51
+ async enrollStatus(
52
+ input: DeviceBatchRequest<DeviceEnrollStatusRequest>,
53
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollStatusResponse>>> {
54
+ return await this.httpRequest.post(`${this.baseUrl}/devices/enroll/status`, input);
55
+ }
56
+
57
+ async getStatus(
58
+ input: DeviceBatchRequest<DeviceStatusRequest>,
59
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceStatusResponse>>> {
60
+ return await this.httpRequest.post(`${this.baseUrl}/devices/status`, input);
61
+ }
62
+
63
+ async getLastSeen(
64
+ input: DeviceBatchRequest<DeviceLastSeenRequest>,
65
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceLastSeenResponse>>> {
66
+ return await this.httpRequest.post(`${this.baseUrl}/devices/last-seen`, input);
67
+ }
68
+
69
+ async lockDevices(
70
+ input: DeviceBatchRequest<DeviceLockRequest>,
71
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceLockResponse>>> {
72
+ return await this.httpRequest.post(`${this.baseUrl}/devices/lock`, input);
73
+ }
74
+
75
+ async unlockDevices(
76
+ input: DeviceBatchRequest<DeviceUnlockRequest>,
77
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceUnlockResponse>>> {
78
+ return await this.httpRequest.post(`${this.baseUrl}/devices/unlock`, input);
79
+ }
80
+
81
+ async autoLockSchedule(
82
+ input: DeviceBatchRequest<DeviceAutoLockScheduleRequest>,
83
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceAutoLockScheduleResponse>>> {
84
+ return await this.httpRequest.post(`${this.baseUrl}/devices/auto-lock-schedule`, input);
85
+ }
86
+ }
@@ -0,0 +1,144 @@
1
+ import { StandardResponse } from "@fiado/gateway-adapter";
2
+ import {
3
+ DeviceAutoLockScheduleRequest,
4
+ DeviceAutoLockScheduleResponse,
5
+ DeviceEnrollInitiateResponse,
6
+ DeviceEnrollRequest,
7
+ DeviceEnrollStatusRequest,
8
+ DeviceEnrollStatusResponse,
9
+ DeviceLastSeenRequest,
10
+ DeviceLastSeenResponse,
11
+ DeviceLockRequest,
12
+ DeviceLockResponse,
13
+ DeviceStatusRequest,
14
+ DeviceStatusResponse,
15
+ DeviceUnlockRequest,
16
+ DeviceUnlockResponse,
17
+ } from "@fiado/type-kit/bin/mdm/index.js";
18
+
19
+ /**
20
+ * Contrato del publisher HTTP del lambda `datacultr-connector` — el provider MDM que traduce el
21
+ * contrato agnóstico de dispositivo (`@fiado/type-kit/bin/mdm`) a la API de Datacultr. Su consumidor
22
+ * es el módulo `device/` de `loan-credit-business` (`MdmConnectorClient`), que NUNCA le pega por
23
+ * `http-client` directo: entre lambdas Fiado se habla siempre por `@fiado/api-invoker`.
24
+ *
25
+ * ─────────────────────────────────────────────────────────────────────────────
26
+ * PATHS — `/devices/...`, SIN prefijo `/private`.
27
+ * ─────────────────────────────────────────────────────────────────────────────
28
+ * A diferencia de la mayoría de los privados Fiado (`/private/...`), el `PrivateController` de este
29
+ * connector declara los paths como `/devices/enroll`, `/devices/status`, etc. El aislamiento no lo da
30
+ * el prefijo sino la red: la API privada es `EndpointConfiguration: PRIVATE` con ResourcePolicy que
31
+ * deniega todo lo que no venga de los VPC Endpoints de Fiado.
32
+ *
33
+ * ─────────────────────────────────────────────────────────────────────────────
34
+ * SIN TENANT — hoy el connector es efectivamente single-tenant.
35
+ * ─────────────────────────────────────────────────────────────────────────────
36
+ * Ningún método lleva `tenantId` porque el destino NO lo resuelve: su `PrivateController` no lee
37
+ * `X-Tenant-Id` ni `x-tenant-issuer` (cero referencias en el repo). Las credenciales de Datacultr
38
+ * salen de un único secret del lambda. DEUDA a levantar cuando el connector implemente M6
39
+ * (multi-tenant del MDM): ahí este contrato suma el tenant y pasa a ser un cambio MAJOR.
40
+ *
41
+ * ─────────────────────────────────────────────────────────────────────────────
42
+ * AUTORIZACIÓN — `Feature.ANONIMUS`.
43
+ * ─────────────────────────────────────────────────────────────────────────────
44
+ * Los privados del connector son `Feature.ANONIMUS`: sin RBAC y sin claims. La única protección es
45
+ * la VPC, así que el caller no manda `Authorization` ni headers de identidad.
46
+ *
47
+ * ⚠️ Retorno `StandardResponse<T>` — el consumer lee `result.data`, NO `result.body`.
48
+ * Errores de transporte (falla el lambda entero, no un ítem): el publisher RECHAZA con el
49
+ * `DomainError` tipado del destino. Nunca fallback: el caller jamás recibe un batch vacío
50
+ * disfrazado de éxito.
51
+ *
52
+ * Granularidad POR ÍTEM — el batch NUNCA falla entero:
53
+ * - Cada `results[i]` trae su propio `status` (`SUCCESS` | `PENDING` | `ERROR`) y un `error` opcional.
54
+ * Un 200 puede traer ítems en `ERROR`: el caller TIENE que recorrer `results` y decidir por ítem.
55
+ * - El `error.code` del ítem es un `MdmErrorCodeEnum` (`PROVIDER_NOT_SUPPORTED`, `DEVICE_NOT_FOUND`,
56
+ * `PROVIDER_TIMEOUT`, …). El `status` no lleva el motivo: el motivo vive solo en `error`.
57
+ *
58
+ * Env var requerida en el consumer: `DATACULTR_CONNECTOR_URL`.
59
+ * El template.yml del consumer la setea con:
60
+ *
61
+ * DATACULTR_CONNECTOR_URL: '{{resolve:ssm:datacultr-connector}}'
62
+ *
63
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
64
+ */
65
+
66
+ /**
67
+ * Sobre de entrada de los endpoints batch del connector. No vive en `@fiado/type-kit/bin/mdm`
68
+ * porque los DTOs publicados son de UN imei; el sobre es dominio del líder (gap E8), migrar a
69
+ * type-kit cuando lo publique.
70
+ */
71
+ export interface DeviceBatchRequest<TItem> {
72
+ items: TItem[];
73
+ }
74
+
75
+ /** Sobre de salida de los endpoints batch: viaja dentro de `StandardResponse.data`. Mismo gap E8. */
76
+ export interface DeviceBatchResult<TItem> {
77
+ results: TItem[];
78
+ }
79
+
80
+ export interface IDatacultrConnectorApi {
81
+ /**
82
+ * POST `/devices/enroll` — pre-registra la expectativa de enrolamiento de cada IMEI.
83
+ *
84
+ * ⚠️ NO TOCA AL PROVEEDOR. Textual del openapi del destino: "Pre-registrar expectativa de
85
+ * enrollment (no toca provider) — DC no acepta enrollment outbound; el webhook inbound (B10) es
86
+ * quien actualiza la row a SUCCESS". Solo persiste una row `PENDING` en `PendingOperation_GT`.
87
+ *
88
+ * ⚠️ Y Datacultr confirmó por correo (2026-08-18) que NO MANDA WEBHOOKS. Sumando las dos: hoy un
89
+ * enrolamiento iniciado por esta vía queda `PENDING` PARA SIEMPRE. El caller NO debe tratar el
90
+ * `PENDING` como "en vuelo, ya se va a resolver" — no hay quien lo resuelva mientras el webhook
91
+ * inbound no exista. El enrolamiento real se hace hoy fuera de banda, en la consola de Datacultr.
92
+ */
93
+ enrollDevices(
94
+ input: DeviceBatchRequest<DeviceEnrollRequest>,
95
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollInitiateResponse>>>;
96
+
97
+ /**
98
+ * POST `/devices/enroll/status` — estado del enrolamiento de cada IMEI. Lee DDB, no toca al
99
+ * proveedor.
100
+ *
101
+ * ⚠️ Espeja la limitación de `enrollDevices`: como nadie actualiza la row (Datacultr no manda
102
+ * webhooks), este método devuelve `PENDING` indefinidamente para todo lo iniciado por esa vía.
103
+ * Un `PENDING` acá NO es señal de progreso.
104
+ */
105
+ enrollStatus(
106
+ input: DeviceBatchRequest<DeviceEnrollStatusRequest>,
107
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceEnrollStatusResponse>>>;
108
+
109
+ /**
110
+ * POST `/devices/status` — estado actual de cada IMEI (bloqueado, libre, sin enrolar).
111
+ * Si el batch trae un solo ítem, el destino cae a Registration Status y enriquece brand/model.
112
+ */
113
+ getStatus(
114
+ input: DeviceBatchRequest<DeviceStatusRequest>,
115
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceStatusResponse>>>;
116
+
117
+ /** POST `/devices/last-seen` — último contacto reportado por cada IMEI (señal de vida). */
118
+ getLastSeen(
119
+ input: DeviceBatchRequest<DeviceLastSeenRequest>,
120
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceLastSeenResponse>>>;
121
+
122
+ /**
123
+ * POST `/devices/lock` — bloquea cada IMEI.
124
+ * Los ítems con `mode: TEMPORARY` se degradan por ítem con `PROVIDER_NOT_SUPPORTED` sin llegar al
125
+ * proveedor: Datacultr solo soporta el bloqueo firme.
126
+ */
127
+ lockDevices(
128
+ input: DeviceBatchRequest<DeviceLockRequest>,
129
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceLockResponse>>>;
130
+
131
+ /** POST `/devices/unlock` — desbloquea cada IMEI. */
132
+ unlockDevices(
133
+ input: DeviceBatchRequest<DeviceUnlockRequest>,
134
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceUnlockResponse>>>;
135
+
136
+ /**
137
+ * POST `/devices/auto-lock-schedule` — programa el bloqueo de cada IMEI en una fecha futura.
138
+ * `lockAt` es ISO8601 UTC y el destino valida `lockAt > now` antes de llamar al proveedor.
139
+ * Es la vía real del bloqueo en Datacultr: el inmediato no surte efecto, el programado sí.
140
+ */
141
+ autoLockSchedule(
142
+ input: DeviceBatchRequest<DeviceAutoLockScheduleRequest>,
143
+ ): Promise<StandardResponse<DeviceBatchResult<DeviceAutoLockScheduleResponse>>>;
144
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./api/interfaces/IDatacultrConnectorApi.js";
2
+ export { default as DatacultrConnectorApi } from "./api/DatacultrConnectorApi.js";
package/src/index.ts CHANGED
@@ -110,3 +110,4 @@ export * from "./kyc/index.js";
110
110
  export * from "./central-payments-webhook/index.js";
111
111
  export * from "./collection-engine/index.js";
112
112
  export * from "./domain-callback/index.js";
113
+ export * from "./datacultrConnector/index.js";
@@ -0,0 +1,86 @@
1
+ import { inject, injectable } from "inversify";
2
+ import type { IHttpRequest } from "@fiado/http-client";
3
+ import { StandardResponse } from "@fiado/gateway-adapter";
4
+ import {
5
+ LoanDeviceEnrollRequest,
6
+ LoanDeviceEnrollResponse,
7
+ LoanDeviceEnrollStatusRequest,
8
+ LoanDeviceEnrollStatusResponse,
9
+ LoanDeviceLastSeenRequest,
10
+ LoanDeviceLastSeenResponse,
11
+ LoanDeviceManualLockRequest,
12
+ LoanDeviceManualLockResponse,
13
+ LoanDeviceManualUnlockRequest,
14
+ LoanDeviceManualUnlockResponse,
15
+ LoanDeviceStatusRequest,
16
+ LoanDeviceStatusResponse,
17
+ } from "@fiado/type-kit/bin/loanCredit/index.js";
18
+ import { ILoanCreditDeviceApi } from "./interfaces/ILoanCreditDeviceApi.js";
19
+
20
+ /**
21
+ * Publisher HTTP de los endpoints de dispositivo/MDM del lambda `loan-credit-business`
22
+ * (componente 09 SureKeep F2). Contrato y semántica completos → `ILoanCreditDeviceApi`
23
+ * (tenantId obligatorio, `StandardResponse` → leer `result.data`, granularidad por ítem).
24
+ *
25
+ * Env var requerida en el consumer: `LOAN_CREDIT_BUSINESS_URL`.
26
+ * El template.yml del consumer la setea con:
27
+ *
28
+ * LOAN_CREDIT_BUSINESS_URL: '{{resolve:ssm:loan-credit-business}}'
29
+ *
30
+ * Convención CLAUDE.md global: SSM key = nombre del lambda owner de la URL.
31
+ */
32
+ @injectable()
33
+ export default class LoanCreditDeviceApi implements ILoanCreditDeviceApi {
34
+ // El buildspec publica la URL en SSM con "/" final — se normaliza para no armar "//private".
35
+ private readonly baseUrl = (process.env.LOAN_CREDIT_BUSINESS_URL || "").replace(/\/+$/, "");
36
+
37
+ constructor(@inject("IHttpRequest") private httpRequest: IHttpRequest) {}
38
+
39
+ async enroll(
40
+ input: LoanDeviceEnrollRequest,
41
+ tenantId: string,
42
+ ): Promise<StandardResponse<LoanDeviceEnrollResponse>> {
43
+ const url = `${this.baseUrl}/private/devices/enroll?tenantId=${encodeURIComponent(tenantId)}`;
44
+ return await this.httpRequest.post(url, input);
45
+ }
46
+
47
+ async getEnrollStatus(
48
+ input: LoanDeviceEnrollStatusRequest,
49
+ tenantId: string,
50
+ ): Promise<StandardResponse<LoanDeviceEnrollStatusResponse>> {
51
+ const url = `${this.baseUrl}/private/devices/enroll-status?tenantId=${encodeURIComponent(tenantId)}`;
52
+ return await this.httpRequest.post(url, input);
53
+ }
54
+
55
+ async getStatus(
56
+ input: LoanDeviceStatusRequest,
57
+ tenantId: string,
58
+ ): Promise<StandardResponse<LoanDeviceStatusResponse>> {
59
+ const url = `${this.baseUrl}/private/devices/status?tenantId=${encodeURIComponent(tenantId)}`;
60
+ return await this.httpRequest.post(url, input);
61
+ }
62
+
63
+ async getLastSeen(
64
+ input: LoanDeviceLastSeenRequest,
65
+ tenantId: string,
66
+ ): Promise<StandardResponse<LoanDeviceLastSeenResponse>> {
67
+ const url = `${this.baseUrl}/private/devices/last-seen?tenantId=${encodeURIComponent(tenantId)}`;
68
+ return await this.httpRequest.post(url, input);
69
+ }
70
+
71
+ async manualLock(
72
+ input: LoanDeviceManualLockRequest,
73
+ tenantId: string,
74
+ ): Promise<StandardResponse<LoanDeviceManualLockResponse>> {
75
+ const url = `${this.baseUrl}/private/devices/manual-lock?tenantId=${encodeURIComponent(tenantId)}`;
76
+ return await this.httpRequest.post(url, input);
77
+ }
78
+
79
+ async manualUnlock(
80
+ input: LoanDeviceManualUnlockRequest,
81
+ tenantId: string,
82
+ ): Promise<StandardResponse<LoanDeviceManualUnlockResponse>> {
83
+ const url = `${this.baseUrl}/private/devices/manual-unlock?tenantId=${encodeURIComponent(tenantId)}`;
84
+ return await this.httpRequest.post(url, input);
85
+ }
86
+ }
@@ -0,0 +1,94 @@
1
+ import { StandardResponse } from "@fiado/gateway-adapter";
2
+ import {
3
+ LoanDeviceEnrollRequest,
4
+ LoanDeviceEnrollResponse,
5
+ LoanDeviceEnrollStatusRequest,
6
+ LoanDeviceEnrollStatusResponse,
7
+ LoanDeviceLastSeenRequest,
8
+ LoanDeviceLastSeenResponse,
9
+ LoanDeviceManualLockRequest,
10
+ LoanDeviceManualLockResponse,
11
+ LoanDeviceManualUnlockRequest,
12
+ LoanDeviceManualUnlockResponse,
13
+ LoanDeviceStatusRequest,
14
+ LoanDeviceStatusResponse,
15
+ } from "@fiado/type-kit/bin/loanCredit/index.js";
16
+
17
+ /**
18
+ * Contrato del publisher de dispositivo/MDM de `loan-credit-business` (componente 09 SureKeep F2).
19
+ * Endpoints PRIVADOS (VPC) que envuelven al motor MDM: enrolamiento del equipo, consulta de estado
20
+ * y bloqueo/desbloqueo manual. El contrato de crédito vive en `ILoanCreditBusinessApi`.
21
+ *
22
+ * Convenciones (idénticas a `ILoanCreditBusinessApi`):
23
+ * - `tenantId` OBLIGATORIO en todos los métodos — viaja por query `?tenantId=` (convención de los
24
+ * privados Fiado). Sin él el lambda responde `400 UNKNOWN_TENANT` (fail-closed, nunca default).
25
+ * - Retorna `StandardResponse<T>` → leer `result.data` (NO `result.body`).
26
+ * - Errores de transporte (falla el lambda entero, no un ítem): el publisher RECHAZA con el
27
+ * `DomainError` tipado del motor — `400 UNKNOWN_TENANT` / `400` de validación del body /
28
+ * `5xx` del motor. Nunca fallback: el caller jamás recibe un batch vacío disfrazado de éxito.
29
+ * - Idempotencia: las 3 mutaciones (`enroll`, `manualLock`, `manualUnlock`) son idempotentes por
30
+ * `input.operationReference` — es el idempotencyKey del caller. Reintentar el mismo lote con la
31
+ * misma referencia no vuelve a enrolar ni a bloquear; sin ella un retry tras timeout duplica.
32
+ *
33
+ * Granularidad POR ÍTEM — el batch NUNCA falla entero:
34
+ * - Cada `results[i]` trae su propio `status` (`SUCCESS` | `PENDING` | `ERROR`) y un `error`
35
+ * opcional. Un `StandardResponse` 200 puede traer ítems en `ERROR`: el caller TIENE que recorrer
36
+ * `results` y decidir por ítem — nunca asumir éxito global por el status HTTP.
37
+ * - El `error.code` del ítem es un `MdmErrorCodeEnum` (`PROVIDER_NOT_SUPPORTED`, `DEVICE_NOT_FOUND`,
38
+ * `PROVIDER_TIMEOUT`, …). El `status` del ítem NO lleva el motivo: el motivo vive solo en `error`.
39
+ * - `status: PENDING` = operación asíncrona en vuelo (típico de `enroll`). La resuelve el polling
40
+ * interno del motor; el caller la consulta después con `getEnrollStatus`.
41
+ *
42
+ * Env var requerida en el consumer:
43
+ *
44
+ * LOAN_CREDIT_BUSINESS_URL: '{{resolve:ssm:loan-credit-business}}'
45
+ */
46
+ export interface ILoanCreditDeviceApi {
47
+ /**
48
+ * Enrola el equipo del crédito en el MDM (paso 09 del wizard). Típicamente devuelve el ítem en
49
+ * `PENDING`: el enrolamiento es asíncrono y se confirma después con `getEnrollStatus`.
50
+ */
51
+ enroll(
52
+ input: LoanDeviceEnrollRequest,
53
+ tenantId: string,
54
+ ): Promise<StandardResponse<LoanDeviceEnrollResponse>>;
55
+
56
+ /**
57
+ * Consulta en qué punto va el enrolamiento de cada crédito.
58
+ * Es POST y no GET porque el lote de `creditIds` puede traer miles de ids y no caben en la URL.
59
+ */
60
+ getEnrollStatus(
61
+ input: LoanDeviceEnrollStatusRequest,
62
+ tenantId: string,
63
+ ): Promise<StandardResponse<LoanDeviceEnrollStatusResponse>>;
64
+
65
+ /**
66
+ * Estado actual del dispositivo de cada crédito (bloqueado, libre, sin enrolar).
67
+ * Es POST y no GET porque el lote de `creditIds` puede traer miles de ids y no caben en la URL.
68
+ */
69
+ getStatus(
70
+ input: LoanDeviceStatusRequest,
71
+ tenantId: string,
72
+ ): Promise<StandardResponse<LoanDeviceStatusResponse>>;
73
+
74
+ /**
75
+ * Último contacto reportado por el dispositivo de cada crédito (señal de vida para cobranza).
76
+ * Es POST y no GET porque el lote de `creditIds` puede traer miles de ids y no caben en la URL.
77
+ */
78
+ getLastSeen(
79
+ input: LoanDeviceLastSeenRequest,
80
+ tenantId: string,
81
+ ): Promise<StandardResponse<LoanDeviceLastSeenResponse>>;
82
+
83
+ /** Bloqueo manual del dispositivo disparado desde el backoffice (acción de un operador). */
84
+ manualLock(
85
+ input: LoanDeviceManualLockRequest,
86
+ tenantId: string,
87
+ ): Promise<StandardResponse<LoanDeviceManualLockResponse>>;
88
+
89
+ /** Desbloqueo manual del dispositivo disparado desde el backoffice (acción de un operador). */
90
+ manualUnlock(
91
+ input: LoanDeviceManualUnlockRequest,
92
+ tenantId: string,
93
+ ): Promise<StandardResponse<LoanDeviceManualUnlockResponse>>;
94
+ }
@@ -1,2 +1,5 @@
1
1
  export * from "./api/interfaces/ILoanCreditBusinessApi.js";
2
2
  export { default as LoanCreditBusinessApi } from "./api/LoanCreditBusinessApi.js";
3
+
4
+ export * from "./api/interfaces/ILoanCreditDeviceApi.js";
5
+ export { default as LoanCreditDeviceApi } from "./api/LoanCreditDeviceApi.js";