npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@quo-digital/multipac-sdk

v0.2.0

Published

SDK oficial de Node/TypeScript para consumir la API CFDI de Multipac.

Readme

@quo-digital/multipac-sdk

SDK oficial de Node/TypeScript para consumir la API CFDI de Multipac (/api/v1/cfdi/*).

Maneja autenticación (obtención y renovación de token de forma transparente), reintentos ante fallos transitorios del PAC o del rate limit, y validación/tipado en runtime de cada respuesta — para que no tengas que hablar HTTP a mano.

Instalación

npm install @quo-digital/multipac-sdk

Requiere Node.js 18 o superior (usa fetch nativo, sin dependencias de cliente HTTP).

Uso rápido

client.timbrar() recibe el comprobante como JSON estructurado — Multipac construye, sella y timbra el XML por ti; nunca le mandas un XML ya armado.

import { MultipacClient } from '@quo-digital/multipac-sdk';

const client = new MultipacClient({
	baseUrl: 'https://sandbox-multipac.quo.solutions',
	clientKey: process.env.MULTIPAC_CLIENT_KEY!,
	clientSecret: process.env.MULTIPAC_CLIENT_SECRET!,
});

const { uuidCfdi, xmlTimbrado } = await client.timbrar({
	comprobante: {
		tipoDeComprobante: 'I',
		subTotal: 100,
		moneda: 'MXN',
		total: 100,
		lugarExpedicion: '64000',
		emisor: { rfc: 'EKU9003173C9' },
		receptor: {
			rfc: 'URE180429TM6',
			nombre: 'UNIVERSIDAD ROBOTICA ESPANOLA',
			domicilioFiscalReceptor: '65000',
			regimenFiscalReceptor: '601',
			usoCFDI: 'G03',
		},
		conceptos: [
			{
				claveProdServ: '01010101',
				cantidad: 1,
				claveUnidad: 'H87',
				descripcion: 'Producto de prueba',
				valorUnitario: 100,
				importe: 100,
				objetoImp: '01',
			},
		],
	},
});

El cliente obtiene y renueva el token internamente en la primera llamada — no necesitas manejarlo.

Multipac valida la aritmética (importe = cantidad × valorUnitario, subTotal/total, impuestos agregados, totales de complementos, …) pero nunca la calcula para insertarla: si algo no cuadra, rechaza la petición con ConceptoInconsistenteError (codigo: CONCEPTO_INCONSISTENTE) en vez de adivinar un valor. El SDK valida localmente la forma (campos requeridos, tipos, formato de RFC/CP) antes de mandar la petición — ver Manejo de errores — pero esa aritmética es responsabilidad de Multipac, no del SDK.

Complementos

comprobante.complementos agrega un complemento soportado — hoy, solo Nómina (nomina12):

const { uuidCfdi } = await client.timbrar({
	comprobante: {
		tipoDeComprobante: 'N', // Nómina exige "N", y "N" exige un complemento nomina12
		subTotal: 5000,
		descuento: 250,
		moneda: 'MXN',
		metodoPago: 'PUE', // requerido para N — formaPago, en cambio, está prohibido
		total: 4750,
		lugarExpedicion: '72530',
		emisor: { rfc: 'EWE1709045U0' },
		receptor: {
			rfc: 'AAA010101AAA',
			nombre: 'Receptor de nómina de prueba',
			domicilioFiscalReceptor: '72530',
			regimenFiscalReceptor: '605',
			usoCFDI: 'CN01',
		},
		conceptos: [
			{
				claveProdServ: '84111505',
				cantidad: 1,
				claveUnidad: 'ACT',
				descripcion: 'Pago de nómina',
				valorUnitario: 5000,
				importe: 5000,
				descuento: 250,
				objetoImp: '01',
			},
		],
	},
	complementos: [
		{
			tipo: 'nomina12',
			data: {
				tipoNomina: 'O',
				fechaPago: '2026-08-31',
				fechaInicialPago: '2026-08-16',
				fechaFinalPago: '2026-08-31',
				numDiasPagados: 15,
				emisor: { registroPatronal: 'Y4646861106' },
				receptor: {
					curp: 'XEXX010101HNEXXXA4',
					tipoRegimen: '02',
					numEmpleado: '001',
					tipoContrato: '01',
					periodicidadPago: '04',
					claveEntFed: 'CMX',
				},
				percepciones: [
					{
						tipoPercepcion: '001',
						clave: '001',
						concepto: 'Sueldo',
						importeGravado: 5000,
						importeExento: 0,
					},
				],
				totalSueldos: 5000,
				totalGravado: 5000,
				totalExento: 0,
			},
		},
	],
});

El tipo Complemento (exportado por el SDK) es un discriminated union sobre tipo — cuando Multipac soporte un complemento nuevo, el SDK le agrega su propio miembro sin tocar el resto de los tipos de timbrar().

API

| Método | Descripción | | ---------------------------------------------- | -------------------------------------------------------------------------- | | client.timbrar(input) | Timbra un CFDI 4.0 | | client.cancelar(uuidCfdi, input) | Cancela un CFDI timbrado | | client.consultarEstado(uuidCfdi, params) | Consulta el estado de un CFDI ante el PAC | | client.recuperarXml(uuidCfdi, params?) | Recupera el XML timbrado | | client.consultarOperacion(operacionId) | Consulta el estado de una operación (útil en modo asíncrono) | | esperarOperacion(client, operacionId, opts?) | Hace polling de una operación asíncrona hasta que llegue a un estado final |

Manejo de errores

Cada operación puede lanzar una subclase de MultipacError. Los errores de negocio del catálogo de Multipac (ConceptoInconsistenteError, CsdNoVigenteError, CreditosAgotadosError, etc.) exponen un codigo estable — ConceptoInconsistenteError (CONCEPTO_INCONSISTENTE) es el más frecuente al timbrar: sale cuando algo en comprobante/complementos no cuadra aritméticamente:

import {
	ConceptoInconsistenteError,
	CreditosAgotadosError,
	MultipacError,
} from '@quo-digital/multipac-sdk';

try {
	await client.timbrar({ comprobante });
} catch (err) {
	if (err instanceof ConceptoInconsistenteError) {
		// un total/importe declarado no cuadra con el detalle — revisa err.message
	} else if (err instanceof CreditosAgotadosError) {
		// manejar saldo agotado
	} else if (err instanceof MultipacError) {
		// cualquier otro error del SDK
	}
}

Un input que no cumple la forma esperada (falta comprobante, un campo requerido, un RFC mal formado, …) nunca llega a la red: client.timbrar() lanza MultipacValidationError de inmediato.

Reintentos

429/502 se reintentan automáticamente (con backoff) tanto en GET como en POST, porque son respuestas del servidor que confirman que la operación no se completó. Un fallo de red puro (timeout, conexión perdida) solo se reintenta en operaciones de lectura (consultarEstado, recuperarXml, consultarOperacion). timbrar/cancelar nunca reintentan un fallo de red: si la respuesta se pierde, no hay forma de saber si el servidor ya procesó la operación, y reintentar podría timbrar el mismo CFDI dos veces o duplicar una cancelación. Ante un MultipacNetworkError de timbrar/cancelar, verifica el estado real con client.consultarOperacion() antes de reintentar manualmente.

Desarrollo

Requiere Node.js 18+ y npm.

git clone [email protected]:excel/multipac/sdk.git
cd sdk
npm install

npm install corre prepare (Husky) y deja activados los hooks de pre-commit (lint-staged) y pre-push.

Scripts

| Comando | Qué hace | | ---------------------- | ------------------------------------------------------------------------ | | npm run dev | Build en modo watch (tsup --watch) | | npm run build | Build dual ESM/CJS + .d.ts en dist/ | | npm run typecheck | Chequeo de tipos sin emitir (tsc --noEmit) | | npm run lint | ESLint con --fix | | npm run lint:check | ESLint sin modificar archivos (el que corre en CI) | | npm run format | Prettier con --write | | npm run format:check | Prettier en modo check (el que corre en CI) | | npm test | Corre la suite de tests (Vitest) | | npm run test:watch | Vitest en modo watch | | npm run test:cov | Suite + cobertura (gate de CI: 80% líneas/branches/funciones/statements) |

Tests

La suite usa msw para mockear la API de Multipac — no necesitas credenciales reales, sandbox, ni red para correrla. Los mocks viven en src/__mocks__/.

Releases

El versionado y el publish a npm son automáticos vía CI, gatillados por changeset:

  1. En tu PR, si el cambio debe reflejarse en una nueva versión, corre npx changeset — te pregunta el tipo de bump (patch/minor/major) y una descripción; esto crea un archivo en .changeset/. Cambios internos que no afectan al consumidor (CI, tests, docs) no necesitan uno.
  2. Al mergear a main, el job version consume los changesets pendientes, bumpea package.json, actualiza CHANGELOG.md, y commitea ese cambio de vuelta a main.
  3. Ese commit dispara un nuevo pipeline: el job publish detecta la versión nueva (aún no está en el registro) y la publica. Un push sin changesets pendientes no genera versión nueva — el pipeline queda verde sin publicar nada.

Licencia

MIT