apacuana-sdk-core 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +131 -164
  2. package/coverage/clover.xml +386 -296
  3. package/coverage/coverage-final.json +7 -7
  4. package/coverage/lcov-report/index.html +38 -38
  5. package/coverage/lcov-report/src/api/certs.js.html +14 -8
  6. package/coverage/lcov-report/src/api/faceLiveness.js.html +1 -1
  7. package/coverage/lcov-report/src/api/index.html +31 -31
  8. package/coverage/lcov-report/src/api/revocations.js.html +2 -2
  9. package/coverage/lcov-report/src/api/signatures.js.html +17 -56
  10. package/coverage/lcov-report/src/api/users.js.html +160 -13
  11. package/coverage/lcov-report/src/config/index.html +1 -1
  12. package/coverage/lcov-report/src/config/index.js.html +1 -1
  13. package/coverage/lcov-report/src/errors/index.html +1 -1
  14. package/coverage/lcov-report/src/errors/index.js.html +1 -1
  15. package/coverage/lcov-report/src/index.html +21 -21
  16. package/coverage/lcov-report/src/index.js.html +267 -63
  17. package/coverage/lcov-report/src/success/index.html +1 -1
  18. package/coverage/lcov-report/src/success/index.js.html +1 -1
  19. package/coverage/lcov-report/src/utils/constant.js.html +115 -4
  20. package/coverage/lcov-report/src/utils/helpers.js.html +286 -22
  21. package/coverage/lcov-report/src/utils/httpClient.js.html +32 -20
  22. package/coverage/lcov-report/src/utils/index.html +24 -24
  23. package/coverage/lcov.info +776 -586
  24. package/dist/api/users.d.ts +27 -21
  25. package/dist/index.d.ts +19 -42
  26. package/dist/index.js +357 -251
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +357 -251
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/types/certs.d.ts +20 -34
  31. package/dist/types/signatures.d.ts +19 -4
  32. package/dist/utils/constant.d.ts +31 -0
  33. package/dist/utils/helpers.d.ts +5 -3
  34. package/package.json +1 -1
  35. package/src/api/certs.js +4 -2
  36. package/src/api/revocations.js +1 -1
  37. package/src/api/signatures.js +8 -21
  38. package/src/api/users.js +51 -2
  39. package/src/index.js +113 -45
  40. package/src/types/certs.js +36 -25
  41. package/src/types/signatures.js +6 -1
  42. package/src/utils/constant.js +37 -0
  43. package/src/utils/helpers.js +100 -12
  44. package/src/utils/httpClient.js +16 -12
  45. package/tests/api/certs.test.js +11 -3
  46. package/tests/api/revocations.test.js +1 -1
  47. package/tests/api/signatures.test.js +73 -16
  48. package/tests/api/users.test.js +1 -1
  49. package/tests/index.test.js +3 -2
@@ -9,6 +9,43 @@ export const STATUS_CERTIFICATE = {
9
9
  sign_pending: "PENDIENTE_POR_FIRMAR",
10
10
  };
11
11
 
12
+ export const PARSE_STATUS = {
13
+ [STATUS_CERTIFICATE.certificate_another_device]: {
14
+ text: "Verificado",
15
+ descriptionText: undefined,
16
+ },
17
+ [STATUS_CERTIFICATE.request_cert]: {
18
+ text: "En revisión",
19
+ descriptionText:
20
+ "La información y documentos enviados están siendo certificados por la Autoridad de Certificación para confirmar su validez.",
21
+ },
22
+ [STATUS_CERTIFICATE.request]: {
23
+ text: "Por verificar",
24
+ descriptionText:
25
+ "La información y documentos enviados están siendo revisados por la Autoridad de Registro.",
26
+ },
27
+ [STATUS_CERTIFICATE.generate]: {
28
+ text: "Por generar",
29
+ descriptionText:
30
+ "Tu solicitud fue aprobada. Solo falta que generes tu certificado para comenzar a usarlo.",
31
+ },
32
+ [STATUS_CERTIFICATE.current]: {
33
+ text: " ",
34
+ descriptionText: undefined,
35
+ },
36
+ [STATUS_CERTIFICATE.expire]: "Certificado expirado.",
37
+ [STATUS_CERTIFICATE.request_revoque]: {
38
+ text: "Por revocar",
39
+ descriptionText:
40
+ "Recibimos tu solicitud de revocación y estamos procesándola. El certificado dejará de ser válido una vez finalice el trámite.",
41
+ },
42
+ [STATUS_CERTIFICATE.revoque]: {
43
+ text: "Revocado",
44
+ descriptionText:
45
+ "Este certificado ha sido revocado. Debes solicitar uno nuevo.",
46
+ },
47
+ };
48
+
12
49
  export const VERIFICATION_STATUS = {
13
50
  IN_REGISTER: 0,
14
51
  APPROVED: 1,
@@ -17,6 +17,11 @@ const getCertificateStatus = (userData, certificateInDevice) => {
17
17
 
18
18
  if (userData?.verificationstatus?.id !== VERIFICATION_STATUS.APPROVED)
19
19
  return STATUS_CERTIFICATE.request;
20
+ if (
21
+ !userData?.certificationId &&
22
+ userData?.requestscert?.requests.length === 0
23
+ )
24
+ return STATUS_CERTIFICATE.request_cert;
20
25
  if (
21
26
  userData?.certificationId &&
22
27
  !userData?.cangenerate &&
@@ -25,7 +30,6 @@ const getCertificateStatus = (userData, certificateInDevice) => {
25
30
  userData?.certificatestatus !== STATUS_CERTIFICATE.revoque
26
31
  )
27
32
  return STATUS_CERTIFICATE.certificate_another_device;
28
-
29
33
  if (userData?.requestscert?.pending && userData?.certificationId)
30
34
  return STATUS_CERTIFICATE.request_revoque;
31
35
  if (
@@ -33,10 +37,7 @@ const getCertificateStatus = (userData, certificateInDevice) => {
33
37
  userData?.certificatestatus === STATUS_CERTIFICATE.revoque
34
38
  )
35
39
  return STATUS_CERTIFICATE.revoque;
36
-
37
- if (!userData?.approvedUser) return STATUS_CERTIFICATE.request;
38
40
  if (userData?.cangenerate) return STATUS_CERTIFICATE.generate;
39
-
40
41
  if (userData?.certificationId) return STATUS_CERTIFICATE.current;
41
42
 
42
43
  return STATUS_CERTIFICATE.revoque;
@@ -65,8 +66,31 @@ const encryptedCsr = (csr, encryptKey = KEY_HEX) => {
65
66
  return body;
66
67
  };
67
68
 
69
+ const createPayloadGetDigest = (signData) => {
70
+ const formData = new FormData();
71
+
72
+ // Iterate over the object's own properties
73
+ Object.keys(signData).forEach((key) => {
74
+ // Conditionally handle the 'document' field
75
+
76
+ if (key === "document" && signData[key] instanceof File) {
77
+ // Append the file directly
78
+ formData.append(key, signData[key]);
79
+ } else if (key === "cert") {
80
+ formData.append("publickey", signData[key]);
81
+ }
82
+ });
83
+ return formData;
84
+ };
85
+
68
86
  const validateOnBoardingSignerData = (signerData) => {
69
- if (!signerData.name || typeof signerData.name !== "string") {
87
+ // Create a copy of the original object to avoid modifying the function parameter
88
+ const validatedSignerData = { ...signerData };
89
+
90
+ if (
91
+ !validatedSignerData.name ||
92
+ typeof validatedSignerData.name !== "string"
93
+ ) {
70
94
  throw new ApacuanaAPIError(
71
95
  'El campo "name" es requerido y debe ser una cadena de texto.',
72
96
  400,
@@ -74,16 +98,46 @@ const validateOnBoardingSignerData = (signerData) => {
74
98
  );
75
99
  }
76
100
 
77
- if (!signerData.reference || typeof signerData.reference !== "string") {
101
+ // Limpia 'reference' si es inválido o está vacío.
102
+ if (
103
+ !validatedSignerData.reference ||
104
+ typeof validatedSignerData.reference !== "string" ||
105
+ validatedSignerData.reference.trim() === ""
106
+ ) {
107
+ delete validatedSignerData.reference;
108
+ }
109
+
110
+ // Limpia 'file' si no es una instancia de File.
111
+ if (
112
+ typeof File === "undefined" ||
113
+ !validatedSignerData.file ||
114
+ !(validatedSignerData.file instanceof File)
115
+ ) {
116
+ delete validatedSignerData.file;
117
+ }
118
+
119
+ // Valida que solo uno de los dos campos exista después de la limpieza.
120
+ if (validatedSignerData.reference && validatedSignerData.file) {
121
+ throw new ApacuanaAPIError(
122
+ "No se pueden proporcionar 'reference' y 'file' simultáneamente.",
123
+ 400,
124
+ "INVALID_PARAMETER_FORMAT"
125
+ );
126
+ }
127
+
128
+ if (!validatedSignerData.reference && !validatedSignerData.file) {
78
129
  throw new ApacuanaAPIError(
79
- 'El campo "reference" es requerido y debe ser una cadena de texto.',
130
+ "Se debe proporcionar un campo 'reference' o 'file' válido.",
80
131
  400,
81
132
  "INVALID_PARAMETER_FORMAT"
82
133
  );
83
134
  }
84
135
 
85
136
  const validDocTypes = ["V", "P", "E"];
86
- if (!signerData.typedoc || !validDocTypes.includes(signerData.typedoc)) {
137
+ if (
138
+ !validatedSignerData.typedoc ||
139
+ !validDocTypes.includes(validatedSignerData.typedoc)
140
+ ) {
87
141
  throw new ApacuanaAPIError(
88
142
  'El campo "typedoc" es requerido y debe ser uno de los siguientes valores: V, P, E.',
89
143
  400,
@@ -91,7 +145,7 @@ const validateOnBoardingSignerData = (signerData) => {
91
145
  );
92
146
  }
93
147
 
94
- if (!signerData.doc || typeof signerData.doc !== "string") {
148
+ if (!validatedSignerData.doc || typeof validatedSignerData.doc !== "string") {
95
149
  throw new ApacuanaAPIError(
96
150
  'El campo "doc" es requerido y debe ser una cadena de texto.',
97
151
  400,
@@ -100,8 +154,8 @@ const validateOnBoardingSignerData = (signerData) => {
100
154
  }
101
155
 
102
156
  if (
103
- !Array.isArray(signerData.signature) ||
104
- signerData.signature.length === 0
157
+ !Array.isArray(validatedSignerData.signature) ||
158
+ validatedSignerData.signature.length === 0
105
159
  ) {
106
160
  throw new ApacuanaAPIError(
107
161
  'El campo "signature" es requerido y debe ser un array no vacío.',
@@ -110,7 +164,7 @@ const validateOnBoardingSignerData = (signerData) => {
110
164
  );
111
165
  }
112
166
 
113
- signerData.signature.forEach((sig, index) => {
167
+ validatedSignerData.signature.forEach((sig, index) => {
114
168
  if (!Number.isInteger(sig.page) || sig.page < 1) {
115
169
  throw new ApacuanaAPIError(
116
170
  `La página de la firma ${
@@ -141,6 +195,19 @@ const validateOnBoardingSignerData = (signerData) => {
141
195
  );
142
196
  }
143
197
  });
198
+
199
+ const formData = new FormData();
200
+ Object.keys(validatedSignerData).forEach((key) => {
201
+ if (key === "signature") {
202
+ formData.append(key, JSON.stringify(validatedSignerData[key]));
203
+ } else if (key === "file") {
204
+ formData.append("document", validatedSignerData[key]);
205
+ } else if (validatedSignerData[key] !== undefined) {
206
+ formData.append(key, validatedSignerData[key]);
207
+ }
208
+ });
209
+
210
+ return formData;
144
211
  };
145
212
 
146
213
  const validateCsr = (encryptedCSR) => {
@@ -243,6 +310,26 @@ const validateOnBoardingSignDocumentData = (signData) => {
243
310
  "INVALID_PARAMETER"
244
311
  );
245
312
  }
313
+
314
+ const formData = new FormData();
315
+ formData.append(
316
+ "positions",
317
+ JSON.stringify(
318
+ signature.positions.map((p) => ({
319
+ x: p.x,
320
+ y: p.y,
321
+ page: p.page,
322
+ status: 1,
323
+ }))
324
+ )
325
+ );
326
+ formData.append("publickey", cert);
327
+ formData.append("signeddigest", signedDigest);
328
+ if (signData.document && signData.document instanceof File) {
329
+ formData.append("document", signData.document);
330
+ }
331
+
332
+ return formData;
246
333
  };
247
334
 
248
335
  export default {
@@ -255,4 +342,5 @@ export default {
255
342
  validateGetDocsData,
256
343
  validateGetDigestData,
257
344
  validateOnBoardingSignDocumentData,
345
+ createPayloadGetDigest,
258
346
  };
@@ -16,8 +16,7 @@ export const initHttpClient = () => {
16
16
  !config.apiUrl ||
17
17
  !config.secretKey ||
18
18
  !config.apiKey ||
19
- !config.verificationId ||
20
- !config.customerId
19
+ !config.verificationId
21
20
  ) {
22
21
  throw new Error(
23
22
  "HttpClient: Configuración de inicialización incompleta. Llama a apacuana.init() primero."
@@ -129,18 +128,23 @@ export const httpRequest = async (path, data = {}, method = "POST") => {
129
128
  let dataToSend;
130
129
  const requestConfig = {};
131
130
 
132
- const hasFile = Object.values(data).some(
133
- (value) => typeof File !== "undefined" && value instanceof File
134
- );
135
- if (hasFile) {
136
- const formData = new FormData();
137
- Object.keys(data).forEach((key) => {
138
- formData.append(key, data[key]);
139
- });
140
- dataToSend = formData;
131
+ if (typeof FormData !== "undefined" && data instanceof FormData) {
132
+ dataToSend = data;
141
133
  } else {
142
- dataToSend = { ...data };
134
+ const hasFile = Object.values(data).some(
135
+ (value) => typeof File !== "undefined" && value instanceof File
136
+ );
137
+ if (hasFile) {
138
+ const formData = new FormData();
139
+ Object.keys(data).forEach((key) => {
140
+ formData.append(key, data[key]);
141
+ });
142
+ dataToSend = formData;
143
+ } else {
144
+ dataToSend = { ...data };
145
+ }
143
146
  }
147
+
144
148
  try {
145
149
  let response;
146
150
  switch (method.toUpperCase()) {
@@ -85,9 +85,9 @@ describe("Certificate API - certs.js", () => {
85
85
 
86
86
  describe("getCertStatus", () => {
87
87
  it("should return the certificate status from helpers", () => {
88
- const mockUserData = { certStatus: "VALID" };
88
+ const mockUserData = { certStatus: "SOLICITUD" };
89
89
  getConfig.mockReturnValue({ userData: mockUserData });
90
- helpers.getCertificateStatus.mockReturnValue("VALID");
90
+ helpers.getCertificateStatus.mockReturnValue("SOLICITUD");
91
91
 
92
92
  const result = getCertStatus(true);
93
93
 
@@ -96,7 +96,15 @@ describe("Certificate API - certs.js", () => {
96
96
  true
97
97
  );
98
98
  expect(result).toBeInstanceOf(ApacuanaSuccess);
99
- expect(result.data).toEqual({ status: "VALID" });
99
+
100
+ // --- CORRECCIÓN AQUÍ: El objeto esperado debe estar anidado en la clave 'status' ---
101
+ expect(result.data).toEqual({
102
+ status: {
103
+ text: "Por verificar",
104
+ descriptionText:
105
+ "La información y documentos enviados están siendo revisados por la Autoridad de Registro.",
106
+ },
107
+ });
100
108
  });
101
109
  });
102
110
 
@@ -36,7 +36,7 @@ describe("Revocations API", () => {
36
36
 
37
37
  expect(httpRequest).toHaveBeenCalledWith(
38
38
  "services/api/onboardingclient/requestcert",
39
- params,
39
+ { reason: params.reasonCode },
40
40
  "POST"
41
41
  );
42
42
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -24,9 +24,32 @@ jest.mock("../../src/utils/helpers", () => ({
24
24
  validateGetDocsData: jest.fn(),
25
25
  validateGetDigestData: jest.fn(),
26
26
  validateOnBoardingSignDocumentData: jest.fn(),
27
+ createPayloadGetDigest: jest.fn(),
27
28
  }));
28
29
 
29
30
  describe("API - Signatures", () => {
31
+ beforeAll(() => {
32
+ // FormData no existe en el entorno de Node.js de Jest.
33
+ // Creamos una simulación (mock) que incluye los métodos 'append' y 'forEach'.
34
+ global.FormData = class FormData {
35
+ constructor() {
36
+ this.data = new Map();
37
+ }
38
+ append(key, value) {
39
+ this.data.set(key, value);
40
+ }
41
+ forEach(callback) {
42
+ // Delegamos la llamada forEach al Map interno.
43
+ this.data.forEach((value, key) => callback(value, key));
44
+ }
45
+ };
46
+ });
47
+
48
+ afterAll(() => {
49
+ // Limpiamos la simulación global después de las pruebas.
50
+ delete global.FormData;
51
+ });
52
+
30
53
  afterEach(() => {
31
54
  jest.resetAllMocks();
32
55
  });
@@ -37,6 +60,12 @@ describe("API - Signatures", () => {
37
60
  integrationType: INTEGRATION_TYPE.ONBOARDING,
38
61
  });
39
62
  const signerData = { typedoc: "V", doc: "12345678" };
63
+
64
+ const formData = new FormData();
65
+ formData.append("typedoc", "V");
66
+ formData.append("doc", "12345678");
67
+
68
+ helpers.validateOnBoardingSignerData.mockReturnValue(formData);
40
69
  httpRequest.mockResolvedValue({ success: true });
41
70
 
42
71
  await addSigner(signerData);
@@ -45,8 +74,8 @@ describe("API - Signatures", () => {
45
74
  signerData
46
75
  );
47
76
  expect(httpRequest).toHaveBeenCalledWith(
48
- "services/api/documents/signing",
49
- signerData,
77
+ "services/api/documents/signingsdk",
78
+ formData,
50
79
  "POST"
51
80
  );
52
81
  });
@@ -177,12 +206,17 @@ describe("API - Signatures", () => {
177
206
  const mockResponse = { data: { digest: "test-digest" } };
178
207
  httpRequest.mockResolvedValue(mockResponse);
179
208
 
209
+ const expectedFormData = new FormData();
210
+ expectedFormData.append("publickey", "test-cert");
211
+ helpers.createPayloadGetDigest.mockReturnValue(expectedFormData);
212
+
180
213
  const result = await getDigest(signData);
181
214
 
182
215
  expect(helpers.validateGetDigestData).toHaveBeenCalledWith(signData);
216
+ expect(helpers.createPayloadGetDigest).toHaveBeenCalledWith(signData);
183
217
  expect(httpRequest).toHaveBeenCalledWith(
184
218
  `services/api/documents/getdigest/${signData.signatureId}`,
185
- { publickey: signData.cert },
219
+ expectedFormData,
186
220
  "POST"
187
221
  );
188
222
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -243,20 +277,33 @@ describe("API - Signatures", () => {
243
277
  const mockResponse = { success: true };
244
278
  httpRequest.mockResolvedValue(mockResponse);
245
279
 
280
+ const expectedFormData = new FormData();
281
+ expectedFormData.append(
282
+ "positions",
283
+ JSON.stringify(
284
+ signData.signature.positions.map((p) => ({
285
+ x: p.x,
286
+ y: p.y,
287
+ page: p.page,
288
+ status: 1,
289
+ }))
290
+ )
291
+ );
292
+ expectedFormData.append("publickey", signData.cert);
293
+ expectedFormData.append("signeddigest", signData.signedDigest);
294
+
295
+ helpers.validateOnBoardingSignDocumentData.mockReturnValue(
296
+ expectedFormData
297
+ );
298
+
246
299
  const result = await signDocument(signData);
247
300
 
248
- expect(
249
- helpers.validateOnBoardingSignDocumentData
250
- ).toHaveBeenCalledWith(signData);
301
+ expect(helpers.validateOnBoardingSignDocumentData).toHaveBeenCalledWith(
302
+ signData
303
+ );
251
304
  expect(httpRequest).toHaveBeenCalledWith(
252
305
  `services/api/documents/sign/${signData.signature.id}`,
253
- {
254
- positions: JSON.stringify([
255
- { x: 0.1, y: 0.2, page: 1, status: 1 },
256
- ]),
257
- publickey: signData.cert,
258
- signeddigest: signData.signedDigest,
259
- },
306
+ expectedFormData,
260
307
  "PUT"
261
308
  );
262
309
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -306,10 +353,18 @@ describe("API - Signatures", () => {
306
353
 
307
354
  it("should throw an error if data or data.file is not provided", async () => {
308
355
  await expect(uploadSignatureVariant(null)).rejects.toThrow(
309
- new ApacuanaAPIError("El parámetro 'file' es requerido.", 400, "INVALID_PARAMETER")
356
+ new ApacuanaAPIError(
357
+ "El parámetro 'file' es requerido.",
358
+ 400,
359
+ "INVALID_PARAMETER"
360
+ )
310
361
  );
311
362
  await expect(uploadSignatureVariant({})).rejects.toThrow(
312
- new ApacuanaAPIError("El parámetro 'file' es requerido.", 400, "INVALID_PARAMETER")
363
+ new ApacuanaAPIError(
364
+ "El parámetro 'file' es requerido.",
365
+ 400,
366
+ "INVALID_PARAMETER"
367
+ )
313
368
  );
314
369
  });
315
370
 
@@ -325,7 +380,9 @@ describe("API - Signatures", () => {
325
380
 
326
381
  it("should throw an error if file is not a PNG", async () => {
327
382
  const invalidFile = new File([], "test.txt", { type: "text/plain" });
328
- await expect(uploadSignatureVariant({ file: invalidFile })).rejects.toThrow(
383
+ await expect(
384
+ uploadSignatureVariant({ file: invalidFile })
385
+ ).rejects.toThrow(
329
386
  new ApacuanaAPIError(
330
387
  "El archivo debe ser de tipo PNG (image/png).",
331
388
  400,
@@ -2,8 +2,8 @@
2
2
  import { httpRequest } from "../../src/utils/httpClient.js";
3
3
  import { getConfig } from "../../src/config/index.js";
4
4
  import { ApacuanaAPIError } from "../../src/errors/index.js";
5
- import getCustomer from "../../src/api/users.js";
6
5
  import ApacuanaSuccess from "../../src/success/index.js";
6
+ import { getCustomer } from "../../src/api/users.js";
7
7
 
8
8
  // Mockear las dependencias:
9
9
  jest.mock("../../src/utils/httpClient.js", () => ({
@@ -2,8 +2,8 @@
2
2
  import apacuana from "../src/index.js";
3
3
  import { setConfig, getConfig } from "../src/config/index.js";
4
4
  import { initHttpClient, setAuthToken } from "../src/utils/httpClient.js";
5
- import getCustomer from "../src/api/users.js";
6
5
  import ApacuanaSuccess from "../src/success/index.js";
6
+ import { getCustomer } from "../src/api/users.js";
7
7
 
8
8
  // Mockear todas las dependencias del index.js
9
9
  jest.mock("../src/config/index.js", () => ({
@@ -17,6 +17,7 @@ jest.mock("../src/utils/httpClient.js", () => ({
17
17
  jest.mock("../src/api/users.js", () => ({
18
18
  __esModule: true,
19
19
  default: jest.fn(),
20
+ getCustomer: jest.fn(),
20
21
  }));
21
22
 
22
23
  describe("Apacuana SDK Initialization (init)", () => {
@@ -71,7 +72,7 @@ describe("Apacuana SDK Initialization (init)", () => {
71
72
  expect(result).toBeInstanceOf(ApacuanaSuccess);
72
73
  expect(result.data).toEqual({
73
74
  initialized: true,
74
- message: "SDK inicializado correctamente.",
75
+ message: "SDK inicializado con sesión de usuario.",
75
76
  });
76
77
 
77
78
  // 5. Verificar que las funciones se llamaron el número correcto de veces.