Primera factura en sandbox
El objetivo de la primera prueba no es obtener un número “bonito”. Es demostrar que tu sistema puede crear una operación una sola vez, conservar sus identificadores y llegar a un resultado terminal reproducible.
Antes de comenzar
Sección titulada «Antes de comenzar»Necesitas:
- acceso habilitado al piloto;
- credencial de sandbox;
- contribuyente y tipos e-CF asignados;
- un
external_idque tu sistema no reutilizará; - un proceso de consulta que conserve los identificadores devueltos;
- acceso a la referencia contractual.
No necesitas configurar prefijo comercial, numeración, dirección pública, certificado ni secuencias. CierreListo toma el RNC y la razón social del contribuyente asignado a la credencial y usa una dirección sintética exclusivamente para ejecutar el preflight interno. Nada de esa simulación se convierte en una factura fiscal ni se promueve a otro ambiente.
Recorrido
Sección titulada «Recorrido»-
Construye una operación mínima
Incluye fecha, tipo de comprobante, receptor, moneda, líneas, impuestos, descuentos, pagos y totales declarados según el modelo publicado.
-
Asigna identidad
Conserva tu
external_idy genera unaIdempotency-Keyexclusiva para la intención de crear esa factura. -
Usa la operación publicada
Abre “Crear factura” en la referencia API. Copia el ejemplo generado desde OpenAPI y sustituye únicamente los valores del fixture.
-
Interpreta la respuesta inicial
El
202confirma que CierreListo registró la intención. En sandbox,status: acceptedidentifica un resultado sintético de validación; nunca significa que la DGII aceptó el comprobante. -
Persiste referencias
Guarda
external_id, la clave de idempotencia, los identificadores devueltos yrequest_idantes de esperar el resultado. -
Sigue el ciclo
Consulta la factura o búscala por
external_id. Sandbox v1 no genera eventos webhook. -
Reconcilia
Compara tu operación, el recurso de CierreListo y el resultado esperado del fixture.
Pruebas mínimas del sandbox
Sección titulada «Pruebas mínimas del sandbox»Ejecuta al menos:
- una operación aceptada;
- una validación fallida;
- la misma solicitud y misma clave;
- la misma clave con cuerpo distinto;
- la consulta por
external_id; - la consulta del documento fiscal sintético y el rechazo esperado de sus artefactos.
El documento sintético de sandbox conserva identificadores y estados para reconciliación, pero sus
endpoints XML/PDF responden 409 artifact_not_ready: sandbox no reserva e-NCF, no firma XML y no
debe fabricar artefactos con apariencia fiscal.
Criterio de salida
Sección titulada «Criterio de salida»La integración está lista para avanzar cuando puede reconstruir el historial de una operación sin depender de una pantalla ni de un log completo. En el siguiente ambiente también debe tolerar eventos repetidos y reconciliar el estado actual por API.
Configura el entorno
Sección titulada «Configura el entorno»Los ejemplos descargables se regeneran desde el mismo openapi.json que construye la referencia.
Necesitan una API key y el identificador del contribuyente asignado a esa integración:
export CIERRELISTO_API_KEY="fh_<prefijo>_<secreto>"export CIERRELISTO_TAXPAYER_ID="uuid-del-contribuyente"CIERRELISTO_API_URL usa el sandbox por defecto. No reutilices una credencial de otro ambiente.
Ejemplo cURL · e-CF 31
Sección titulada «Ejemplo cURL · e-CF 31»El tipo 31 representa una factura de crédito fiscal dentro del alcance actual de la API pública.
#!/usr/bin/env bash
set -euo pipefail
: "${CIERRELISTO_API_KEY:?Define CIERRELISTO_API_KEY}"
: "${CIERRELISTO_TAXPAYER_ID:?Define CIERRELISTO_TAXPAYER_ID}"
CIERRELISTO_API_URL="${CIERRELISTO_API_URL:-https://sandbox.api.cierrelisto.com}"
CIERRELISTO_EXTERNAL_ID="${CIERRELISTO_EXTERNAL_ID:-sandbox-31-$(date +%s)}"
CIERRELISTO_IDEMPOTENCY_KEY="${CIERRELISTO_IDEMPOTENCY_KEY:-$CIERRELISTO_EXTERNAL_ID}"
curl --fail-with-body --silent --show-error \
--request POST \
--url "$CIERRELISTO_API_URL/v1/taxpayers/$CIERRELISTO_TAXPAYER_ID/invoices" \
--header "Authorization: Bearer $CIERRELISTO_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: $CIERRELISTO_IDEMPOTENCY_KEY" \
--data @- <<JSON
{
"external_id": "$CIERRELISTO_EXTERNAL_ID",
"ecf_type": "31",
"issue_date": "2026-07-29",
"due_date": "2026-08-28",
"currency": "DOP",
"language": "es",
"payment_type": "credit",
"buyer": {
"legal_name": "CLIENTE DE PRUEBA SRL",
"tax_id": "131246796",
"email": "cuentas@cliente.example",
"address": {
"line1": "Av. Abraham Lincoln 1001",
"city": "Santo Domingo de Guzmán",
"province_or_state": "Distrito Nacional",
"country_code": "DO"
}
},
"lines": [
{
"line_id": "1",
"description": "Servicios profesionales de julio de 2026",
"sku": "SERV-CONT-001",
"unit": "servicio",
"item_kind": "service",
"billing_indicator": 1,
"unit_code": 28,
"quantity": "1",
"unit_price": "70000.00",
"discount": null,
"taxes": [
{
"code": "ITBIS",
"label": "ITBIS 18%",
"rate_percent": "18"
}
]
}
],
"declared_totals": {
"subtotal": "70000.00",
"discount_total": "0.00",
"tax_total": "12600.00",
"total": "82600.00"
},
"payment_terms": "Pago por transferencia dentro de 30 días."
}
JSON
Ejecútalo con:
./create-invoice-31.shEjemplo cURL · e-CF 32
Sección titulada «Ejemplo cURL · e-CF 32»El tipo 32 usa el mismo contrato estructurado. El servidor aplica las reglas del tipo seleccionado.
#!/usr/bin/env bash
set -euo pipefail
: "${CIERRELISTO_API_KEY:?Define CIERRELISTO_API_KEY}"
: "${CIERRELISTO_TAXPAYER_ID:?Define CIERRELISTO_TAXPAYER_ID}"
CIERRELISTO_API_URL="${CIERRELISTO_API_URL:-https://sandbox.api.cierrelisto.com}"
CIERRELISTO_EXTERNAL_ID="${CIERRELISTO_EXTERNAL_ID:-sandbox-32-$(date +%s)}"
CIERRELISTO_IDEMPOTENCY_KEY="${CIERRELISTO_IDEMPOTENCY_KEY:-$CIERRELISTO_EXTERNAL_ID}"
curl --fail-with-body --silent --show-error \
--request POST \
--url "$CIERRELISTO_API_URL/v1/taxpayers/$CIERRELISTO_TAXPAYER_ID/invoices" \
--header "Authorization: Bearer $CIERRELISTO_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: $CIERRELISTO_IDEMPOTENCY_KEY" \
--data @- <<JSON
{
"external_id": "$CIERRELISTO_EXTERNAL_ID",
"ecf_type": "32",
"issue_date": "2026-07-29",
"due_date": "2026-08-28",
"currency": "DOP",
"language": "es",
"payment_type": "credit",
"buyer": {
"legal_name": "CLIENTE DE PRUEBA SRL",
"tax_id": "131246796",
"email": "cuentas@cliente.example",
"address": {
"line1": "Av. Abraham Lincoln 1001",
"city": "Santo Domingo de Guzmán",
"province_or_state": "Distrito Nacional",
"country_code": "DO"
}
},
"lines": [
{
"line_id": "1",
"description": "Servicios profesionales de julio de 2026",
"sku": "SERV-CONT-001",
"unit": "servicio",
"item_kind": "service",
"billing_indicator": 1,
"unit_code": 28,
"quantity": "1",
"unit_price": "70000.00",
"discount": null,
"taxes": [
{
"code": "ITBIS",
"label": "ITBIS 18%",
"rate_percent": "18"
}
]
}
],
"declared_totals": {
"subtotal": "70000.00",
"discount_total": "0.00",
"tax_total": "12600.00",
"total": "82600.00"
},
"payment_terms": "Pago por transferencia dentro de 30 días."
}
JSON
Ejemplo TypeScript · e-CF 31 o 32
Sección titulada «Ejemplo TypeScript · e-CF 31 o 32»El script usa el cliente oficial tipado y funciona en Node.js 22 o posterior. El SDK valida la solicitud antes de enviarla, valida la respuesta contra los mismos schemas Zod que generan OpenAPI y distingue errores del API, validación local, transporte y respuestas fuera de contrato.
Durante el piloto, instala el artefacto .tgz versionado que recibiste en tu onboarding:
pnpm add ./cierrelisto-sdk-0.1.0.tgzEl nombre de importación continúa siendo @cierrelisto/sdk. La instalación directa desde el
registro se documentará cuando esa versión esté publicada.
Usa 31 por defecto; cambia el tipo con CIERRELISTO_ECF_TYPE=32.
/* biome-ignore-all lint/suspicious/noConsole: This executable CLI example reports its result to stdout. */
import {
CierreListoClient,
type CierreListoFailure,
type CreateInvoiceRequest,
type PublicEcfTypeCode,
} from "@cierrelisto/sdk";
function requiredEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Falta la variable ${name}`);
return value;
}
const apiUrl = process.env.CIERRELISTO_API_URL ?? "https://sandbox.api.cierrelisto.com";
const apiKey = requiredEnv("CIERRELISTO_API_KEY");
const taxpayerId = requiredEnv("CIERRELISTO_TAXPAYER_ID");
const externalId = process.env.CIERRELISTO_EXTERNAL_ID ?? `sandbox-${crypto.randomUUID()}`;
const idempotencyKey = process.env.CIERRELISTO_IDEMPOTENCY_KEY ?? externalId;
const configuredType = process.env.CIERRELISTO_ECF_TYPE ?? "31";
if (configuredType !== "31" && configuredType !== "32") {
throw new Error("CIERRELISTO_ECF_TYPE debe ser 31 o 32");
}
function describeFailure(failure: CierreListoFailure): string {
switch (failure.kind) {
case "api_error":
return `${failure.error.code}: ${failure.error.message} (${failure.http.requestId})`;
case "client_validation_error":
return `${failure.message}\n${JSON.stringify(failure.issues, null, 2)}`;
case "invalid_response":
return `${failure.message} (HTTP ${failure.status}, ${failure.requestId ?? "sin request_id"})`;
case "transport_error":
return `No fue posible conectar con CierreListo: ${failure.message}`;
}
}
const ecfType: PublicEcfTypeCode = configuredType;
const payload = {
external_id: externalId,
ecf_type: ecfType,
issue_date: "2026-07-29",
due_date: "2026-08-28",
currency: "DOP",
language: "es",
payment_type: "credit",
buyer: {
legal_name: "CLIENTE DE PRUEBA SRL",
tax_id: "131246796",
email: "cuentas@cliente.example",
address: {
line1: "Av. Abraham Lincoln 1001",
city: "Santo Domingo de Guzmán",
province_or_state: "Distrito Nacional",
country_code: "DO",
},
},
lines: [
{
line_id: "1",
description: "Servicios profesionales de julio de 2026",
sku: "SERV-CONT-001",
unit: "servicio",
item_kind: "service",
billing_indicator: 1,
unit_code: 28,
quantity: "1",
unit_price: "70000.00",
discount: null,
taxes: [
{
code: "ITBIS",
label: "ITBIS 18%",
rate_percent: "18",
},
],
},
],
declared_totals: {
subtotal: "70000.00",
discount_total: "0.00",
tax_total: "12600.00",
total: "82600.00",
},
payment_terms: "Pago por transferencia dentro de 30 días.",
} satisfies CreateInvoiceRequest;
const client =
apiUrl === "https://sandbox.api.cierrelisto.com"
? new CierreListoClient({ apiKey, environment: "sandbox" })
: new CierreListoClient({ apiKey, baseUrl: apiUrl });
const result = await client.createInvoice({
taxpayerId,
idempotencyKey,
invoice: payload,
});
if (!result.ok) {
console.error(describeFailure(result));
process.exitCode = 1;
} else {
console.log(JSON.stringify(result.body, null, 2));
console.log(`request_id: ${result.http.requestId}`);
if (result.http.idempotencyReplayed) {
console.log("La respuesta fue recuperada de la operación idempotente original.");
}
}
CIERRELISTO_ECF_TYPE=32 pnpm dlx tsx create-invoice.ts