From e63da7c4e925d7a4d32c6b63efb0db59218b364e Mon Sep 17 00:00:00 2001 From: "raul@facturapi.io" Date: Thu, 5 Nov 2026 11:22:43 -0600 Subject: [PATCH] feat(organizations): add manifiesto ZIP download URL --- CHANGELOG.md | 1 + openapi/source.json | 2 +- scripts/sdk/resources.json | 6 +++ src/generated/dates.ts | 1 + src/generated/input.ts | 51 +++++++++++++++++++++ src/generated/output.ts | 51 +++++++++++++++++++++ src/resources/organizations.ts | 23 ++++++++++ test/node/signed-download-urls.node.test.ts | 29 ++++++++++++ 8 files changed, 163 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca85b75..a878a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- `organizations.downloadManifiestoZipUrl(id)` to obtain a temporary URL for downloading the signed Carta Manifiesto ZIP associated with the organization's certificate RFC. - Autocompletion and editor documentation for request fields, method arguments, and responses across SDK operations. - More precise input types for CFDI variants, drafts, structured complements, and customer creation. Related fields are checked by TypeScript in supported cases, helping catch incomplete or incompatible inputs before sending a request. - Invoice responses distinguish draft, pending, failed, and stamped states; checking the status exposes the guaranteed UUID and stamp fields. diff --git a/openapi/source.json b/openapi/source.json index 02568e4..1d6a842 100644 --- a/openapi/source.json +++ b/openapi/source.json @@ -1,5 +1,5 @@ { "repository": "FacturAPI/facturapi-docs", - "revision": "fbcd72e8cf2b1d1b38d3fd5889ecde6545434c61", + "revision": "6a81247386e6e9036c618e99c2e91c2b7e74ab77", "path": "website/openapi_v2.yaml" } diff --git a/scripts/sdk/resources.json b/scripts/sdk/resources.json index d5a7c8d..de2918f 100644 --- a/scripts/sdk/resources.json +++ b/scripts/sdk/resources.json @@ -211,6 +211,12 @@ ] }, "deleteCertificate": "deleteOrganizationCertificate", + "downloadManifiestoZipUrl": { + "operation": "getOrganizationManifiestoDownloadUrl", + "argumentNames": { + "organization_id": "id" + } + }, "del": "deleteOrganization", "getTestApiKey": "getTestApiKey", "renewTestApiKey": "renewTestApiKey", diff --git a/src/generated/dates.ts b/src/generated/dates.ts index da980e6..d6449b2 100644 --- a/src/generated/dates.ts +++ b/src/generated/dates.ts @@ -753,6 +753,7 @@ export const operationDatePlans = { meOrganization: 122, getOrganization: 122, deleteOrganization: 122, + getOrganizationManifiestoDownloadUrl: 37, editOrganizationLegal: 122, uploadOrganizationCertificate: 122, deleteOrganizationCertificate: 127, diff --git a/src/generated/input.ts b/src/generated/input.ts index 2d110c9..cb422e5 100644 --- a/src/generated/input.ts +++ b/src/generated/input.ts @@ -1362,6 +1362,28 @@ export interface paths { patch?: never; trace?: never; }; + '/organizations/{organization_id}/manifiesto/download-url': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Obtener enlace de descarga de Carta Manifiesto + * Devuelve un enlace temporal para descargar en ZIP la Carta Manifiesto firmada que corresponde al RFC del CSD cargado en la organización. + * + * La organización debe tener un CSD cargado y debe existir una Carta Manifiesto firmada para el RFC extraído de ese certificado. El enlace da acceso al archivo mientras siga vigente: trátalo como una credencial y no lo almacenes. + */ + get: operations['getOrganizationManifiestoDownloadUrl']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/organizations/{organization_id}/legal': { parameters: { query?: never; @@ -9973,6 +9995,35 @@ export interface operations { 500: components['responses']['UnexpectedError']; }; }; + getOrganizationManifiestoDownloadUrl: { + parameters: { + query?: never; + header?: never; + path: { + /** ID de la organización. También puedes usar `me` con una Secret Key de la organización. */ + organization_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Objeto con una URL temporal para descargar la Carta Manifiesto en ZIP, su fecha de expiración, el tipo de contenido y el nombre del archivo. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SignedDownloadUrl']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 404: components['responses']['NotFound']; + 409: components['responses']['Conflict']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; editOrganizationLegal: { parameters: { query?: never; diff --git a/src/generated/output.ts b/src/generated/output.ts index 8f4a4ef..baecb9e 100644 --- a/src/generated/output.ts +++ b/src/generated/output.ts @@ -1374,6 +1374,28 @@ export interface paths { patch?: never; trace?: never; }; + '/organizations/{organization_id}/manifiesto/download-url': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Obtener enlace de descarga de Carta Manifiesto + * Devuelve un enlace temporal para descargar en ZIP la Carta Manifiesto firmada que corresponde al RFC del CSD cargado en la organización. + * + * La organización debe tener un CSD cargado y debe existir una Carta Manifiesto firmada para el RFC extraído de ese certificado. El enlace da acceso al archivo mientras siga vigente: trátalo como una credencial y no lo almacenes. + */ + get: operations['getOrganizationManifiestoDownloadUrl']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/organizations/{organization_id}/legal': { parameters: { query?: never; @@ -9989,6 +10011,35 @@ export interface operations { 500: components['responses']['UnexpectedError']; }; }; + getOrganizationManifiestoDownloadUrl: { + parameters: { + query?: never; + header?: never; + path: { + /** ID de la organización. También puedes usar `me` con una Secret Key de la organización. */ + organization_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Objeto con una URL temporal para descargar la Carta Manifiesto en ZIP, su fecha de expiración, el tipo de contenido y el nombre del archivo. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['SignedDownloadUrl']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 404: components['responses']['NotFound']; + 409: components['responses']['Conflict']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; editOrganizationLegal: { parameters: { query?: never; diff --git a/src/resources/organizations.ts b/src/resources/organizations.ts index b421953..8965664 100644 --- a/src/resources/organizations.ts +++ b/src/resources/organizations.ts @@ -335,6 +335,29 @@ export default class Organizations { }); } + /** + * Obtener enlace de descarga de Carta Manifiesto + * + * Devuelve un enlace temporal para descargar en ZIP la Carta Manifiesto firmada que corresponde al RFC del CSD cargado en la organización. + * + * La organización debe tener un CSD cargado y debe existir una Carta Manifiesto firmada para el RFC extraído de ese certificado. El enlace da acceso al archivo mientras siga vigente: trátalo como una credencial y no lo almacenes. + * + * @param id - ID de la organización. También puedes usar `me` con una Secret Key de la organización. + * @returns Objeto SignedDownloadUrl con url, expires_at, content_type y filename. + */ + downloadManifiestoZipUrl( + id: string, + ): Promise> { + if (!id) return Promise.reject(new Error('id is required')); + + return this.client.request< + OperationResponse<'getOrganizationManifiestoDownloadUrl'> + >(`/organizations/${encodeURIComponent(id)}/manifiesto/download-url`, { + method: 'GET', + datePlan: operationDatePlans.getOrganizationManifiestoDownloadUrl, + }); + } + /** * Eliminar organización * diff --git a/test/node/signed-download-urls.node.test.ts b/test/node/signed-download-urls.node.test.ts index 30f945a..8a80228 100644 --- a/test/node/signed-download-urls.node.test.ts +++ b/test/node/signed-download-urls.node.test.ts @@ -116,6 +116,32 @@ describe('signed download URLs', () => { expect(result.filename).toBe('receipt.pdf'); }); + it('requests a signed URL for the organization manifiesto ZIP', async () => { + const client = new Facturapi('sk_test_123'); + client.BASE_URL = 'https://api.test.local/v2'; + globalThis.fetch = vi.fn(async (url, options) => { + expect(url).toBe( + 'https://api.test.local/v2/organizations/org_123/manifiesto/download-url', + ); + expect(options?.method).toBe('GET'); + return signedUrlResponse( + 'application/zip', + 'manifiesto_AAA010101AAA.zip', + ); + }) as typeof fetch; + + const result = + await client.organizations.downloadManifiestoZipUrl('org_123'); + + expect(result).toEqual( + expect.objectContaining({ + content_type: 'application/zip', + filename: 'manifiesto_AAA010101AAA.zip', + expires_at: new Date('2030-01-01T00:00:00.000Z'), + }), + ); + }); + it.each([ [ 'PDF', @@ -189,6 +215,9 @@ describe('signed download URLs', () => { await expect(client.retentions.downloadZipUrl('')).rejects.toThrow( 'id is required', ); + await expect( + client.organizations.downloadManifiestoZipUrl(''), + ).rejects.toThrow('id is required'); expect(fetchMock).not.toHaveBeenCalled(); }); });