# Hacienda Cr

> TypeScript SDK, CLI & MCP Server for Costa Rica electronic invoicing (Hacienda API)

- **Type:** MCP server
- **Install:** `agentstack add mcp-dojocodinglabs-hacienda-cr`
- **Verified:** Pending review
- **Seller:** [DojoCodingLabs](https://agentstack.voostack.com/s/dojocodinglabs)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [DojoCodingLabs](https://github.com/DojoCodingLabs)
- **Source:** https://github.com/DojoCodingLabs/hacienda-cr

## Install

```sh
agentstack add mcp-dojocodinglabs-hacienda-cr
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# hacienda-cr — Facturación Electrónica Costa Rica

**El toolkit open-source más completo para facturación electrónica en Costa Rica.**\
SDK + CLI + Servidor MCP para emitir comprobantes electrónicos contra la API v4.4 del Ministerio de Hacienda.

[](https://www.npmjs.com/package/@dojocoding/hacienda-sdk)
[](https://github.com/DojoCodingLabs/hacienda-cr/actions/workflows/ci.yml)
[](LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org)

---

## ¿Por qué hacienda-cr?

Emitir facturas electrónicas en Costa Rica no debería ser un dolor de cabeza. Entre la autenticación OAuth2, la generación de XML con namespaces específicos, la firma digital XAdES-EPES, la clave numérica de 50 dígitos y el polling del estado... hay demasiada complejidad accidental.

**hacienda-cr** resuelve todo eso en un solo toolkit:

- **SDK** — Librería TypeScript con tipado estricto: auth, XML, firma digital, cálculo de IVA, envío y consulta.
- **CLI** — Herramienta de línea de comandos `hacienda` para emitir, firmar, validar y consultar desde la terminal.
- **MCP Server** — Servidor de Model Context Protocol para que asistentes de IA (Claude, etc.) emitan facturas por vos.

> Funciona con los 7 tipos de comprobante + Mensaje Receptor. Compatible con sandbox y producción.

---

## Empezá en 2 minutos

### Opción 1: SDK (para desarrolladores)

```bash
npm install @dojocoding/hacienda-sdk
```

```ts
import { HaciendaClient, DocumentType, Situation } from "@dojocoding/hacienda-sdk";

// 1. Crear el cliente
const client = new HaciendaClient({
  environment: "sandbox",
  credentials: {
    idType: "02", // Cédula Jurídica
    idNumber: "3101234567",
    password: process.env.HACIENDA_PASSWORD!,
  },
});

// 2. Autenticarse
await client.authenticate();

// 3. Generar la clave numérica
const clave = client.buildClave({
  date: new Date(),
  taxpayerId: "3101234567",
  documentType: DocumentType.FACTURA_ELECTRONICA,
  sequence: 1,
  situation: Situation.NORMAL,
});

// 4. Construir XML, firmar y enviar (ver ejemplo completo abajo)
```

### Opción 2: CLI (para facturar desde la terminal)

```bash
npm install -g @dojocoding/hacienda-cli

# Autenticarse
hacienda auth login --cedula-type 02 --cedula 3101234567

# Crear borrador interactivo
hacienda draft --interactive

# Validar antes de enviar
hacienda validate factura.json

# Enviar (vista previa primero)
hacienda submit factura.json --dry-run

# Consultar contribuyente
hacienda lookup 3101234567
```

### Opción 3: MCP Server (para asistentes de IA)

```bash
npm install -g @dojocoding/hacienda-mcp
hacienda-mcp
```

Le podés decir a Claude: _"Creá una factura de Mi Empresa S.A. (cédula 3101234567) a Cliente S.R.L. (cédula 3109876543) por 2 horas de consultoría a ₡50.000 cada una con IVA del 13%."_

---

## Tipos de comprobante soportados

| Código | Tipo de comprobante                   | Builder del SDK                |
| ------ | ------------------------------------- | ------------------------------ |
| `01`   | Factura Electrónica                   | `buildFacturaXml()`            |
| `02`   | Nota de Débito Electrónica            | `buildNotaDebitoXml()`         |
| `03`   | Nota de Crédito Electrónica           | `buildNotaCreditoXml()`        |
| `04`   | Tiquete Electrónico                   | `buildTiqueteXml()`            |
| `05`   | Factura Electrónica de Compra         | `buildFacturaCompraXml()`      |
| `06`   | Factura Electrónica de Exportación    | `buildFacturaExportacionXml()` |
| `07`   | Recibo Electrónico de Pago            | `buildReciboPagoXml()`         |
| —      | Mensaje Receptor (aceptación/rechazo) | `buildMensajeReceptorXml()`    |

---

## Tabla de contenidos

- [SDK — Documentación completa](#sdk--documentación-completa)
  - [HaciendaClient](#haciendaclient)
  - [Autenticación OAuth2](#autenticación-oauth2)
  - [Creación de documentos](#creación-de-documentos)
  - [Cálculo de IVA](#cálculo-de-iva)
  - [Clave numérica](#clave-numérica)
  - [Firma digital XAdES-EPES](#firma-digital-xades-epes)
  - [Envío y consulta de estado](#envío-y-consulta-de-estado)
  - [Consulta de contribuyentes](#consulta-de-contribuyentes)
  - [Gestión de configuración](#gestión-de-configuración)
  - [Logging estructurado](#logging-estructurado)
  - [Manejo de errores](#manejo-de-errores)
- [CLI — Referencia de comandos](#cli--referencia-de-comandos)
- [MCP Server — Integración con IA](#mcp-server--integración-con-ia)
- [Desarrollo](#desarrollo)
- [Licencia](#licencia)

---

## SDK — Documentación completa

### HaciendaClient

El punto de entrada principal. Orquesta autenticación, generación de claves y operaciones con la API.

```ts
import { HaciendaClient } from "@dojocoding/hacienda-sdk";

const client = new HaciendaClient({
  // Requerido
  environment: "sandbox", // "sandbox" | "production"
  credentials: {
    idType: "02", // "01"=Física, "02"=Jurídica, "03"=DIMEX, "04"=NITE
    idNumber: "3101234567", // Cédula de 9-12 dígitos
    password: process.env.HACIENDA_PASSWORD!,
  },

  // Opcional
  p12Path: "/ruta/al/certificado.p12", // Para firma digital
  p12Pin: process.env.HACIENDA_P12_PIN, // PIN del .p12
  fetchFn: customFetch, // Implementación fetch personalizada
});
```

Las opciones se validan al instanciar con Zod. Si algo está mal, lanza `ValidationError` con detalles claros.

### Autenticación OAuth2

Hacienda usa OAuth2 ROPC (Resource Owner Password Credentials). El SDK maneja todo el ciclo de vida del token automáticamente.

```ts
// Autenticarse (obtiene access + refresh token)
await client.authenticate();

// Verificar estado
console.log(client.isAuthenticated); // true

// Obtener token válido (refresca automáticamente si expiró)
const token = await client.getAccessToken();

// Forzar re-autenticación
client.invalidate();
await client.authenticate();
```

**Ciclo de vida del token:**

- Access token expira en ~5 minutos (se cachea en memoria, se refresca 30s antes)
- Refresh token dura ~10 horas
- `getAccessToken()` maneja el refresh de forma transparente

**Ambientes de Hacienda:**

| Ambiente     | URL base de la API                                         | IDP Realm  | Client ID  |
| ------------ | ---------------------------------------------------------- | ---------- | ---------- |
| `sandbox`    | `api.comprobanteselectronicos.go.cr/recepcion-sandbox/v1/` | `rut-stag` | `api-stag` |
| `production` | `api.comprobanteselectronicos.go.cr/recepcion/v1/`         | `rut`      | `api-prod` |

### Creación de documentos

Ejemplo completo de una Factura Electrónica — el flujo es igual para los demás tipos:

```ts
import {
  buildFacturaXml,
  calculateLineItemTotals,
  calculateInvoiceSummary,
  buildClave,
  DocumentType,
  Situation,
} from "@dojocoding/hacienda-sdk";
import type { LineItemInput } from "@dojocoding/hacienda-sdk";

// 1. Definir las líneas de detalle
const lineas: LineItemInput[] = [
  {
    numeroLinea: 1,
    codigoCabys: "8310100000000", // Código CABYS (13 dígitos)
    cantidad: 2,
    unidadMedida: "Unid",
    detalle: "Servicios de desarrollo web",
    precioUnitario: 50000,
    esServicio: true,
    impuesto: [
      {
        codigo: "01", // IVA
        codigoTarifaIVA: "08", // Tarifa general 13%
        tarifa: 13,
      },
    ],
  },
  {
    numeroLinea: 2,
    codigoCabys: "4321000000000",
    cantidad: 1,
    unidadMedida: "Unid",
    detalle: "Laptop",
    precioUnitario: 500000,
    esServicio: false,
    impuesto: [
      {
        codigo: "01",
        codigoTarifaIVA: "08",
        tarifa: 13,
      },
    ],
    descuento: [
      {
        montoDescuento: 25000,
        codigoDescuento: "01",
        naturalezaDescuento: "Descuento por volumen",
      },
    ],
  },
];

// 2. Calcular totales por línea (agrega montoTotal, subTotal, impuestoNeto, etc.)
const lineasCalculadas = lineas.map(calculateLineItemTotals);

// 3. Calcular resumen de factura (ResumenFactura)
const resumen = calculateInvoiceSummary(lineasCalculadas);

// 4. Generar la clave numérica
const clave = buildClave({
  date: new Date(),
  taxpayerId: "3101234567",
  documentType: DocumentType.FACTURA_ELECTRONICA,
  sequence: 1,
  situation: Situation.NORMAL,
});

// 5. Consecutivo
const numeroConsecutivo = "00100001010000000001";

// 6. Armar la factura y generar XML
const factura = {
  clave,
  proveedorSistemas: "3101234567", // Cédula del proveedor de sistemas (v4.4)
  codigoActividadEmisor: "620100",
  numeroConsecutivo,
  fechaEmision: new Date().toISOString(),
  emisor: {
    nombre: "Mi Empresa S.A.",
    identificacion: { tipo: "02", numero: "3101234567" },
    ubicacion: {
      provincia: "1",
      canton: "01",
      distrito: "01",
      otrasSenas: "100m norte del parque central",
    },
    correoElectronico: "facturacion@miempresa.co.cr",
  },
  receptor: {
    nombre: "Cliente S.R.L.",
    identificacion: { tipo: "02", numero: "3109876543" },
    correoElectronico: "pagos@cliente.co.cr",
  },
  condicionVenta: "01", // Contado
  detalleServicio: lineasCalculadas,
  resumenFactura: {
    ...resumen,
    // v4.4: los medios de pago van dentro del ResumenFactura, con monto
    medioPago: [{ tipoMedioPago: "01", totalMedioPago: resumen.totalComprobante }],
  },
};

const xml = buildFacturaXml(factura);
```

**Validación de XML:**

```ts
import { validateFacturaInput } from "@dojocoding/hacienda-sdk";

const resultado = validateFacturaInput(datosFactura);
if (!resultado.valid) {
  for (const err of resultado.errors) {
    console.error(`${err.path}: ${err.message}`);
  }
}
```

### Cálculo de IVA

Utilidades para calcular impuestos, totales por línea y resúmenes según la normativa de Hacienda. Todos los montos se redondean a 5 decimales.

```ts
import { round5, calculateLineItemTotals, calculateInvoiceSummary } from "@dojocoding/hacienda-sdk";
import type { LineItemInput, CalculatedLineItem, InvoiceSummary } from "@dojocoding/hacienda-sdk";

const item: LineItemInput = {
  numeroLinea: 1,
  codigoCabys: "8310100000000",
  cantidad: 3,
  unidadMedida: "Sp",
  detalle: "Horas de consultoría",
  precioUnitario: 75000,
  esServicio: true,
  impuesto: [{ codigo: "01", codigoTarifaIVA: "08", tarifa: 13 }],
};

const calculado: CalculatedLineItem = calculateLineItemTotals(item);
// calculado.montoTotal      = 225000       (3 × ₡75.000)
// calculado.subTotal        = 225000       (sin descuentos)
// calculado.impuestoNeto    = 29250        (₡225.000 × 13%)
// calculado.montoTotalLinea = 254250       (₡225.000 + ₡29.250)

const resumen: InvoiceSummary = calculateInvoiceSummary([calculado]);
// resumen.totalServGravados  = 225000
// resumen.totalImpuesto      = 29250
// resumen.totalComprobante   = 254250
```

**Exoneraciones de IVA:**

```ts
const itemExonerado: LineItemInput = {
  // ...campos base
  impuesto: [
    {
      codigo: "01",
      codigoTarifaIVA: "08",
      tarifa: 13,
      exoneracion: {
        tipoDocumento: "01",
        numeroDocumento: "AL-001-2025",
        nombreInstitucion: "99", // código de institución (Nota v4.4)
        fechaEmision: "2025-01-01T00:00:00",
        tarifaExonerada: 13, // puntos de tarifa exonerados
      },
    },
  ],
};
```

**Tarifas de IVA soportadas:** 0%, 0.5%, 1%, 2%, 4%, 8%, 13% (códigos 01-11 de la v4.4)

### Clave numérica

Cada comprobante electrónico requiere una clave numérica única de 50 dígitos. El SDK la genera y parsea automáticamente.

**Estructura:** `[506][DDMMYY][cédula 12 dígitos][sucursal 3][terminal 5][tipo doc 2][consecutivo 10][situación 1][código seguridad 8]`

```ts
import { buildClave, parseClave, DocumentType, Situation } from "@dojocoding/hacienda-sdk";

// Generar clave
const clave = buildClave({
  date: new Date("2025-07-15"),
  taxpayerId: "3101234567",
  documentType: DocumentType.FACTURA_ELECTRONICA,
  sequence: 42,
  situation: Situation.NORMAL,
  branch: "001", // Opcional, default "001"
  pos: "00001", // Opcional, default "00001"
});
// => "50615072500310123456700100001010000000042112345678"

// Parsear clave existente
const parsed = parseClave(clave);
// parsed.countryCode   => "506"
// parsed.date          => Date(2025-07-15)
// parsed.taxpayerId    => "003101234567"
// parsed.documentType  => "01"
// parsed.sequence      => 42
// parsed.situation     => "1"
// parsed.securityCode  => "12345678"
```

**Códigos de situación:**

- `1` Normal (envío estándar en línea)
- `2` Contingencia (fallo del sistema de Hacienda)
- `3` Sin Internet (fuera de línea)

### Firma digital XAdES-EPES

Todo XML enviado a Hacienda debe estar firmado con XAdES-EPES usando el certificado `.p12` del contribuyente (RSA 2048 + SHA-256). El SDK maneja todo el proceso de firma.

```ts
import { readFileSync } from "node:fs";
import { signXml, signAndEncode, loadP12 } from "@dojocoding/hacienda-sdk";

const p12Buffer = readFileSync("/ruta/al/certificado.p12");
const pin = process.env.HACIENDA_P12_PIN!;

// Firmar XML (retorna XML firmado como string)
const xmlFirmado = await signXml(xml, p12Buffer, pin);

// Firmar y codificar en Base64 (listo para enviar a la API)
const xmlBase64 = await signAndEncode(xml, p12Buffer, pin);

// Cargar .p12 para inspeccionar el certificado
const credenciales = await loadP12(p12Buffer, pin);
// credenciales.privateKey      — CryptoKey para firma
// credenciales.certificateDer  — Certificado codificado en DER
```

### Envío y consulta de estado

**Opción simplificada — `submitAndWait` (recomendada):**

Envía el documento y espera a que Hacienda lo procese. Maneja el polling automáticamente.

```ts
import { submitAndWait, HttpClient } from "@dojocoding/hacienda-sdk";

const httpClient = new HttpClient({
  baseUrl: "https://api.comprobanteselectronicos.go.cr/recepcion-sandbox/v1",
  getToken: () => client.getAccessToken(),
});

const resultado = await submitAndWait(
  httpClient,
  {
    clave: "50601...",
    fecha: new Date().toISOString(),
    emisor: {
      tipoIdentificacion: "02",
      numeroIdentificacion: "3101234567",
    },
    comprobanteXml: xmlBase64Firmado,
  },
  {
    pollIntervalMs: 3000, // Consultar cada 3 segundos (default)
    timeoutMs: 60000, // Timeout a 60 segundos (default)
    onPoll: (status, intento) => {
      console.log(`Intento ${intento}: ${status.status}`);
    },
  },
);

if (resultado.accepted) {
  console.log("¡Comprobante aceptado por Hacienda!");
} else {
  console.log("Rechazado:", resultado.rejectionReason);
}
```

**Opción granular — control total:**

```ts
import { submitDocument, getStatus, isTerminalStatus } from "@dojocoding/hacienda-sdk";

// Enviar
const response = await submitDocument(httpClient, solicitud);

// Consultar estado
const status = await getStatus(httpClient, "50601...");
if (isTerminalStatus(status.status)) {
  console.log("Estado final:", status.status);
}
```

**Listar y consultar comprobantes:**

```ts
import { listComprobantes, getComprobante } from "@dojocoding/hacienda-sdk";

const lista = await listComprobantes(httpClient, {
  offset: 0,
  limit: 10,
  fechaEmisionDesde: "2025-01-01",
  fechaEmisionHasta: "2025-12-31",
});

const detalle = await getComprobante(httpClient, "50601...");
```

**Reintentos con backoff exponencial:**

```ts
import { withRetry } from "@dojocoding/hacienda-sdk";

const resultado = await withRetry(() => submitDocument(httpClient, solicitud), {
  maxAttempts: 3,
  delayMs: 1000,
  backoff: "exponential",
});
```

### Consulta de contribuyentes

Buscá información de cualquier contribuyente usando la API pública de actividades económicas de Hacienda (no requiere autenticación):

```ts
import { lookupTaxpayer } from "@dojocoding/hacienda-sdk";

const info = await lookupTaxpayer("3101234567");
console.log(info.nombre); // "MI EMPRESA S.A."
console.log(info.tipoIdentificacion); // "02"
for (const actividad of info.actividades) {
  console.log(`${actividad.codigo}: ${actividad.descripcion} (${actividad.estado})`);
}
```

### Gestión de configuración

La configuración se almacena en `~/.hacienda-cr/config.toml` con soporte para múltiples perfiles (ej: sandbox, producci

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [DojoCodingLabs](https://github.com/DojoCodingLabs)
- **Source:** [DojoCodingLabs/hacienda-cr](https://github.com/DojoCodingLabs/hacienda-cr)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-dojocodinglabs-hacienda-cr
- Seller: https://agentstack.voostack.com/s/dojocodinglabs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
