Ir al contenido

SDK y modelosSDK y modelos: TypeScript

SDK y modelos: TypeScript

Ver como Markdown

Interfaces sin dependencias: funcionan en Node, Bun o el navegador. Copia el archivo a tu proyecto e impórtalo donde construyas o leas comprobantes.

emitir.ts
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);

Todo el contenido de ecf.ts, listo para copiar. Incluye IssueEcfCommand (el cuerpo de POST /ecf), EcfDto (la respuesta) y sus tipos anidados.

ecf.ts
// 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;
}