SDK y modelos: TypeScript
Interfaces sin dependencias: funcionan en Node, Bun o el navegador. Copia el archivo a tu proyecto e impórtalo donde construyas o leas comprobantes.
Descargar
Sección titulada «Descargar»- ecf.ts: emisión de e-CF, con sus tipos anidados.
- novafe-models-typescript.zip: todas las áreas de la API.
- documento OpenAPI: el contrato completo, para generar tus propios modelos.
Ejemplo: emitir un comprobante
Sección titulada «Ejemplo: emitir un comprobante»import type { EcfDto, IssueEcfCommand } from "./ecf";
const payload: IssueEcfCommand = { type: 31, incomeType: "01", internalNumber: "FAC-2026-00042", buyer: { rnc: "131880681", name: "Mi Cliente SRL" }, payment: { condition: "credit", dueDate: "15-03-2026", methods: [{ type: "check_transfer", amount: 2360 }], }, lines: [ { name: "Servicio de consultoría", kind: "service", quantity: 1, unitOfMeasure: "43", unitPrice: 2000, itbisRate: 1, }, ],};
const response = await fetch("https://api.novafe.example/api/v1/ecf", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": process.env.NOVAFE_API_KEY!, "Idempotency-Key": crypto.randomUUID(), }, body: JSON.stringify(payload),});
const ecf: EcfDto = await response.json();console.log(ecf.encf, ecf.status);Modelos de emisión
Sección titulada «Modelos de emisión»Todo el contenido de ecf.ts, listo para copiar. Incluye IssueEcfCommand (el cuerpo de POST /ecf), EcfDto (la respuesta) y sus tipos anidados.
// Generado desde el documento OpenAPI publico de NovaFE. No lo edites a mano: regeneralo con `bun run models`.
export interface DgiiMessage { code: number; value: string;}
/** Una entrada de `/{amb}/consultatrackids/api/trackids/consulta`. Puede haber más de una para el mismo e-NCF si se remitió varias veces. */export interface DgiiTrackIdEntry { trackId: string; estado: string; fechaRecepcion?: string | null;}
/** Texto libre para la Representación Impresa. */export interface EcfAdditionalInfoPayload { issuer?: string | null; buyer?: string | null;}
/** Desglose de impuestos adicionales por código de la Tabla I (001–039). Todos los montos los trae el cliente ya calculados. `iscEspecifico` (monto fijo por volumen: alcoholes 006-018, cigarrillos 019-022) integra la base imponible del ITBIS de esa línea; `iscAdvalorem` y `otros` (Propina, CDT, ISC de servicios, Primera Placa…) van "por encima", sin tocar la base. */export interface EcfAdditionalTaxPayload { code: string; rate?: number; iscEspecifico?: number; iscAdvalorem?: number; otros?: number;}
/** Un renglón del historial de envíos de un comprobante: cómo terminó un ciclo contra la DGII. Lo que emite `GET /api/v1/ecf/{id}/attempts`. */export interface EcfAttemptDto { /** 1, 2, 3… dentro del comprobante. */ attempt: number; /** En qué estado dejó el ciclo al comprobante: `accepted`, `accepted_conditional`, `rejected`, `review` o `failed`. */ outcome: string; /** `TrackId` de la DGII de este ciclo; ausente si no llegó a haber uno. */ trackId?: string | null; /** Código de estado de la DGII (1 aceptado, 2 rechazado, 4 aceptado condicional); ausente si no hubo veredicto. */ statusCode?: number | null; /** El `estado` textual de la DGII. */ statusText?: string | null; /** `secuenciaUtilizada` de la DGII: `false` = el e-NCF no se consumió; `true` o ausente = consumido. */ sequenceUsed?: boolean | null; /** Observaciones o motivo de rechazo de la DGII; o el motivo del fallo si no hubo veredicto. */ messages: DgiiMessage[]; /** Qué se decidió con un rechazo: `auto:<código>` (la DGII falló de forma transitoria y NovaFE reenvía el mismo XML solo, p. ej. `auto:4`) o `exhausted` (era reintentable pero se agotó el ladder y el rechazo quedó firme). Ausente en el resto de resultados. */ retryDecision?: string | null; /** SHA-256 (hex) del XML enviado en este ciclo. */ xmlSha256: string; /** Cuándo se registró el resultado. */ recordedAt: string;}
/** Bloque `<Comprador>`. `Rnc` y `ForeignId` son excluyentes. */export interface EcfBuyerPayload { name?: string | null; rnc?: string | null; foreignId?: string | null; email?: string | null; contact?: string | null; address?: string | null; municipality?: string | null; province?: string | null; additionalInfo?: string | null;}
/** Totales que calculó el cliente (en DOP). Se comparan con el cálculo de NovaFE (tolerancia RF-06.6); el valor de NovaFE es el que va al XML. Todo opcional. */export interface EcfDeclaredTotalsPayload { montoGravadoTotal?: number | null; montoExento?: number | null; totalItbis?: number | null; montoImpuestoAdicional?: number | null; montoTotal?: number | null;}
/** El intercambio con la DGII de un comprobante: lo que ella respondió más los instantes en que NovaFE registró cada paso. */export interface EcfDgiiExchange { /** Identificador que la DGII asigna al recibir el comprobante. */ trackId?: string | null; /** El `estado` textual de la DGII ("Aceptado", "Rechazado", "En Proceso"…); en el idioma en que lo manda ella. */ status?: string | null; /** Código de estado de la DGII: 1 aceptado · 2 rechazado · 3 en proceso · 4 aceptado condicional. */ statusCode?: number | null; /** `secuenciaUtilizada` de la DGII: `false` = el e-NCF no se consumió (firma/XML inválidos); `true` o ausente = consumido. */ sequenceUsed?: boolean | null; /** Observaciones o motivo de rechazo que devolvió la DGII. */ messages: DgiiMessage[]; /** Instante en que la DGII confirmó la recepción (hay string? EcfDgiiExchange.TrackId). */ submittedAt?: string | null; /** La `fechaRecepcion` que informó la DGII; ausente si no la dio (p. ej. RFCE). */ receivedAt?: string | null; /** Instante en que la DGII dio un resultado definitivo. */ processedAt?: string | null;}
/** Vista de un comprobante emitido (respuesta de `POST /ecf` y `GET /ecf/{id}`). Trae la identidad fiscal, el resumen comercial (comprador, monto total — igual que EcfSummaryDto) y el resultado del intercambio con la DGII (EcfDgiiExchange? EcfDto.Dgii). El desglose por línea y el resto del detalle comercial siguen viviendo solo en el XML firmado (`GET /ecf/{id}/xml`). */export interface EcfDto { id: string; status: string; encf: string; type: number; environment: string; sequenceExpiresOn?: string | null; issueDate: string; issuedAt: string; signedAt: string; securityCode: string; qrUrl: string; submitsRfce: boolean; internalNumber?: string | null; toleranceWarning?: string | null; signedDuringContingency: boolean; montoTotal: number; buyerRnc?: string | null; buyerName?: string | null; documentHash: string; /** Lo que pasó con la DGII: `trackId`, estado (texto y código), mensajes, `secuenciaUtilizada` y los instantes de envío, recepción y resolución. `null` mientras el comprobante no se haya enviado. */ dgii?: EcfDgiiExchange; /** El reintento automático en curso o ya hecho: cuántas veces NovaFE reenvió el mismo comprobante tras un rechazo transitorio de la DGII, el tope, y cuándo es el próximo intento. `null` mientras no haya habido ningún reintento. */ retry?: EcfRetry; /** Enlaces a los recursos relacionados del comprobante. */ links?: EcfLinks;}
/** Datos de exportación (solo tipo 46). */export interface EcfExportPayload { loadingPortName?: string | null; deliveryTerms?: string | null; totalFob?: number | null; insurance?: number | null; freight?: number | null; otherCharges?: number | null; totalCif?: number | null; customsRegime?: string | null; departurePortName?: string | null; unloadingPortName?: string | null;}
/** Facturación en divisa (`<OtraMoneda>`). El cliente trae los montos ya convertidos. */export interface EcfForeignCurrencyPayload { currency: string; exchangeRate: number; totals: EcfForeignCurrencyTotalsPayload;}
export interface EcfForeignCurrencyTotalsPayload { montoGravadoTotal?: number | null; montoGravadoI1?: number | null; montoGravadoI2?: number | null; montoGravadoI3?: number | null; montoExento?: number | null; totalItbis?: number | null; totalItbis1?: number | null; totalItbis2?: number | null; totalItbis3?: number | null; montoTotal?: number | null;}
/** Un descuento o recargo global (Sección D). */export interface EcfGlobalAdjustmentPayload { line: number; kind?: string; affectsItbisRate?: number; amount?: number; norm1007?: boolean; description?: string | null; percentage?: number | null;}
export interface EcfItemCodePayload { type: string; value: string;}
/** Campos opcionales del `<Item>` — passthrough. La derivación del ISC desde `alcoholDegrees`/`referenceQuantity` es un slice posterior; hoy el cliente trae el monto final del ISC en `additionalTaxes` (el ISC específico ahí sí integra la base del ITBIS — ver EcfAdditionalTaxPayload). */export interface EcfLineDetailsPayload { referenceQuantity?: number | null; referenceUnit?: string | null; subquantities?: EcfSubquantityPayload[] | null; alcoholDegrees?: number | null; referenceUnitPrice?: number | null; manufactureDate?: string | null; expiryDate?: string | null; mining?: EcfMiningPayload | null;}
/** `<OtraMonedaDetalle>` — precio y montos de la línea en divisa (passthrough). */export interface EcfLineForeignCurrencyPayload { unitPrice?: number | null; discount?: number | null; surcharge?: number | null; lineAmount?: number | null;}
/** Una línea de `<DetallesItems>`. */export interface EcfLinePayload { name?: string | null; kind?: string | null; quantity?: number | null; unitPrice?: number | null; itbisRate?: number | null; unitOfMeasure?: string | null; description?: string | null; discount?: number; surcharge?: number; priceIncludesTax?: boolean | null; declaredAmount?: number | null; codes?: EcfItemCodePayload[] | null; retention?: EcfLineRetentionPayload | null; additionalTaxes?: EcfAdditionalTaxPayload[] | null; foreignCurrency?: EcfLineForeignCurrencyPayload | null; details?: EcfLineDetailsPayload | null;}
/** Área `<Retencion>` de la línea (tipos 41 y 47). Los montos los calcula el cliente. */export interface EcfLineRetentionPayload { agent?: string; itbisWithheld?: number; isrWithheld?: number;}
/** Enlaces (relativos) a los recursos del comprobante. */export interface EcfLinks { /** El comprobante y su estado. */ self: string; /** El XML firmado (`<ECF>`). */ xml: string; /** El resumen firmado (`<RFCE>`); solo cuando `submitsRfce`. */ rfceXml?: string | null; /** La Representación Impresa en PDF. */ representation: string;}
/** `<Mineria>` — datos de liquidación minera (solo tipos 32/33/34/46). */export interface EcfMiningPayload { netWeightKilogram?: number | null; netWeightMining?: number | null; affiliationType?: number | null; settlement?: number | null;}
/** Página de la RI (`<Paginacion>`). */export interface EcfPagePayload { number?: number | null; lineFrom?: number | null; lineTo?: number | null; montoGravadoTotal?: number | null; montoGravadoI1?: number | null; montoGravadoI2?: number | null; montoGravadoI3?: number | null; montoExento?: number | null; totalItbis?: number | null; itbis1?: number | null; itbis2?: number | null; itbis3?: number | null; montoImpuestoAdicional?: number | null; iscEspecifico?: number | null; otrosImpuestos?: number | null; amount?: number | null; nonInvoiceableAmount?: number | null;}
/** Una forma de pago. `Type` y `Amount` son nullable para distinguir "no vino" de un valor: el validador exige ambos y la API nunca rellena uno por su cuenta. */export interface EcfPaymentMethodPayload { type?: string | null; amount?: number | null;}
/** Bloque de pago del encabezado. */export interface EcfPaymentPayload { condition?: string | null; dueDate?: string | null; methods?: EcfPaymentMethodPayload[] | null;}
/** Sección `<InformacionReferencia>` — Notas de Crédito/Débito y reemplazos. */export interface EcfReferencePayload { modifiedNcf: string; modifiedNcfDate: string; modificationCode?: string | null; otherIssuerRnc?: string | null;}
/** Reintento automático de un comprobante tras un rechazo transitorio de la DGII. */export interface EcfRetry { /** Reenvíos automáticos que NovaFE ya hizo de este comprobante. */ count: number; /** Tope de reenvíos automáticos. Al agotarse, el rechazo queda firme. */ max: number; /** Cuándo es el próximo intento; `null` si no hay uno pendiente. */ nextAttemptAt?: string | null;}
/** Datos de embarque (`<InformacionesAdicionales>`). Passthrough. */export interface EcfShippingPayload { shipmentDate?: string | null; shipmentNumber?: string | null; containerNumber?: string | null; referenceNumber?: string | null; grossWeight?: number | null; netWeight?: number | null; grossWeightUnit?: string | null; netWeightUnit?: string | null; packageCount?: number | null; packageUnit?: string | null; volume?: number | null; volumeUnit?: string | null; export?: EcfExportPayload | null;}
export interface EcfSubquantityPayload { quantity: number; unitCode: string;}
/** Subtotal informativo para la RI (Sección C). No afecta la base imponible. */export interface EcfSubtotalPayload { number?: number | null; description?: string | null; order?: number | null; montoGravadoTotal?: number | null; montoGravadoI1?: number | null; montoGravadoI2?: number | null; montoGravadoI3?: number | null; totalItbis?: number | null; itbis1?: number | null; itbis2?: number | null; itbis3?: number | null; montoImpuestoAdicional?: number | null; montoExento?: number | null; amount?: number | null; lines?: number | null;}
/** Fila del listado de comprobantes emitidos. */export interface EcfSummaryDto { id: string; status: string; encf: string; type: number; environment: string; issueDate: string; montoTotal: number; buyerRnc?: string | null; buyerName?: string | null; createdAt: string; /** Reintento automático (ver EcfRetry? EcfDto.Retry); `null` si no hubo ninguno. */ retry?: EcfRetry;}
/** Datos de transporte (`<Transporte>`). Passthrough. */export interface EcfTransportPayload { driver?: string | null; transportDocument?: string | null; vehicleId?: string | null; plate?: string | null; route?: string | null; zone?: string | null; deliveryNote?: string | null; via?: string | null; originCountry?: string | null; destinationAddress?: string | null; destinationCountry?: string | null; carrierRnc?: string | null; carrierName?: string | null; voyageNumber?: string | null;}
/** Payload de emisión de un e-CF (`POST /api/v1/ecf`). Un solo objeto discriminado por int IssueEcfCommand.Type; el servidor asigna la secuencia, arma el bloque Emisor desde el perfil del tenant, calcula los totales, firma y persiste. */export interface IssueEcfCommand { /** Código DGII del tipo de e-CF: 31, 32, 33, 34, 41, 43, 44, 45, 46, 47. */ type: number; /** e-NCF de 13 caracteres. Solo con `sequences.mode = external` (y obligatorio ahí); con `managed` se rechaza, NovaFE asigna el suyo. */ encf?: string | null; /** `<FechaVencimientoSecuencia>` del e-NCF. Con `sequences.mode = external` es obligatorio en los tipos que llevan vencimiento (todos menos 32 y 34) y no aplica en esos dos; en `managed` se rechaza. */ sequenceExpiresOn?: string | null; /** Fecha de emisión (calendario dominicano). Default: hoy. */ issueDate?: string | null; /** `<TipoIngresos>` — "01"…"06". Obligatorio en 31/32/33/34/44/45/46. */ incomeType?: string | null; /** `true` si los precios de las líneas ya traen el ITBIS incluido. */ pricesIncludeTax?: boolean; /** `<IndicadorEnvioDiferido>` — solo contribuyentes autorizados. */ deferredDelivery?: boolean; /** `<MontoNoFacturable>` — reembolsos, propina voluntaria. Puede ser negativo. */ nonInvoiceableAmount?: number; /** `<NumeroFacturaInterna>` — clave de dedup de negocio. */ internalNumber?: string | null; /** `<CodigoVendedor>`. */ sellerCode?: string | null; additionalInfo?: EcfAdditionalInfoPayload | null; buyer?: EcfBuyerPayload | null; payment?: EcfPaymentPayload; lines: EcfLinePayload[]; reference?: EcfReferencePayload | null; globalAdjustments?: EcfGlobalAdjustmentPayload[] | null; foreignCurrency?: EcfForeignCurrencyPayload | null; shipping?: EcfShippingPayload | null; transport?: EcfTransportPayload | null; subtotals?: EcfSubtotalPayload[] | null; pagination?: EcfPagePayload[] | null; /** Totales declarados por el cliente (chequeo de tolerancia; nunca bloquea). */ declaredTotals?: EcfDeclaredTotalsPayload | null;}
export interface PagedResultOfEcfSummaryDto { items: EcfSummaryDto[]; totalCount: number; page: number; pageSize: number; totalPages?: number; hasNextPage?: boolean; hasPreviousPage?: boolean;}
export interface RepresentationLayout {}
/** Un error o aviso de `POST /api/v1/ecf/validate`. */export interface ValidateEcfIssueDto { /** Campo del payload al que se refiere (p. ej. `Lines[0].Quantity`); `null` si es de todo el comprobante. */ field?: string | null; /** Código estable, apto para lógica del cliente. */ code: string; /** Descripción lista para mostrar. */ message: string;}
/** Vista previa de `POST /api/v1/ecf/validate` para un payload válido: pasó la matriz de validación por tipo (Módulo 2) y el cálculo de totales (Módulo 6) sin asignar una secuencia real, firmar ni persistir nada. */export interface ValidateEcfPreviewDto { type: number; typeName: string; /** Placeholder (`E{tipo}0000000000`) — nunca un e-NCF real, no se asignó ninguna secuencia para construir este documento. */ sampleEncf: string; issueDate: string; /** Estimado (31-dic del año siguiente a DateOnly ValidateEcfPreviewDto.IssueDate) cuando el tipo lo lleva; no viene de una secuencia real. `null` en los tipos que no lo llevan (32, 34). */ sequenceExpiresOnEstimate?: string | null; montoGravadoTotal: number; montoExento: number; totalItbis: number; montoImpuestoAdicional: number; montoTotal: number; /** Los totales quedarían fuera de la tolerancia de cuadratura (RF-06.6) — la DGII probablemente aceptaría el comprobante de forma condicional si se emite así. */ expectConditionalAcceptance: boolean;}
/** Resultado de `POST /api/v1/ecf/validate`. Siempre llega con `200` cuando el cuerpo se pudo leer y el contribuyente puede validar: bool ValidateEcfResultDto.Valid dice si el comprobante pasaría la validación de forma y la matriz estructural y fiscal por tipo (Módulo 2 + 6) que corre `POST /ecf`. No asigna una secuencia real, no firma y no persiste nada. Lo que impide validar (sin autenticar, perfil del emisor sin configurar) sigue siendo un error HTTP. */export interface ValidateEcfResultDto { /** `true` si el comprobante se podría emitir tal cual en cuanto a su contenido. */ valid: boolean; /** Resumen en una frase, listo para mostrar. */ message: string; /** Todos los problemas encontrados, no solo el primero. Vacío si bool ValidateEcfResultDto.Valid. */ errors: ValidateEcfIssueDto[]; /** Avisos que no impiden emitir (p. ej. aceptación condicional probable). */ warnings: ValidateEcfIssueDto[]; /** La vista previa de lo que se emitiría; `null` si el comprobante no es válido. */ preview?: ValidateEcfPreviewDto | null;}