@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.
- package/.github/workflows/npm-publish.yml +31 -0
- package/bin/datacultrConnector/api/DatacultrConnectorApi.d.ts +28 -0
- package/bin/datacultrConnector/api/DatacultrConnectorApi.js +60 -0
- package/bin/datacultrConnector/api/interfaces/IDatacultrConnectorApi.d.ts +105 -0
- package/bin/datacultrConnector/api/interfaces/IDatacultrConnectorApi.js +1 -0
- package/bin/datacultrConnector/index.d.ts +2 -0
- package/bin/datacultrConnector/index.js +2 -0
- package/bin/index.d.ts +1 -0
- package/bin/index.js +1 -0
- package/bin/loanCredit/api/LoanCreditDeviceApi.d.ts +27 -0
- package/bin/loanCredit/api/LoanCreditDeviceApi.js +63 -0
- package/bin/loanCredit/api/interfaces/ILoanCreditDeviceApi.d.ts +57 -0
- package/bin/loanCredit/api/interfaces/ILoanCreditDeviceApi.js +1 -0
- package/bin/loanCredit/index.d.ts +2 -0
- package/bin/loanCredit/index.js +2 -0
- package/package.json +2 -2
- package/src/datacultrConnector/api/DatacultrConnectorApi.ts +86 -0
- package/src/datacultrConnector/api/interfaces/IDatacultrConnectorApi.ts +144 -0
- package/src/datacultrConnector/index.ts +2 -0
- package/src/index.ts +1 -0
- package/src/loanCredit/api/LoanCreditDeviceApi.ts +86 -0
- package/src/loanCredit/api/interfaces/ILoanCreditDeviceApi.ts +94 -0
- package/src/loanCredit/index.ts +3 -0
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
package/bin/index.d.ts
CHANGED
package/bin/index.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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/bin/loanCredit/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/loanCredit/index.ts
CHANGED
|
@@ -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";
|