From 3aaff8298490fad1270eddf7fcff170a39f53586 Mon Sep 17 00:00:00 2001 From: javorosas Date: Mon, 2 Nov 2026 18:07:01 +0100 Subject: [PATCH 1/5] feat(catalogs): add address and fiscal catalog methods --- CHANGELOG.md | 10 + README.md | 12 + openapi/source.json | 2 +- scripts/sdk/resources.json | 12 +- src/generated/dates.ts | 10 + src/generated/input.ts | 317 +++++++++++++++++++++++--- src/generated/models.ts | 6 + src/generated/output.ts | 317 +++++++++++++++++++++++--- src/tools/cartaPorteCatalogs.ts | 30 +-- src/tools/catalogs.ts | 101 ++++++++ src/tools/comercioExteriorCatalogs.ts | 3 +- src/tools/nominaCatalogs.ts | 6 +- test-d/runtime-types.test-d.ts | 24 ++ test/catalogs.shared.ts | 91 ++++++++ test/node/catalogs.node.test.ts | 1 + test/web/catalogs.web.test.ts | 1 + 16 files changed, 863 insertions(+), 80 deletions(-) create mode 100644 test/catalogs.shared.ts create mode 100644 test/node/catalogs.node.test.ts create mode 100644 test/web/catalogs.web.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ca85b75..d22589a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Allow browsing Carta Porte, Comercio Exterior, and payroll catalogs without a search string, matching the API contract. + +### Added + +- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. + ## [6.0.0] Unreleased ### Breaking diff --git a/README.md b/README.md index 8d215f7..79ee3b5 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,18 @@ await facturapi.customers.createGeneric({ Los tres métodos usan la misma operación de la API. `create()` sigue disponible si decides el caso dinámicamente, o si quieres guardar datos incompletos con `{ createEditLink: true }`. Las validaciones siguen siendo responsabilidad de la API. +### Catálogos de domicilio y datos fiscales + +```ts +const zips = await facturapi.catalogs.searchZipCodes({ q: '01', limit: 10 }); +const zip = await facturapi.catalogs.getZipCode('01000'); +const countries = await facturapi.catalogs.searchCountries({ q: 'México' }); +const taxSystems = await facturapi.catalogs.searchTaxSystems({ q: '601' }); +const uses = await facturapi.catalogs.searchInvoiceUses({ q: 'gastos' }); +``` + +La búsqueda postal acepta el código completo o un prefijo numérico y un filtro opcional `state` con la clave de tres letras. El detalle incluye colonias. Estas búsquedas aceptan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. + ## Qué puedes integrar | Necesitas… | Recurso o guía | diff --git a/openapi/source.json b/openapi/source.json index 02568e4..8c8d203 100644 --- a/openapi/source.json +++ b/openapi/source.json @@ -1,5 +1,5 @@ { "repository": "FacturAPI/facturapi-docs", - "revision": "fbcd72e8cf2b1d1b38d3fd5889ecde6545434c61", + "revision": "7ece726bfb9fc22f246b66b5a1f659dc11c6ed19", "path": "website/openapi_v2.yaml" } diff --git a/scripts/sdk/resources.json b/scripts/sdk/resources.json index d5a7c8d..2618a68 100644 --- a/scripts/sdk/resources.json +++ b/scripts/sdk/resources.json @@ -538,7 +538,17 @@ "optional": { "params": false } - } + }, + "searchZipCodes": "searchZipCodes", + "getZipCode": { + "operation": "getZipCode", + "argumentNames": { + "code": "code" + } + }, + "searchCountries": "searchCountries", + "searchTaxSystems": "searchTaxSystems", + "searchInvoiceUses": "searchInvoiceUses" } }, "comercioExteriorCatalogs": { diff --git a/src/generated/dates.ts b/src/generated/dates.ts index da980e6..6a9063b 100644 --- a/src/generated/dates.ts +++ b/src/generated/dates.ts @@ -418,6 +418,11 @@ export const componentDatePlans = { CustomerEditLinkCompletedEvent: 33, ApiEvent: 36, SignedDownloadUrl: 37, + CatalogEntry: 0, + CatalogEntrySearchResult: 0, + ZipCode: 0, + ZipCodeDetail: 0, + ZipCodeSearchResult: 0, SearchKeyDescriptionResult: 0, RelatedResourceMessage: 5, EventBase: 38, @@ -797,6 +802,11 @@ export const operationDatePlans = { validateWebhookSignature: 0, checkApiHealth: 0, validateTaxId: 0, + searchCountries: 0, + searchTaxSystems: 0, + searchInvoiceUses: 0, + searchZipCodes: 0, + getZipCode: 0, searchProducts: 0, searchUnits: 0, onInvoiceGlobalInvoiceCreated: 0, diff --git a/src/generated/input.ts b/src/generated/input.ts index 2d110c9..667a90a 100644 --- a/src/generated/input.ts +++ b/src/generated/input.ts @@ -2060,6 +2060,106 @@ export interface paths { patch?: never; trace?: never; }; + '/catalogs/countries': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar países + * Busca países por código ISO de tres letras o nombre. La búsqueda ignora acentos y mayúsculas. + */ + get: operations['searchCountries']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/tax-systems': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar regímenes fiscales + * Consulta los regímenes fiscales admitidos por la API. El receptor debe declarar su régimen; esta búsqueda no lo determina. + */ + get: operations['searchTaxSystems']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/invoice-uses': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar usos CFDI + * Consulta claves de uso CFDI por código o descripción. El uso debe ser declarado por el receptor. + */ + get: operations['searchInvoiceUses']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/zip-codes': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar códigos postales + * Busca códigos postales mexicanos por código completo o prefijo numérico, ordenados por código. Las colonias se consultan en el detalle. No admite búsqueda por nombre de ciudad. + */ + get: operations['searchZipCodes']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/zip-codes/{code}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Consultar código postal + * Devuelve los datos de domicilio y las colonias de un código postal mexicano. + */ + get: operations['getZipCode']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/catalogs/products': { parameters: { query?: never; @@ -2412,6 +2512,26 @@ export interface components { /** Nombre sugerido del archivo. */ filename: string; }; + CatalogEntry: { + key: string; + description: string; + }; + CatalogEntrySearchResult: components['schemas']['SearchResult'] & { + data: components['schemas']['CatalogEntry'][]; + }; + ZipCode: { + key: string; + country: components['schemas']['CatalogEntry']; + state: components['schemas']['CatalogEntry'] | null; + municipality: components['schemas']['CatalogEntry'] | null; + locality: components['schemas']['CatalogEntry'] | null; + }; + ZipCodeDetail: components['schemas']['ZipCode'] & { + neighborhoods: components['schemas']['CatalogEntry'][]; + }; + ZipCodeSearchResult: components['schemas']['SearchResult'] & { + data: components['schemas']['ZipCode'][]; + }; SearchKeyDescriptionResult: components['schemas']['SearchResult'] & { data?: { /** Clave del catálogo */ @@ -7494,9 +7614,9 @@ export type $defs = Record; export interface operations { searchNominaDeductions: { parameters: { - query: { + query?: { /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; page?: number; limit?: number; }; @@ -7522,9 +7642,9 @@ export interface operations { }; searchNominaPerceptions: { parameters: { - query: { + query?: { /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; page?: number; limit?: number; }; @@ -7550,7 +7670,7 @@ export interface operations { }; searchCartaPorteAirTransportCodes: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7558,7 +7678,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `airline_name` o `icao_designator`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7588,7 +7708,7 @@ export interface operations { }; searchComercioExteriorTariffFractions: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7596,7 +7716,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7626,7 +7746,7 @@ export interface operations { }; searchCartaPorteTransportConfigs: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7634,7 +7754,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7664,7 +7784,7 @@ export interface operations { }; searchCartaPorteRightsOfPassage: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7672,7 +7792,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `right_of_passage` o `concessionaire`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7702,7 +7822,7 @@ export interface operations { }; searchCartaPorteCustomsDocuments: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7710,7 +7830,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7740,7 +7860,7 @@ export interface operations { }; searchCartaPortePackagingTypes: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7748,7 +7868,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7778,7 +7898,7 @@ export interface operations { }; searchCartaPorteTrailerTypes: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7786,7 +7906,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7816,7 +7936,7 @@ export interface operations { }; searchCartaPorteHazardousMaterials: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7824,7 +7944,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `description` o `class_division`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7854,7 +7974,7 @@ export interface operations { }; searchCartaPorteNavalAuthorizations: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7862,7 +7982,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7896,7 +8016,7 @@ export interface operations { }; searchCartaPortePortStations: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7904,7 +8024,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `description` o `iata_designator`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7934,7 +8054,7 @@ export interface operations { }; searchCartaPorteMarineContainers: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7942,7 +8062,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -11248,6 +11368,151 @@ export interface operations { 500: components['responses']['UnexpectedError']; }; }; + searchCountries: { + parameters: { + query?: { + /** Código o texto a buscar. */ + q?: string; + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CatalogEntrySearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + searchTaxSystems: { + parameters: { + query?: { + /** Código o texto a buscar. */ + q?: string; + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CatalogEntrySearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + searchInvoiceUses: { + parameters: { + query?: { + /** Código o texto a buscar. */ + q?: string; + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CatalogEntrySearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + searchZipCodes: { + parameters: { + query?: { + /** Código o prefijo numérico. */ + q?: string; + page?: number; + limit?: number; + /** Clave de estado de tres letras (por ejemplo ZAC). */ + state?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ZipCodeSearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + getZipCode: { + parameters: { + query?: never; + header?: never; + path: { + code: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Código postal */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ZipCodeDetail']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 404: components['responses']['NotFound']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; searchProducts: { parameters: { query?: { diff --git a/src/generated/models.ts b/src/generated/models.ts index 17f02ba..223d4f9 100644 --- a/src/generated/models.ts +++ b/src/generated/models.ts @@ -17,6 +17,12 @@ export type ReceiptStatusUpdatedEvent = export type CustomerEditLinkCompletedEvent = Output['schemas']['CustomerEditLinkCompletedEvent']; export type SignedDownloadUrl = Output['schemas']['SignedDownloadUrl']; +export type CatalogEntry = Output['schemas']['CatalogEntry']; +export type CatalogEntrySearchResult = + Output['schemas']['CatalogEntrySearchResult']; +export type ZipCode = Output['schemas']['ZipCode']; +export type ZipCodeDetail = Output['schemas']['ZipCodeDetail']; +export type ZipCodeSearchResult = Output['schemas']['ZipCodeSearchResult']; export type SearchKeyDescriptionResult = Output['schemas']['SearchKeyDescriptionResult']; export type RelatedResourceMessage = diff --git a/src/generated/output.ts b/src/generated/output.ts index 8f4a4ef..68cc580 100644 --- a/src/generated/output.ts +++ b/src/generated/output.ts @@ -2072,6 +2072,106 @@ export interface paths { patch?: never; trace?: never; }; + '/catalogs/countries': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar países + * Busca países por código ISO de tres letras o nombre. La búsqueda ignora acentos y mayúsculas. + */ + get: operations['searchCountries']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/tax-systems': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar regímenes fiscales + * Consulta los regímenes fiscales admitidos por la API. El receptor debe declarar su régimen; esta búsqueda no lo determina. + */ + get: operations['searchTaxSystems']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/invoice-uses': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar usos CFDI + * Consulta claves de uso CFDI por código o descripción. El uso debe ser declarado por el receptor. + */ + get: operations['searchInvoiceUses']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/zip-codes': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Buscar códigos postales + * Busca códigos postales mexicanos por código completo o prefijo numérico, ordenados por código. Las colonias se consultan en el detalle. No admite búsqueda por nombre de ciudad. + */ + get: operations['searchZipCodes']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/catalogs/zip-codes/{code}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Consultar código postal + * Devuelve los datos de domicilio y las colonias de un código postal mexicano. + */ + get: operations['getZipCode']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; '/catalogs/products': { parameters: { query?: never; @@ -2424,6 +2524,26 @@ export interface components { /** Nombre sugerido del archivo. */ filename: string; }; + CatalogEntry: { + key: string; + description: string; + }; + CatalogEntrySearchResult: components['schemas']['SearchResult'] & { + data: components['schemas']['CatalogEntry'][]; + }; + ZipCode: { + key: string; + country: components['schemas']['CatalogEntry']; + state: components['schemas']['CatalogEntry'] | null; + municipality: components['schemas']['CatalogEntry'] | null; + locality: components['schemas']['CatalogEntry'] | null; + }; + ZipCodeDetail: components['schemas']['ZipCode'] & { + neighborhoods: components['schemas']['CatalogEntry'][]; + }; + ZipCodeSearchResult: components['schemas']['SearchResult'] & { + data: components['schemas']['ZipCode'][]; + }; SearchKeyDescriptionResult: components['schemas']['SearchResult'] & { data?: { /** Clave del catálogo */ @@ -7510,9 +7630,9 @@ export type $defs = Record; export interface operations { searchNominaDeductions: { parameters: { - query: { + query?: { /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; page?: number; limit?: number; }; @@ -7538,9 +7658,9 @@ export interface operations { }; searchNominaPerceptions: { parameters: { - query: { + query?: { /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; page?: number; limit?: number; }; @@ -7566,7 +7686,7 @@ export interface operations { }; searchCartaPorteAirTransportCodes: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7574,7 +7694,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `airline_name` o `icao_designator`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7604,7 +7724,7 @@ export interface operations { }; searchComercioExteriorTariffFractions: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7612,7 +7732,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7642,7 +7762,7 @@ export interface operations { }; searchCartaPorteTransportConfigs: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7650,7 +7770,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7680,7 +7800,7 @@ export interface operations { }; searchCartaPorteRightsOfPassage: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7688,7 +7808,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `right_of_passage` o `concessionaire`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7718,7 +7838,7 @@ export interface operations { }; searchCartaPorteCustomsDocuments: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7726,7 +7846,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7756,7 +7876,7 @@ export interface operations { }; searchCartaPortePackagingTypes: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7764,7 +7884,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7794,7 +7914,7 @@ export interface operations { }; searchCartaPorteTrailerTypes: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7802,7 +7922,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7832,7 +7952,7 @@ export interface operations { }; searchCartaPorteHazardousMaterials: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7840,7 +7960,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `description` o `class_division`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7870,7 +7990,7 @@ export interface operations { }; searchCartaPorteNavalAuthorizations: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7878,7 +7998,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7912,7 +8032,7 @@ export interface operations { }; searchCartaPortePortStations: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7920,7 +8040,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key`, `description` o `iata_designator`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -7950,7 +8070,7 @@ export interface operations { }; searchCartaPorteMarineContainers: { parameters: { - query: { + query?: { /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ pagination?: components['parameters']['SearchPagination']; /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ @@ -7958,7 +8078,7 @@ export interface operations { /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; /** Prefijo para buscar en `key` o `description`. */ - q: string; + q?: string; /** Página de resultados a regresar, empezando desde la página 1. El máximo no es fijo; junto con `limit` debe caber dentro del tope de 3,000 resultados. */ page?: number; /** Número del 1 al 100 que representa la cantidad máxima de resultados a regresar con motivos de paginación. */ @@ -11264,6 +11384,151 @@ export interface operations { 500: components['responses']['UnexpectedError']; }; }; + searchCountries: { + parameters: { + query?: { + /** Código o texto a buscar. */ + q?: string; + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CatalogEntrySearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + searchTaxSystems: { + parameters: { + query?: { + /** Código o texto a buscar. */ + q?: string; + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CatalogEntrySearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + searchInvoiceUses: { + parameters: { + query?: { + /** Código o texto a buscar. */ + q?: string; + page?: number; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['CatalogEntrySearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + searchZipCodes: { + parameters: { + query?: { + /** Código o prefijo numérico. */ + q?: string; + page?: number; + limit?: number; + /** Clave de estado de tres letras (por ejemplo ZAC). */ + state?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Resultados de búsqueda */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ZipCodeSearchResult']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; + getZipCode: { + parameters: { + query?: never; + header?: never; + path: { + code: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** Código postal */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + 'application/json': components['schemas']['ZipCodeDetail']; + }; + }; + 400: components['responses']['BadRequest']; + 401: components['responses']['Unauthenticated']; + 404: components['responses']['NotFound']; + 429: components['responses']['RateLimited']; + 500: components['responses']['UnexpectedError']; + }; + }; searchProducts: { parameters: { query?: { diff --git a/src/tools/cartaPorteCatalogs.ts b/src/tools/cartaPorteCatalogs.ts index 68d19f1..8ea4074 100644 --- a/src/tools/cartaPorteCatalogs.ts +++ b/src/tools/cartaPorteCatalogs.ts @@ -14,11 +14,10 @@ export default class CartaPorteCatalogs { * Devuelve entradas del catálogo de aerolíneas que coinciden con la consulta. Usado para el complemento Carta Porte. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key`, `airline_name` o `icao_designator`. * @returns Búsqueda exitosa */ searchAirTransportCodes( - params: OperationQuery<'searchCartaPorteAirTransportCodes'> | null, + params?: OperationQuery<'searchCartaPorteAirTransportCodes'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteAirTransportCodes'> @@ -35,11 +34,10 @@ export default class CartaPorteCatalogs { * Devuelve configuraciones de transporte (p. ej., camión/semirremolque). * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchTransportConfigs( - params: OperationQuery<'searchCartaPorteTransportConfigs'> | null, + params?: OperationQuery<'searchCartaPorteTransportConfigs'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteTransportConfigs'> @@ -56,11 +54,10 @@ export default class CartaPorteCatalogs { * Devuelve derechos de paso ferroviarios que coinciden con la consulta. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key`, `right_of_passage` o `concessionaire`. * @returns Búsqueda exitosa */ searchRightsOfPassage( - params: OperationQuery<'searchCartaPorteRightsOfPassage'> | null, + params?: OperationQuery<'searchCartaPorteRightsOfPassage'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteRightsOfPassage'> @@ -77,11 +74,10 @@ export default class CartaPorteCatalogs { * Devuelve tipos de documentos aduaneros. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchCustomsDocuments( - params: OperationQuery<'searchCartaPorteCustomsDocuments'> | null, + params?: OperationQuery<'searchCartaPorteCustomsDocuments'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteCustomsDocuments'> @@ -98,11 +94,10 @@ export default class CartaPorteCatalogs { * Devuelve tipos de empaque para mercancías. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchPackagingTypes( - params: OperationQuery<'searchCartaPortePackagingTypes'> | null, + params?: OperationQuery<'searchCartaPortePackagingTypes'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPortePackagingTypes'> @@ -119,11 +114,10 @@ export default class CartaPorteCatalogs { * Devuelve tipos de remolque/semirremolque. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchTrailerTypes( - params: OperationQuery<'searchCartaPorteTrailerTypes'> | null, + params?: OperationQuery<'searchCartaPorteTrailerTypes'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteTrailerTypes'> @@ -140,11 +134,10 @@ export default class CartaPorteCatalogs { * Devuelve entradas del catálogo de materiales peligrosos. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key`, `description` o `class_division`. * @returns Búsqueda exitosa */ searchHazardousMaterials( - params: OperationQuery<'searchCartaPorteHazardousMaterials'> | null, + params?: OperationQuery<'searchCartaPorteHazardousMaterials'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteHazardousMaterials'> @@ -161,11 +154,10 @@ export default class CartaPorteCatalogs { * Devuelve códigos de autorización naval (solo `key`). * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key`. * @returns Búsqueda exitosa */ searchNavalAuthorizations( - params: OperationQuery<'searchCartaPorteNavalAuthorizations'> | null, + params?: OperationQuery<'searchCartaPorteNavalAuthorizations'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteNavalAuthorizations'> @@ -182,11 +174,10 @@ export default class CartaPorteCatalogs { * Devuelve entradas de estaciones aéreas/marítimas/terrestres. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key`, `description` o `iata_designator`. * @returns Búsqueda exitosa */ searchPortStations( - params: OperationQuery<'searchCartaPortePortStations'> | null, + params?: OperationQuery<'searchCartaPortePortStations'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPortePortStations'> @@ -203,11 +194,10 @@ export default class CartaPorteCatalogs { * Devuelve tipos de contenedores marítimos. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchMarineContainers( - params: OperationQuery<'searchCartaPorteMarineContainers'> | null, + params?: OperationQuery<'searchCartaPorteMarineContainers'> | null, ): Promise> { return this.client.request< OperationResponse<'searchCartaPorteMarineContainers'> diff --git a/src/tools/catalogs.ts b/src/tools/catalogs.ts index 6c865b3..e109ab4 100644 --- a/src/tools/catalogs.ts +++ b/src/tools/catalogs.ts @@ -49,4 +49,105 @@ export default class Catalogs { }, ); } + + /** + * Buscar códigos postales + * + * Busca códigos postales mexicanos por código completo o prefijo numérico, ordenados por código. Las colonias se consultan en el detalle. No admite búsqueda por nombre de ciudad. + * + * @param params - Parámetros de consulta. + * @returns Resultados de búsqueda + */ + searchZipCodes( + params?: OperationQuery<'searchZipCodes'> | null, + ): Promise> { + return this.client.request>( + `/catalogs/zip-codes`, + { + method: 'GET', + datePlan: operationDatePlans.searchZipCodes, + params: params, + }, + ); + } + + /** + * Consultar código postal + * + * Devuelve los datos de domicilio y las colonias de un código postal mexicano. + * + * @param code - code + * @returns Código postal + */ + getZipCode(code: string): Promise> { + if (!code) return Promise.reject(new Error('code is required')); + + return this.client.request>( + `/catalogs/zip-codes/${encodeURIComponent(code)}`, + { method: 'GET', datePlan: operationDatePlans.getZipCode }, + ); + } + + /** + * Buscar países + * + * Busca países por código ISO de tres letras o nombre. La búsqueda ignora acentos y mayúsculas. + * + * @param params - Parámetros de consulta. + * @returns Resultados de búsqueda + */ + searchCountries( + params?: OperationQuery<'searchCountries'> | null, + ): Promise> { + return this.client.request>( + `/catalogs/countries`, + { + method: 'GET', + datePlan: operationDatePlans.searchCountries, + params: params, + }, + ); + } + + /** + * Buscar regímenes fiscales + * + * Consulta los regímenes fiscales admitidos por la API. El receptor debe declarar su régimen; esta búsqueda no lo determina. + * + * @param params - Parámetros de consulta. + * @returns Resultados de búsqueda + */ + searchTaxSystems( + params?: OperationQuery<'searchTaxSystems'> | null, + ): Promise> { + return this.client.request>( + `/catalogs/tax-systems`, + { + method: 'GET', + datePlan: operationDatePlans.searchTaxSystems, + params: params, + }, + ); + } + + /** + * Buscar usos CFDI + * + * Consulta claves de uso CFDI por código o descripción. El uso debe ser declarado por el receptor. + * + * @param params - Parámetros de consulta. + * @returns Resultados de búsqueda + */ + searchInvoiceUses( + params?: OperationQuery<'searchInvoiceUses'> | null, + ): Promise> { + return this.client.request>( + `/catalogs/invoice-uses`, + { + method: 'GET', + datePlan: operationDatePlans.searchInvoiceUses, + params: params, + }, + ); + } } diff --git a/src/tools/comercioExteriorCatalogs.ts b/src/tools/comercioExteriorCatalogs.ts index aef29aa..106bc48 100644 --- a/src/tools/comercioExteriorCatalogs.ts +++ b/src/tools/comercioExteriorCatalogs.ts @@ -14,11 +14,10 @@ export default class ComercioExteriorCatalogs { * Devuelve fracciones arancelarias que coinciden con la consulta. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchTariffFractions( - params: OperationQuery<'searchComercioExteriorTariffFractions'>, + params?: OperationQuery<'searchComercioExteriorTariffFractions'> | null, ): Promise> { return this.client.request< OperationResponse<'searchComercioExteriorTariffFractions'> diff --git a/src/tools/nominaCatalogs.ts b/src/tools/nominaCatalogs.ts index e8a37c9..4e73e36 100644 --- a/src/tools/nominaCatalogs.ts +++ b/src/tools/nominaCatalogs.ts @@ -14,11 +14,10 @@ export default class NominaCatalogs { * Devuelve tipos de deducción que coinciden con la consulta. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchDeductions( - params: OperationQuery<'searchNominaDeductions'>, + params?: OperationQuery<'searchNominaDeductions'> | null, ): Promise> { return this.client.request>( `/catalogs/deductions`, @@ -36,11 +35,10 @@ export default class NominaCatalogs { * Devuelve tipos de percepción que coinciden con la consulta. * * @param params - Parámetros de consulta. - * @param params.q - Prefijo para buscar en `key` o `description`. * @returns Búsqueda exitosa */ searchPerceptions( - params: OperationQuery<'searchNominaPerceptions'>, + params?: OperationQuery<'searchNominaPerceptions'> | null, ): Promise> { return this.client.request>( `/catalogs/perceptions`, diff --git a/test-d/runtime-types.test-d.ts b/test-d/runtime-types.test-d.ts index b248abb..2195638 100644 --- a/test-d/runtime-types.test-d.ts +++ b/test-d/runtime-types.test-d.ts @@ -5,6 +5,9 @@ import { expectError, } from 'tsd'; import Facturapi, { + CatalogEntrySearchResult, + ZipCodeSearchResult, + ZipCodeDetail, BinaryDownload, ApiKeys, ApiEvent, @@ -774,3 +777,24 @@ expectType(restoredOrganization.legal?.curp); expectType( restoredOrganization.customization?.default_series?.I, ); + +expectType>( + client.catalogs.searchZipCodes({ q: '01', state: 'CMX', limit: 10 }), +); +expectType>(client.catalogs.getZipCode('01000')); +expectType>( + client.catalogs.searchCountries({ q: 'México' }), +); +expectType>( + client.catalogs.searchTaxSystems(), +); +expectType>( + client.catalogs.searchInvoiceUses({ page: 2 }), +); +expectError(client.catalogs.getZipCode(1000)); +expectError(client.catalogs.searchZipCodes({ state: 9 })); + +client.cartaPorteCatalogs.searchAirTransportCodes({ page: 1 }); +client.comercioExteriorCatalogs.searchTariffFractions({ limit: 10 }); +client.nominaCatalogs.searchDeductions(); +client.nominaCatalogs.searchPerceptions({ page: 2 }); diff --git a/test/catalogs.shared.ts b/test/catalogs.shared.ts new file mode 100644 index 0000000..3966772 --- /dev/null +++ b/test/catalogs.shared.ts @@ -0,0 +1,91 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import Facturapi from '../src'; + +afterEach(() => vi.unstubAllGlobals()); + +describe('address and fiscal catalogs', () => { + it('sends search parameters through HTTP and preserves catalog keys', async () => { + const client = new Facturapi('sk_test_example'); + client.BASE_URL = 'https://api.example.com/v2'; + const page = { + page: 2, + total_pages: 3, + total_results: 25, + data: [{ key: '01000', description: 'Catalog entry' }], + }; + const fetch = vi.fn(async () => Response.json(page)); + vi.stubGlobal('fetch', fetch); + for (const [method, path] of [ + ['searchZipCodes', 'zip-codes'], + ['searchCountries', 'countries'], + ['searchTaxSystems', 'tax-systems'], + ['searchInvoiceUses', 'invoice-uses'], + ] as const) { + fetch.mockClear(); + expect( + await client.catalogs[method]({ q: '01', page: 2, limit: 10 }), + ).toEqual(page); + expect(fetch).toHaveBeenCalledExactlyOnceWith( + `https://api.example.com/v2/catalogs/${path}?q=01&page=2&limit=10`, + expect.objectContaining({ method: 'GET' }), + ); + } + }); + + it('browses specialized catalogs without a query string', async () => { + const client = new Facturapi('sk_test_example'); + client.BASE_URL = 'https://api.example.com/v2'; + const page = { page: 0, total_pages: 0, total_results: 0, data: [] }; + const fetch = vi.fn(async () => Response.json(page)); + vi.stubGlobal('fetch', fetch); + expect(await client.nominaCatalogs.searchDeductions()).toEqual(page); + expect( + await client.cartaPorteCatalogs.searchAirTransportCodes({ page: 1 }), + ).toEqual(page); + expect( + await client.comercioExteriorCatalogs.searchTariffFractions({ + limit: 10, + }), + ).toEqual(page); + expect(fetch).toHaveBeenNthCalledWith( + 1, + 'https://api.example.com/v2/catalogs/deductions', + expect.objectContaining({ method: 'GET' }), + ); + expect(fetch).toHaveBeenNthCalledWith( + 2, + 'https://api.example.com/v2/catalogs/cartaporte/3.1/air-transport-codes?page=1', + expect.objectContaining({ method: 'GET' }), + ); + expect(fetch).toHaveBeenNthCalledWith( + 3, + 'https://api.example.com/v2/catalogs/comercioexterior/2.0/tariff-fractions?limit=10', + expect.objectContaining({ method: 'GET' }), + ); + }); + + it('retrieves postal-code details and URL-encodes the code argument', async () => { + const client = new Facturapi('sk_test_example'); + client.BASE_URL = 'https://api.example.com/v2'; + const detail = { + key: '01000', + country: { key: 'MEX', description: 'México' }, + state: null, + municipality: null, + locality: null, + neighborhoods: [{ key: '0001', description: 'Centro' }], + }; + const fetch = vi.fn(async () => Response.json(detail)); + vi.stubGlobal('fetch', fetch); + expect(await client.catalogs.getZipCode('01000')).toEqual(detail); + expect(fetch).toHaveBeenLastCalledWith( + 'https://api.example.com/v2/catalogs/zip-codes/01000', + expect.objectContaining({ method: 'GET' }), + ); + await client.catalogs.getZipCode('01/000'); + expect(fetch).toHaveBeenLastCalledWith( + 'https://api.example.com/v2/catalogs/zip-codes/01%2F000', + expect.objectContaining({ method: 'GET' }), + ); + }); +}); diff --git a/test/node/catalogs.node.test.ts b/test/node/catalogs.node.test.ts new file mode 100644 index 0000000..6fc9dbd --- /dev/null +++ b/test/node/catalogs.node.test.ts @@ -0,0 +1 @@ +import '../catalogs.shared'; diff --git a/test/web/catalogs.web.test.ts b/test/web/catalogs.web.test.ts new file mode 100644 index 0000000..6fc9dbd --- /dev/null +++ b/test/web/catalogs.web.test.ts @@ -0,0 +1 @@ +import '../catalogs.shared'; From 31484e6d1bf09754f0f7fb5164d29d46585efb6f Mon Sep 17 00:00:00 2001 From: javorosas Date: Mon, 2 Nov 2026 22:18:19 +0100 Subject: [PATCH 2/5] feat(catalogs): support postal cursor pagination --- CHANGELOG.md | 4 ++-- README.md | 15 +++++++++++++-- openapi/source.json | 2 +- src/generated/input.ts | 6 ++++++ src/generated/output.ts | 6 ++++++ test-d/runtime-types.test-d.ts | 9 +++++++++ test/catalogs.shared.ts | 29 +++++++++++++++++++++++++++++ 7 files changed, 66 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d22589a..91bd9a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed -- Allow browsing Carta Porte, Comercio Exterior, and payroll catalogs without a search string, matching the API contract. +- Allow omitting search text for Carta Porte, Comercio Exterior, and payroll catalogs, matching the API contract. Empty queries return no matches. ### Added -- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. +- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches support page and cursor pagination. ## [6.0.0] Unreleased diff --git a/README.md b/README.md index 79ee3b5..f4f5865 100644 --- a/README.md +++ b/README.md @@ -160,14 +160,25 @@ Los tres métodos usan la misma operación de la API. `create()` sigue disponibl ### Catálogos de domicilio y datos fiscales ```ts -const zips = await facturapi.catalogs.searchZipCodes({ q: '01', limit: 10 }); +const zips = await facturapi.catalogs.searchZipCodes({ + q: '01', + pagination: 'cursor', + limit: 10, +}); +if (zips.next_cursor) { + await facturapi.catalogs.searchZipCodes({ + q: '01', + after: zips.next_cursor, + limit: 10, + }); +} const zip = await facturapi.catalogs.getZipCode('01000'); const countries = await facturapi.catalogs.searchCountries({ q: 'México' }); const taxSystems = await facturapi.catalogs.searchTaxSystems({ q: '601' }); const uses = await facturapi.catalogs.searchInvoiceUses({ q: 'gastos' }); ``` -La búsqueda postal acepta el código completo o un prefijo numérico y un filtro opcional `state` con la clave de tres letras. El detalle incluye colonias. Estas búsquedas aceptan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. +La búsqueda postal acepta el código completo o un prefijo numérico y un filtro opcional `state` con la clave de tres letras. El detalle incluye colonias. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. ## Qué puedes integrar diff --git a/openapi/source.json b/openapi/source.json index 8c8d203..b8019aa 100644 --- a/openapi/source.json +++ b/openapi/source.json @@ -1,5 +1,5 @@ { "repository": "FacturAPI/facturapi-docs", - "revision": "7ece726bfb9fc22f246b66b5a1f659dc11c6ed19", + "revision": "76a4b3bde05acc4123c5cdb8893ec1a67ff4ac0d", "path": "website/openapi_v2.yaml" } diff --git a/src/generated/input.ts b/src/generated/input.ts index 667a90a..7d40e80 100644 --- a/src/generated/input.ts +++ b/src/generated/input.ts @@ -11458,6 +11458,12 @@ export interface operations { searchZipCodes: { parameters: { query?: { + /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ + pagination?: components['parameters']['SearchPagination']; + /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ + after?: components['parameters']['SearchAfter']; + /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ + before?: components['parameters']['SearchBefore']; /** Código o prefijo numérico. */ q?: string; page?: number; diff --git a/src/generated/output.ts b/src/generated/output.ts index 68cc580..d291a44 100644 --- a/src/generated/output.ts +++ b/src/generated/output.ts @@ -11474,6 +11474,12 @@ export interface operations { searchZipCodes: { parameters: { query?: { + /** Modo de paginación de la búsqueda. `page` (por defecto) o `cursor` (recomendado para listas grandes). */ + pagination?: components['parameters']['SearchPagination']; + /** Devuelve los resultados posteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `before`. */ + after?: components['parameters']['SearchAfter']; + /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ + before?: components['parameters']['SearchBefore']; /** Código o prefijo numérico. */ q?: string; page?: number; diff --git a/test-d/runtime-types.test-d.ts b/test-d/runtime-types.test-d.ts index 2195638..aac47bc 100644 --- a/test-d/runtime-types.test-d.ts +++ b/test-d/runtime-types.test-d.ts @@ -798,3 +798,12 @@ client.cartaPorteCatalogs.searchAirTransportCodes({ page: 1 }); client.comercioExteriorCatalogs.searchTariffFractions({ limit: 10 }); client.nominaCatalogs.searchDeductions(); client.nominaCatalogs.searchPerceptions({ page: 2 }); + +client.catalogs.searchZipCodes({ + q: '01', + pagination: 'cursor', + after: 'cursor', + limit: 10, +}); +client.catalogs.searchZipCodes({ q: '01', before: 'cursor', limit: 10 }); +expectError(client.catalogs.searchZipCodes({ pagination: 'other' })); diff --git a/test/catalogs.shared.ts b/test/catalogs.shared.ts index 3966772..5a02cd0 100644 --- a/test/catalogs.shared.ts +++ b/test/catalogs.shared.ts @@ -64,6 +64,35 @@ describe('address and fiscal catalogs', () => { ); }); + it('forwards postal cursor boundaries and preserves cursor metadata', async () => { + const client = new Facturapi('sk_test_example'); + client.BASE_URL = 'https://api.example.com/v2'; + const result = { + data: [{ key: '01000' }], + next_cursor: 'next', + previous_cursor: null, + }; + const fetch = vi.fn(async () => Response.json(result)); + vi.stubGlobal('fetch', fetch); + expect( + await client.catalogs.searchZipCodes({ + q: '01', + pagination: 'cursor', + after: 'boundary', + limit: 10, + }), + ).toEqual(result); + expect(fetch).toHaveBeenLastCalledWith( + 'https://api.example.com/v2/catalogs/zip-codes?q=01&pagination=cursor&after=boundary&limit=10', + expect.objectContaining({ method: 'GET' }), + ); + await client.catalogs.searchZipCodes({ q: '01', before: 'boundary' }); + expect(fetch).toHaveBeenLastCalledWith( + 'https://api.example.com/v2/catalogs/zip-codes?q=01&before=boundary', + expect.objectContaining({ method: 'GET' }), + ); + }); + it('retrieves postal-code details and URL-encodes the code argument', async () => { const client = new Facturapi('sk_test_example'); client.BASE_URL = 'https://api.example.com/v2'; From c2ce9603dbf99ffef09049edb429b63e3beb394c Mon Sep 17 00:00:00 2001 From: javorosas Date: Wed, 4 Nov 2026 15:00:50 +0100 Subject: [PATCH 3/5] feat(catalogs): support postal address name queries --- CHANGELOG.md | 2 +- README.md | 8 +++++--- openapi/source.json | 2 +- src/generated/input.ts | 4 ++-- src/generated/output.ts | 4 ++-- src/tools/catalogs.ts | 2 +- test/catalogs.shared.ts | 30 ++++++++++++++++++++++++++++++ 7 files changed, 42 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bd9a8..7888e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches support page and cursor pagination. +- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches accept numeric prefixes or neighborhood, locality/city, and municipality names, ignore case and diacritics, and support page and cursor pagination. ## [6.0.0] Unreleased diff --git a/README.md b/README.md index f4f5865..cf67323 100644 --- a/README.md +++ b/README.md @@ -161,13 +161,15 @@ Los tres métodos usan la misma operación de la API. `create()` sigue disponibl ```ts const zips = await facturapi.catalogs.searchZipCodes({ - q: '01', + q: 'Nápoles', + state: 'CMX', pagination: 'cursor', limit: 10, }); if (zips.next_cursor) { await facturapi.catalogs.searchZipCodes({ - q: '01', + q: 'Nápoles', + state: 'CMX', after: zips.next_cursor, limit: 10, }); @@ -178,7 +180,7 @@ const taxSystems = await facturapi.catalogs.searchTaxSystems({ q: '601' }); const uses = await facturapi.catalogs.searchInvoiceUses({ q: 'gastos' }); ``` -La búsqueda postal acepta el código completo o un prefijo numérico y un filtro opcional `state` con la clave de tres letras. El detalle incluye colonias. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. +La búsqueda postal acepta el código completo o un prefijo numérico, o el nombre de una colonia, localidad/ciudad o municipio, y un filtro opcional `state` con la clave de tres letras. Las búsquedas por nombre ignoran mayúsculas y diacríticos, priorizan coincidencias exactas y devuelven cada código postal una sola vez. El detalle incluye colonias. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. ## Qué puedes integrar diff --git a/openapi/source.json b/openapi/source.json index b8019aa..1539ab5 100644 --- a/openapi/source.json +++ b/openapi/source.json @@ -1,5 +1,5 @@ { "repository": "FacturAPI/facturapi-docs", - "revision": "76a4b3bde05acc4123c5cdb8893ec1a67ff4ac0d", + "revision": "7760d3ea2c91ac1bc3c658c0f8ef65548dfd9efb", "path": "website/openapi_v2.yaml" } diff --git a/src/generated/input.ts b/src/generated/input.ts index 7d40e80..432a360 100644 --- a/src/generated/input.ts +++ b/src/generated/input.ts @@ -2129,7 +2129,7 @@ export interface paths { }; /** * Buscar códigos postales - * Busca códigos postales mexicanos por código completo o prefijo numérico, ordenados por código. Las colonias se consultan en el detalle. No admite búsqueda por nombre de ciudad. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. */ get: operations['searchZipCodes']; put?: never; @@ -11464,7 +11464,7 @@ export interface operations { after?: components['parameters']['SearchAfter']; /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; - /** Código o prefijo numérico. */ + /** Código o prefijo de 1 a 5 dígitos, o nombre de colonia, localidad/ciudad o municipio. */ q?: string; page?: number; limit?: number; diff --git a/src/generated/output.ts b/src/generated/output.ts index d291a44..0758f98 100644 --- a/src/generated/output.ts +++ b/src/generated/output.ts @@ -2141,7 +2141,7 @@ export interface paths { }; /** * Buscar códigos postales - * Busca códigos postales mexicanos por código completo o prefijo numérico, ordenados por código. Las colonias se consultan en el detalle. No admite búsqueda por nombre de ciudad. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. */ get: operations['searchZipCodes']; put?: never; @@ -11480,7 +11480,7 @@ export interface operations { after?: components['parameters']['SearchAfter']; /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; - /** Código o prefijo numérico. */ + /** Código o prefijo de 1 a 5 dígitos, o nombre de colonia, localidad/ciudad o municipio. */ q?: string; page?: number; limit?: number; diff --git a/src/tools/catalogs.ts b/src/tools/catalogs.ts index e109ab4..d54fbc1 100644 --- a/src/tools/catalogs.ts +++ b/src/tools/catalogs.ts @@ -53,7 +53,7 @@ export default class Catalogs { /** * Buscar códigos postales * - * Busca códigos postales mexicanos por código completo o prefijo numérico, ordenados por código. Las colonias se consultan en el detalle. No admite búsqueda por nombre de ciudad. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. * * @param params - Parámetros de consulta. * @returns Resultados de búsqueda diff --git a/test/catalogs.shared.ts b/test/catalogs.shared.ts index 5a02cd0..f7b2e15 100644 --- a/test/catalogs.shared.ts +++ b/test/catalogs.shared.ts @@ -93,6 +93,36 @@ describe('address and fiscal catalogs', () => { ); }); + it('forwards neighborhood names with case and diacritics intact', async () => { + const client = new Facturapi('sk_test_example'); + client.BASE_URL = 'https://api.example.com/v2'; + const result = { + page: 1, + total_pages: 1, + total_results: 1, + data: [ + { + key: '03810', + country: { key: 'MEX', description: 'México' }, + state: { key: 'CMX', description: 'Ciudad de México' }, + municipality: null, + locality: null, + }, + ], + }; + const fetch = vi.fn(async () => Response.json(result)); + vi.stubGlobal('fetch', fetch); + for (const q of ['Nápoles', 'NAPOLES', 'na\u0301poles']) { + expect(await client.catalogs.searchZipCodes({ q, state: 'CMX' })).toEqual( + result, + ); + expect(fetch).toHaveBeenLastCalledWith( + `https://api.example.com/v2/catalogs/zip-codes?q=${encodeURIComponent(q)}&state=CMX`, + expect.objectContaining({ method: 'GET' }), + ); + } + }); + it('retrieves postal-code details and URL-encodes the code argument', async () => { const client = new Facturapi('sk_test_example'); client.BASE_URL = 'https://api.example.com/v2'; From c93ae8196d01b4b46a8b7aa403024fe2685af40c Mon Sep 17 00:00:00 2001 From: javorosas Date: Wed, 4 Nov 2026 15:24:52 +0100 Subject: [PATCH 4/5] feat(catalogs): include neighborhoods in postal results --- CHANGELOG.md | 2 +- README.md | 2 +- openapi/source.json | 2 +- src/generated/input.ts | 6 +++--- src/generated/output.ts | 6 +++--- src/tools/catalogs.ts | 2 +- test/catalogs.shared.ts | 4 ++++ 7 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7888e26..ea26ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches accept numeric prefixes or neighborhood, locality/city, and municipality names, ignore case and diacritics, and support page and cursor pagination. +- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches accept numeric prefixes or neighborhood, locality/city, and municipality names, ignore case and diacritics, and support page and cursor pagination. Every postal-code result includes all its neighborhoods. ## [6.0.0] Unreleased diff --git a/README.md b/README.md index cf67323..abc4557 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ const taxSystems = await facturapi.catalogs.searchTaxSystems({ q: '601' }); const uses = await facturapi.catalogs.searchInvoiceUses({ q: 'gastos' }); ``` -La búsqueda postal acepta el código completo o un prefijo numérico, o el nombre de una colonia, localidad/ciudad o municipio, y un filtro opcional `state` con la clave de tres letras. Las búsquedas por nombre ignoran mayúsculas y diacríticos, priorizan coincidencias exactas y devuelven cada código postal una sola vez. El detalle incluye colonias. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. +La búsqueda postal acepta el código completo o un prefijo numérico, o el nombre de una colonia, localidad/ciudad o municipio, y un filtro opcional `state` con la clave de tres letras. Las búsquedas por nombre ignoran mayúsculas y diacríticos, priorizan coincidencias exactas y devuelven cada código postal una sola vez. Cada resultado incluye todas sus colonias en `neighborhoods`, con clave y nombre; el detalle devuelve el mismo objeto. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. ## Qué puedes integrar diff --git a/openapi/source.json b/openapi/source.json index 1539ab5..2f027ea 100644 --- a/openapi/source.json +++ b/openapi/source.json @@ -1,5 +1,5 @@ { "repository": "FacturAPI/facturapi-docs", - "revision": "7760d3ea2c91ac1bc3c658c0f8ef65548dfd9efb", + "revision": "43c696fbe20f568368bc023b6bc5be5b3ec47b92", "path": "website/openapi_v2.yaml" } diff --git a/src/generated/input.ts b/src/generated/input.ts index 432a360..5edd7e1 100644 --- a/src/generated/input.ts +++ b/src/generated/input.ts @@ -2129,7 +2129,7 @@ export interface paths { }; /** * Buscar códigos postales - * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. */ get: operations['searchZipCodes']; put?: never; @@ -2525,10 +2525,10 @@ export interface components { state: components['schemas']['CatalogEntry'] | null; municipality: components['schemas']['CatalogEntry'] | null; locality: components['schemas']['CatalogEntry'] | null; - }; - ZipCodeDetail: components['schemas']['ZipCode'] & { + /** Todas las colonias del código postal, aunque la búsqueda coincida por otro campo. */ neighborhoods: components['schemas']['CatalogEntry'][]; }; + ZipCodeDetail: components['schemas']['ZipCode']; ZipCodeSearchResult: components['schemas']['SearchResult'] & { data: components['schemas']['ZipCode'][]; }; diff --git a/src/generated/output.ts b/src/generated/output.ts index 0758f98..b4a65e4 100644 --- a/src/generated/output.ts +++ b/src/generated/output.ts @@ -2141,7 +2141,7 @@ export interface paths { }; /** * Buscar códigos postales - * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. */ get: operations['searchZipCodes']; put?: never; @@ -2537,10 +2537,10 @@ export interface components { state: components['schemas']['CatalogEntry'] | null; municipality: components['schemas']['CatalogEntry'] | null; locality: components['schemas']['CatalogEntry'] | null; - }; - ZipCodeDetail: components['schemas']['ZipCode'] & { + /** Todas las colonias del código postal, aunque la búsqueda coincida por otro campo. */ neighborhoods: components['schemas']['CatalogEntry'][]; }; + ZipCodeDetail: components['schemas']['ZipCode']; ZipCodeSearchResult: components['schemas']['SearchResult'] & { data: components['schemas']['ZipCode'][]; }; diff --git a/src/tools/catalogs.ts b/src/tools/catalogs.ts index d54fbc1..2ea46e0 100644 --- a/src/tools/catalogs.ts +++ b/src/tools/catalogs.ts @@ -53,7 +53,7 @@ export default class Catalogs { /** * Buscar códigos postales * - * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. * * @param params - Parámetros de consulta. * @returns Resultados de búsqueda diff --git a/test/catalogs.shared.ts b/test/catalogs.shared.ts index f7b2e15..6135fb2 100644 --- a/test/catalogs.shared.ts +++ b/test/catalogs.shared.ts @@ -107,6 +107,10 @@ describe('address and fiscal catalogs', () => { state: { key: 'CMX', description: 'Ciudad de México' }, municipality: null, locality: null, + neighborhoods: [ + { key: '0001', description: 'Nápoles' }, + { key: '0002', description: 'Norte' }, + ], }, ], }; From b2692aa40c74597c1e8d26a5c500237af8d0a0b8 Mon Sep 17 00:00:00 2001 From: javorosas Date: Wed, 4 Nov 2026 15:35:46 +0100 Subject: [PATCH 5/5] feat(catalogs): support postal queries by state name --- CHANGELOG.md | 2 +- README.md | 2 +- openapi/source.json | 2 +- src/generated/input.ts | 4 ++-- src/generated/output.ts | 4 ++-- src/tools/catalogs.ts | 2 +- test/catalogs.shared.ts | 15 +++++++++++++++ 7 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea26ab4..e34a823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches accept numeric prefixes or neighborhood, locality/city, and municipality names, ignore case and diacritics, and support page and cursor pagination. Every postal-code result includes all its neighborhoods. +- Search Mexican postal codes, countries, tax regimes, and CFDI uses through `catalogs`, and retrieve postal-code details including neighborhoods. Postal-code searches accept numeric prefixes or neighborhood, locality/city, municipality, and state names, ignore case and diacritics, and support page and cursor pagination. Every postal-code result includes all its neighborhoods. ## [6.0.0] Unreleased diff --git a/README.md b/README.md index abc4557..6253e0c 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ const taxSystems = await facturapi.catalogs.searchTaxSystems({ q: '601' }); const uses = await facturapi.catalogs.searchInvoiceUses({ q: 'gastos' }); ``` -La búsqueda postal acepta el código completo o un prefijo numérico, o el nombre de una colonia, localidad/ciudad o municipio, y un filtro opcional `state` con la clave de tres letras. Las búsquedas por nombre ignoran mayúsculas y diacríticos, priorizan coincidencias exactas y devuelven cada código postal una sola vez. Cada resultado incluye todas sus colonias en `neighborhoods`, con clave y nombre; el detalle devuelve el mismo objeto. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. +La búsqueda postal acepta el código completo o un prefijo numérico, o el nombre de una colonia, localidad/ciudad, municipio o estado, y un filtro opcional `state` con la clave de tres letras. Las búsquedas por nombre ignoran mayúsculas y diacríticos, priorizan coincidencias exactas y devuelven cada código postal una sola vez. Cada resultado incluye todas sus colonias en `neighborhoods`, con clave y nombre; el detalle devuelve el mismo objeto. Los códigos postales admiten páginas o cursores (`pagination`, `after`, `before`); conserva los filtros al avanzar. Países, regímenes y usos usan `page` y `limit` (1–100); las claves de catálogo permanecen como strings para conservar ceros iniciales. El régimen y el uso CFDI deben ser declarados por el receptor; consultar el catálogo no los determina. ## Qué puedes integrar diff --git a/openapi/source.json b/openapi/source.json index 2f027ea..d4b04bf 100644 --- a/openapi/source.json +++ b/openapi/source.json @@ -1,5 +1,5 @@ { "repository": "FacturAPI/facturapi-docs", - "revision": "43c696fbe20f568368bc023b6bc5be5b3ec47b92", + "revision": "9e0fdd689647a1d60f8d9181038225dd04c12732", "path": "website/openapi_v2.yaml" } diff --git a/src/generated/input.ts b/src/generated/input.ts index 5edd7e1..2a995a3 100644 --- a/src/generated/input.ts +++ b/src/generated/input.ts @@ -2129,7 +2129,7 @@ export interface paths { }; /** * Buscar códigos postales - * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad, municipio o estado, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. */ get: operations['searchZipCodes']; put?: never; @@ -11464,7 +11464,7 @@ export interface operations { after?: components['parameters']['SearchAfter']; /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; - /** Código o prefijo de 1 a 5 dígitos, o nombre de colonia, localidad/ciudad o municipio. */ + /** Código o prefijo de 1 a 5 dígitos, o nombre de colonia, localidad/ciudad, municipio o estado. */ q?: string; page?: number; limit?: number; diff --git a/src/generated/output.ts b/src/generated/output.ts index b4a65e4..748a014 100644 --- a/src/generated/output.ts +++ b/src/generated/output.ts @@ -2141,7 +2141,7 @@ export interface paths { }; /** * Buscar códigos postales - * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad, municipio o estado, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. */ get: operations['searchZipCodes']; put?: never; @@ -11480,7 +11480,7 @@ export interface operations { after?: components['parameters']['SearchAfter']; /** Devuelve los resultados anteriores al cursor indicado. Solo con `pagination=cursor`; mutuamente excluyente con `after`. */ before?: components['parameters']['SearchBefore']; - /** Código o prefijo de 1 a 5 dígitos, o nombre de colonia, localidad/ciudad o municipio. */ + /** Código o prefijo de 1 a 5 dígitos, o nombre de colonia, localidad/ciudad, municipio o estado. */ q?: string; page?: number; limit?: number; diff --git a/src/tools/catalogs.ts b/src/tools/catalogs.ts index 2ea46e0..f5d5e7d 100644 --- a/src/tools/catalogs.ts +++ b/src/tools/catalogs.ts @@ -53,7 +53,7 @@ export default class Catalogs { /** * Buscar códigos postales * - * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad o municipio, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. + * Busca códigos postales mexicanos. Una consulta de 1 a 5 dígitos busca por código o prefijo; el texto busca nombres de colonia, localidad/ciudad, municipio o estado, sin distinguir mayúsculas ni diacríticos. Las coincidencias exactas de nombre preceden a las parciales. Cada código postal aparece una sola vez e incluye todas sus colonias en neighborhoods. El filtro de estado se aplica en ambos casos. Sin consulta, lista códigos postales. Admite páginas y cursores; los totales de búsquedas textuales pueden estar limitados, indicado por totals_are_capped. * * @param params - Parámetros de consulta. * @returns Resultados de búsqueda diff --git a/test/catalogs.shared.ts b/test/catalogs.shared.ts index 6135fb2..037b9f3 100644 --- a/test/catalogs.shared.ts +++ b/test/catalogs.shared.ts @@ -127,6 +127,21 @@ describe('address and fiscal catalogs', () => { } }); + it('forwards state names in postal search text', async () => { + const client = new Facturapi('sk_test_example'); + client.BASE_URL = 'https://api.example.com/v2'; + const response = { page: 0, total_pages: 0, total_results: 0, data: [] }; + const fetch = vi.fn(async () => Response.json(response)); + vi.stubGlobal('fetch', fetch); + expect( + await client.catalogs.searchZipCodes({ q: 'Nuevo León', state: 'NLE' }), + ).toEqual(response); + expect(fetch).toHaveBeenCalledExactlyOnceWith( + 'https://api.example.com/v2/catalogs/zip-codes?q=Nuevo+Le%C3%B3n&state=NLE', + expect.objectContaining({ method: 'GET' }), + ); + }); + it('retrieves postal-code details and URL-encodes the code argument', async () => { const client = new Facturapi('sk_test_example'); client.BASE_URL = 'https://api.example.com/v2';