SDK y modelos: C#
Records con System.Text.Json ([JsonPropertyName]) y los campos obligatorios marcados como required. Agrega los archivos a tu proyecto; el espacio de nombres es NovaFE.Client.Models.
Descargar
Sección titulada «Descargar»- Ecf.cs: emisión de e-CF, con sus tipos anidados.
- novafe-models-csharp.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»using System.Net.Http.Json;using System.Text.Json;using System.Text.Json.Serialization;using NovaFE.Client.Models;
var payload = new IssueEcfCommand{ Type = 31, IncomeType = "01", InternalNumber = "FAC-2026-00042", Buyer = new EcfBuyerPayload { Rnc = "131880681", Name = "Mi Cliente SRL" }, Payment = new EcfPaymentPayload { Condition = "credit", DueDate = "15-03-2026", Methods = [new EcfPaymentMethodPayload { Type = "check_transfer", Amount = 2360m }], }, Lines = [ new EcfLinePayload { Name = "Servicio de consultoría", Kind = "service", Quantity = 1m, UnitOfMeasure = "43", UnitPrice = 2000m, ItbisRate = 1, }, ],};
using var http = new HttpClient { BaseAddress = new Uri("https://api.novafe.example") };http.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("NOVAFE_API_KEY"));http.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };var response = await http.PostAsJsonAsync("/api/v1/ecf", payload, options);response.EnsureSuccessStatusCode();
var ecf = await response.Content.ReadFromJsonAsync<EcfDto>();Console.WriteLine($"{ecf!.Encf} {ecf.Status}");Modelos de emisión
Sección titulada «Modelos de emisión»Todo el contenido de Ecf.cs, 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`.using System.Text.Json;using System.Text.Json.Serialization;
namespace NovaFE.Client.Models;
public sealed record DgiiMessage{ [JsonPropertyName("code")] public required int Code { get; init; }
[JsonPropertyName("value")] public required string Value { get; init; }}
/// <summary>Una entrada de `/{amb}/consultatrackids/api/trackids/consulta`. Puede haber más de una para el mismo e-NCF si se remitió varias veces.</summary>public sealed record DgiiTrackIdEntry{ [JsonPropertyName("trackId")] public required string TrackId { get; init; }
[JsonPropertyName("estado")] public required string Estado { get; init; }
[JsonPropertyName("fechaRecepcion")] public string? FechaRecepcion { get; init; }}
/// <summary>Texto libre para la Representación Impresa.</summary>public sealed record EcfAdditionalInfoPayload{ [JsonPropertyName("issuer")] public string? Issuer { get; init; }
[JsonPropertyName("buyer")] public string? Buyer { get; init; }}
/// <summary>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.</summary>public sealed record EcfAdditionalTaxPayload{ [JsonPropertyName("code")] public required string Code { get; init; }
[JsonPropertyName("rate")] public decimal? Rate { get; init; }
[JsonPropertyName("iscEspecifico")] public decimal? IscEspecifico { get; init; }
[JsonPropertyName("iscAdvalorem")] public decimal? IscAdvalorem { get; init; }
[JsonPropertyName("otros")] public decimal? Otros { get; init; }}
/// <summary>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`.</summary>public sealed record EcfAttemptDto{ /// <summary>1, 2, 3… dentro del comprobante.</summary> [JsonPropertyName("attempt")] public required int Attempt { get; init; }
/// <summary>En qué estado dejó el ciclo al comprobante: `accepted`, `accepted_conditional`, `rejected`, `review` o `failed`.</summary> [JsonPropertyName("outcome")] public required string Outcome { get; init; }
/// <summary>`TrackId` de la DGII de este ciclo; ausente si no llegó a haber uno.</summary> [JsonPropertyName("trackId")] public string? TrackId { get; init; }
/// <summary>Código de estado de la DGII (1 aceptado, 2 rechazado, 4 aceptado condicional); ausente si no hubo veredicto.</summary> [JsonPropertyName("statusCode")] public int? StatusCode { get; init; }
/// <summary>El `estado` textual de la DGII.</summary> [JsonPropertyName("statusText")] public string? StatusText { get; init; }
/// <summary>`secuenciaUtilizada` de la DGII: `false` = el e-NCF no se consumió; `true` o ausente = consumido.</summary> [JsonPropertyName("sequenceUsed")] public bool? SequenceUsed { get; init; }
/// <summary>Observaciones o motivo de rechazo de la DGII; o el motivo del fallo si no hubo veredicto.</summary> [JsonPropertyName("messages")] public required IReadOnlyList<DgiiMessage> Messages { get; init; }
/// <summary>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.</summary> [JsonPropertyName("retryDecision")] public string? RetryDecision { get; init; }
/// <summary>SHA-256 (hex) del XML enviado en este ciclo.</summary> [JsonPropertyName("xmlSha256")] public required string XmlSha256 { get; init; }
/// <summary>Cuándo se registró el resultado.</summary> [JsonPropertyName("recordedAt")] public required string RecordedAt { get; init; }}
/// <summary>Bloque `<Comprador>`. `Rnc` y `ForeignId` son excluyentes.</summary>public sealed record EcfBuyerPayload{ [JsonPropertyName("name")] public string? Name { get; init; }
[JsonPropertyName("rnc")] public string? Rnc { get; init; }
[JsonPropertyName("foreignId")] public string? ForeignId { get; init; }
[JsonPropertyName("email")] public string? Email { get; init; }
[JsonPropertyName("contact")] public string? Contact { get; init; }
[JsonPropertyName("address")] public string? Address { get; init; }
[JsonPropertyName("municipality")] public string? Municipality { get; init; }
[JsonPropertyName("province")] public string? Province { get; init; }
[JsonPropertyName("additionalInfo")] public string? AdditionalInfo { get; init; }}
/// <summary>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.</summary>public sealed record EcfDeclaredTotalsPayload{ [JsonPropertyName("montoGravadoTotal")] public decimal? MontoGravadoTotal { get; init; }
[JsonPropertyName("montoExento")] public decimal? MontoExento { get; init; }
[JsonPropertyName("totalItbis")] public decimal? TotalItbis { get; init; }
[JsonPropertyName("montoImpuestoAdicional")] public decimal? MontoImpuestoAdicional { get; init; }
[JsonPropertyName("montoTotal")] public decimal? MontoTotal { get; init; }}
/// <summary>El intercambio con la DGII de un comprobante: lo que ella respondió más los instantes en que NovaFE registró cada paso.</summary>public sealed record EcfDgiiExchange{ /// <summary>Identificador que la DGII asigna al recibir el comprobante.</summary> [JsonPropertyName("trackId")] public string? TrackId { get; init; }
/// <summary>El `estado` textual de la DGII ("Aceptado", "Rechazado", "En Proceso"…); en el idioma en que lo manda ella.</summary> [JsonPropertyName("status")] public string? Status { get; init; }
/// <summary>Código de estado de la DGII: 1 aceptado · 2 rechazado · 3 en proceso · 4 aceptado condicional.</summary> [JsonPropertyName("statusCode")] public int? StatusCode { get; init; }
/// <summary>`secuenciaUtilizada` de la DGII: `false` = el e-NCF no se consumió (firma/XML inválidos); `true` o ausente = consumido.</summary> [JsonPropertyName("sequenceUsed")] public bool? SequenceUsed { get; init; }
/// <summary>Observaciones o motivo de rechazo que devolvió la DGII.</summary> [JsonPropertyName("messages")] public required IReadOnlyList<DgiiMessage> Messages { get; init; }
/// <summary>Instante en que la DGII confirmó la recepción (hay string? EcfDgiiExchange.TrackId).</summary> [JsonPropertyName("submittedAt")] public string? SubmittedAt { get; init; }
/// <summary>La `fechaRecepcion` que informó la DGII; ausente si no la dio (p. ej. RFCE).</summary> [JsonPropertyName("receivedAt")] public string? ReceivedAt { get; init; }
/// <summary>Instante en que la DGII dio un resultado definitivo.</summary> [JsonPropertyName("processedAt")] public string? ProcessedAt { get; init; }}
/// <summary>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`).</summary>public sealed record EcfDto{ [JsonPropertyName("id")] public required string Id { get; init; }
[JsonPropertyName("status")] public required string Status { get; init; }
[JsonPropertyName("encf")] public required string Encf { get; init; }
[JsonPropertyName("type")] public required int Type { get; init; }
[JsonPropertyName("environment")] public required string Environment { get; init; }
[JsonPropertyName("sequenceExpiresOn")] public string? SequenceExpiresOn { get; init; }
[JsonPropertyName("issueDate")] public required string IssueDate { get; init; }
[JsonPropertyName("issuedAt")] public required string IssuedAt { get; init; }
[JsonPropertyName("signedAt")] public required string SignedAt { get; init; }
[JsonPropertyName("securityCode")] public required string SecurityCode { get; init; }
[JsonPropertyName("qrUrl")] public required string QrUrl { get; init; }
[JsonPropertyName("submitsRfce")] public required bool SubmitsRfce { get; init; }
[JsonPropertyName("internalNumber")] public string? InternalNumber { get; init; }
[JsonPropertyName("toleranceWarning")] public string? ToleranceWarning { get; init; }
[JsonPropertyName("signedDuringContingency")] public required bool SignedDuringContingency { get; init; }
[JsonPropertyName("montoTotal")] public required decimal MontoTotal { get; init; }
[JsonPropertyName("buyerRnc")] public string? BuyerRnc { get; init; }
[JsonPropertyName("buyerName")] public string? BuyerName { get; init; }
[JsonPropertyName("documentHash")] public required string DocumentHash { get; init; }
/// <summary>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.</summary> [JsonPropertyName("dgii")] public EcfDgiiExchange? Dgii { get; init; }
/// <summary>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.</summary> [JsonPropertyName("retry")] public EcfRetry? Retry { get; init; }
/// <summary>Enlaces a los recursos relacionados del comprobante.</summary> [JsonPropertyName("links")] public EcfLinks? Links { get; init; }}
/// <summary>Datos de exportación (solo tipo 46).</summary>public sealed record EcfExportPayload{ [JsonPropertyName("loadingPortName")] public string? LoadingPortName { get; init; }
[JsonPropertyName("deliveryTerms")] public string? DeliveryTerms { get; init; }
[JsonPropertyName("totalFob")] public decimal? TotalFob { get; init; }
[JsonPropertyName("insurance")] public decimal? Insurance { get; init; }
[JsonPropertyName("freight")] public decimal? Freight { get; init; }
[JsonPropertyName("otherCharges")] public decimal? OtherCharges { get; init; }
[JsonPropertyName("totalCif")] public decimal? TotalCif { get; init; }
[JsonPropertyName("customsRegime")] public string? CustomsRegime { get; init; }
[JsonPropertyName("departurePortName")] public string? DeparturePortName { get; init; }
[JsonPropertyName("unloadingPortName")] public string? UnloadingPortName { get; init; }}
/// <summary>Facturación en divisa (`<OtraMoneda>`). El cliente trae los montos ya convertidos.</summary>public sealed record EcfForeignCurrencyPayload{ [JsonPropertyName("currency")] public required string Currency { get; init; }
[JsonPropertyName("exchangeRate")] public required decimal ExchangeRate { get; init; }
[JsonPropertyName("totals")] public required EcfForeignCurrencyTotalsPayload Totals { get; init; }}
public sealed record EcfForeignCurrencyTotalsPayload{ [JsonPropertyName("montoGravadoTotal")] public decimal? MontoGravadoTotal { get; init; }
[JsonPropertyName("montoGravadoI1")] public decimal? MontoGravadoI1 { get; init; }
[JsonPropertyName("montoGravadoI2")] public decimal? MontoGravadoI2 { get; init; }
[JsonPropertyName("montoGravadoI3")] public decimal? MontoGravadoI3 { get; init; }
[JsonPropertyName("montoExento")] public decimal? MontoExento { get; init; }
[JsonPropertyName("totalItbis")] public decimal? TotalItbis { get; init; }
[JsonPropertyName("totalItbis1")] public decimal? TotalItbis1 { get; init; }
[JsonPropertyName("totalItbis2")] public decimal? TotalItbis2 { get; init; }
[JsonPropertyName("totalItbis3")] public decimal? TotalItbis3 { get; init; }
[JsonPropertyName("montoTotal")] public decimal? MontoTotal { get; init; }}
/// <summary>Un descuento o recargo global (Sección D).</summary>public sealed record EcfGlobalAdjustmentPayload{ [JsonPropertyName("line")] public required int Line { get; init; }
[JsonPropertyName("kind")] public string? Kind { get; init; }
[JsonPropertyName("affectsItbisRate")] public int? AffectsItbisRate { get; init; }
[JsonPropertyName("amount")] public decimal? Amount { get; init; }
[JsonPropertyName("norm1007")] public bool? Norm1007 { get; init; }
[JsonPropertyName("description")] public string? Description { get; init; }
[JsonPropertyName("percentage")] public decimal? Percentage { get; init; }}
public sealed record EcfItemCodePayload{ [JsonPropertyName("type")] public required string Type { get; init; }
[JsonPropertyName("value")] public required string Value { get; init; }}
/// <summary>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).</summary>public sealed record EcfLineDetailsPayload{ [JsonPropertyName("referenceQuantity")] public decimal? ReferenceQuantity { get; init; }
[JsonPropertyName("referenceUnit")] public string? ReferenceUnit { get; init; }
[JsonPropertyName("subquantities")] public IReadOnlyList<EcfSubquantityPayload>? Subquantities { get; init; }
[JsonPropertyName("alcoholDegrees")] public decimal? AlcoholDegrees { get; init; }
[JsonPropertyName("referenceUnitPrice")] public decimal? ReferenceUnitPrice { get; init; }
[JsonPropertyName("manufactureDate")] public string? ManufactureDate { get; init; }
[JsonPropertyName("expiryDate")] public string? ExpiryDate { get; init; }
[JsonPropertyName("mining")] public EcfMiningPayload? Mining { get; init; }}
/// <summary>`<OtraMonedaDetalle>` — precio y montos de la línea en divisa (passthrough).</summary>public sealed record EcfLineForeignCurrencyPayload{ [JsonPropertyName("unitPrice")] public decimal? UnitPrice { get; init; }
[JsonPropertyName("discount")] public decimal? Discount { get; init; }
[JsonPropertyName("surcharge")] public decimal? Surcharge { get; init; }
[JsonPropertyName("lineAmount")] public decimal? LineAmount { get; init; }}
/// <summary>Una línea de `<DetallesItems>`.</summary>public sealed record EcfLinePayload{ [JsonPropertyName("name")] public string? Name { get; init; }
[JsonPropertyName("kind")] public string? Kind { get; init; }
[JsonPropertyName("quantity")] public decimal? Quantity { get; init; }
[JsonPropertyName("unitPrice")] public decimal? UnitPrice { get; init; }
[JsonPropertyName("itbisRate")] public int? ItbisRate { get; init; }
[JsonPropertyName("unitOfMeasure")] public string? UnitOfMeasure { get; init; }
[JsonPropertyName("description")] public string? Description { get; init; }
[JsonPropertyName("discount")] public decimal? Discount { get; init; }
[JsonPropertyName("surcharge")] public decimal? Surcharge { get; init; }
[JsonPropertyName("priceIncludesTax")] public bool? PriceIncludesTax { get; init; }
[JsonPropertyName("declaredAmount")] public decimal? DeclaredAmount { get; init; }
[JsonPropertyName("codes")] public IReadOnlyList<EcfItemCodePayload>? Codes { get; init; }
[JsonPropertyName("retention")] public EcfLineRetentionPayload? Retention { get; init; }
[JsonPropertyName("additionalTaxes")] public IReadOnlyList<EcfAdditionalTaxPayload>? AdditionalTaxes { get; init; }
[JsonPropertyName("foreignCurrency")] public EcfLineForeignCurrencyPayload? ForeignCurrency { get; init; }
[JsonPropertyName("details")] public EcfLineDetailsPayload? Details { get; init; }}
/// <summary>Área `<Retencion>` de la línea (tipos 41 y 47). Los montos los calcula el cliente.</summary>public sealed record EcfLineRetentionPayload{ [JsonPropertyName("agent")] public string? Agent { get; init; }
[JsonPropertyName("itbisWithheld")] public decimal? ItbisWithheld { get; init; }
[JsonPropertyName("isrWithheld")] public decimal? IsrWithheld { get; init; }}
/// <summary>Enlaces (relativos) a los recursos del comprobante.</summary>public sealed record EcfLinks{ /// <summary>El comprobante y su estado.</summary> [JsonPropertyName("self")] public required string Self { get; init; }
/// <summary>El XML firmado (`<ECF>`).</summary> [JsonPropertyName("xml")] public required string Xml { get; init; }
/// <summary>El resumen firmado (`<RFCE>`); solo cuando `submitsRfce`.</summary> [JsonPropertyName("rfceXml")] public string? RfceXml { get; init; }
/// <summary>La Representación Impresa en PDF.</summary> [JsonPropertyName("representation")] public required string Representation { get; init; }}
/// <summary>`<Mineria>` — datos de liquidación minera (solo tipos 32/33/34/46).</summary>public sealed record EcfMiningPayload{ [JsonPropertyName("netWeightKilogram")] public decimal? NetWeightKilogram { get; init; }
[JsonPropertyName("netWeightMining")] public decimal? NetWeightMining { get; init; }
[JsonPropertyName("affiliationType")] public int? AffiliationType { get; init; }
[JsonPropertyName("settlement")] public int? Settlement { get; init; }}
/// <summary>Página de la RI (`<Paginacion>`).</summary>public sealed record EcfPagePayload{ [JsonPropertyName("number")] public int? Number { get; init; }
[JsonPropertyName("lineFrom")] public int? LineFrom { get; init; }
[JsonPropertyName("lineTo")] public int? LineTo { get; init; }
[JsonPropertyName("montoGravadoTotal")] public decimal? MontoGravadoTotal { get; init; }
[JsonPropertyName("montoGravadoI1")] public decimal? MontoGravadoI1 { get; init; }
[JsonPropertyName("montoGravadoI2")] public decimal? MontoGravadoI2 { get; init; }
[JsonPropertyName("montoGravadoI3")] public decimal? MontoGravadoI3 { get; init; }
[JsonPropertyName("montoExento")] public decimal? MontoExento { get; init; }
[JsonPropertyName("totalItbis")] public decimal? TotalItbis { get; init; }
[JsonPropertyName("itbis1")] public decimal? Itbis1 { get; init; }
[JsonPropertyName("itbis2")] public decimal? Itbis2 { get; init; }
[JsonPropertyName("itbis3")] public decimal? Itbis3 { get; init; }
[JsonPropertyName("montoImpuestoAdicional")] public decimal? MontoImpuestoAdicional { get; init; }
[JsonPropertyName("iscEspecifico")] public decimal? IscEspecifico { get; init; }
[JsonPropertyName("otrosImpuestos")] public decimal? OtrosImpuestos { get; init; }
[JsonPropertyName("amount")] public decimal? Amount { get; init; }
[JsonPropertyName("nonInvoiceableAmount")] public decimal? NonInvoiceableAmount { get; init; }}
/// <summary>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.</summary>public sealed record EcfPaymentMethodPayload{ [JsonPropertyName("type")] public string? Type { get; init; }
[JsonPropertyName("amount")] public decimal? Amount { get; init; }}
/// <summary>Bloque de pago del encabezado.</summary>public sealed record EcfPaymentPayload{ [JsonPropertyName("condition")] public string? Condition { get; init; }
[JsonPropertyName("dueDate")] public string? DueDate { get; init; }
[JsonPropertyName("methods")] public IReadOnlyList<EcfPaymentMethodPayload>? Methods { get; init; }}
/// <summary>Sección `<InformacionReferencia>` — Notas de Crédito/Débito y reemplazos.</summary>public sealed record EcfReferencePayload{ [JsonPropertyName("modifiedNcf")] public required string ModifiedNcf { get; init; }
[JsonPropertyName("modifiedNcfDate")] public required string ModifiedNcfDate { get; init; }
[JsonPropertyName("modificationCode")] public string? ModificationCode { get; init; }
[JsonPropertyName("otherIssuerRnc")] public string? OtherIssuerRnc { get; init; }}
/// <summary>Reintento automático de un comprobante tras un rechazo transitorio de la DGII.</summary>public sealed record EcfRetry{ /// <summary>Reenvíos automáticos que NovaFE ya hizo de este comprobante.</summary> [JsonPropertyName("count")] public required int Count { get; init; }
/// <summary>Tope de reenvíos automáticos. Al agotarse, el rechazo queda firme.</summary> [JsonPropertyName("max")] public required int Max { get; init; }
/// <summary>Cuándo es el próximo intento; `null` si no hay uno pendiente.</summary> [JsonPropertyName("nextAttemptAt")] public string? NextAttemptAt { get; init; }}
/// <summary>Datos de embarque (`<InformacionesAdicionales>`). Passthrough.</summary>public sealed record EcfShippingPayload{ [JsonPropertyName("shipmentDate")] public string? ShipmentDate { get; init; }
[JsonPropertyName("shipmentNumber")] public string? ShipmentNumber { get; init; }
[JsonPropertyName("containerNumber")] public string? ContainerNumber { get; init; }
[JsonPropertyName("referenceNumber")] public string? ReferenceNumber { get; init; }
[JsonPropertyName("grossWeight")] public decimal? GrossWeight { get; init; }
[JsonPropertyName("netWeight")] public decimal? NetWeight { get; init; }
[JsonPropertyName("grossWeightUnit")] public string? GrossWeightUnit { get; init; }
[JsonPropertyName("netWeightUnit")] public string? NetWeightUnit { get; init; }
[JsonPropertyName("packageCount")] public decimal? PackageCount { get; init; }
[JsonPropertyName("packageUnit")] public string? PackageUnit { get; init; }
[JsonPropertyName("volume")] public decimal? Volume { get; init; }
[JsonPropertyName("volumeUnit")] public string? VolumeUnit { get; init; }
[JsonPropertyName("export")] public EcfExportPayload? Export { get; init; }}
public sealed record EcfSubquantityPayload{ [JsonPropertyName("quantity")] public required decimal Quantity { get; init; }
[JsonPropertyName("unitCode")] public required string UnitCode { get; init; }}
/// <summary>Subtotal informativo para la RI (Sección C). No afecta la base imponible.</summary>public sealed record EcfSubtotalPayload{ [JsonPropertyName("number")] public int? Number { get; init; }
[JsonPropertyName("description")] public string? Description { get; init; }
[JsonPropertyName("order")] public int? Order { get; init; }
[JsonPropertyName("montoGravadoTotal")] public decimal? MontoGravadoTotal { get; init; }
[JsonPropertyName("montoGravadoI1")] public decimal? MontoGravadoI1 { get; init; }
[JsonPropertyName("montoGravadoI2")] public decimal? MontoGravadoI2 { get; init; }
[JsonPropertyName("montoGravadoI3")] public decimal? MontoGravadoI3 { get; init; }
[JsonPropertyName("totalItbis")] public decimal? TotalItbis { get; init; }
[JsonPropertyName("itbis1")] public decimal? Itbis1 { get; init; }
[JsonPropertyName("itbis2")] public decimal? Itbis2 { get; init; }
[JsonPropertyName("itbis3")] public decimal? Itbis3 { get; init; }
[JsonPropertyName("montoImpuestoAdicional")] public decimal? MontoImpuestoAdicional { get; init; }
[JsonPropertyName("montoExento")] public decimal? MontoExento { get; init; }
[JsonPropertyName("amount")] public decimal? Amount { get; init; }
[JsonPropertyName("lines")] public int? Lines { get; init; }}
/// <summary>Fila del listado de comprobantes emitidos.</summary>public sealed record EcfSummaryDto{ [JsonPropertyName("id")] public required string Id { get; init; }
[JsonPropertyName("status")] public required string Status { get; init; }
[JsonPropertyName("encf")] public required string Encf { get; init; }
[JsonPropertyName("type")] public required int Type { get; init; }
[JsonPropertyName("environment")] public required string Environment { get; init; }
[JsonPropertyName("issueDate")] public required string IssueDate { get; init; }
[JsonPropertyName("montoTotal")] public required decimal MontoTotal { get; init; }
[JsonPropertyName("buyerRnc")] public string? BuyerRnc { get; init; }
[JsonPropertyName("buyerName")] public string? BuyerName { get; init; }
[JsonPropertyName("createdAt")] public required string CreatedAt { get; init; }
/// <summary>Reintento automático (ver EcfRetry? EcfDto.Retry); `null` si no hubo ninguno.</summary> [JsonPropertyName("retry")] public EcfRetry? Retry { get; init; }}
/// <summary>Datos de transporte (`<Transporte>`). Passthrough.</summary>public sealed record EcfTransportPayload{ [JsonPropertyName("driver")] public string? Driver { get; init; }
[JsonPropertyName("transportDocument")] public string? TransportDocument { get; init; }
[JsonPropertyName("vehicleId")] public string? VehicleId { get; init; }
[JsonPropertyName("plate")] public string? Plate { get; init; }
[JsonPropertyName("route")] public string? Route { get; init; }
[JsonPropertyName("zone")] public string? Zone { get; init; }
[JsonPropertyName("deliveryNote")] public string? DeliveryNote { get; init; }
[JsonPropertyName("via")] public string? Via { get; init; }
[JsonPropertyName("originCountry")] public string? OriginCountry { get; init; }
[JsonPropertyName("destinationAddress")] public string? DestinationAddress { get; init; }
[JsonPropertyName("destinationCountry")] public string? DestinationCountry { get; init; }
[JsonPropertyName("carrierRnc")] public string? CarrierRnc { get; init; }
[JsonPropertyName("carrierName")] public string? CarrierName { get; init; }
[JsonPropertyName("voyageNumber")] public string? VoyageNumber { get; init; }}
/// <summary>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.</summary>public sealed record IssueEcfCommand{ /// <summary>Código DGII del tipo de e-CF: 31, 32, 33, 34, 41, 43, 44, 45, 46, 47.</summary> [JsonPropertyName("type")] public required int Type { get; init; }
/// <summary>e-NCF de 13 caracteres. Solo con `sequences.mode = external` (y obligatorio ahí); con `managed` se rechaza, NovaFE asigna el suyo.</summary> [JsonPropertyName("encf")] public string? Encf { get; init; }
/// <summary>`<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.</summary> [JsonPropertyName("sequenceExpiresOn")] public string? SequenceExpiresOn { get; init; }
/// <summary>Fecha de emisión (calendario dominicano). Default: hoy.</summary> [JsonPropertyName("issueDate")] public string? IssueDate { get; init; }
/// <summary>`<TipoIngresos>` — "01"…"06". Obligatorio en 31/32/33/34/44/45/46.</summary> [JsonPropertyName("incomeType")] public string? IncomeType { get; init; }
/// <summary>`true` si los precios de las líneas ya traen el ITBIS incluido.</summary> [JsonPropertyName("pricesIncludeTax")] public bool? PricesIncludeTax { get; init; }
/// <summary>`<IndicadorEnvioDiferido>` — solo contribuyentes autorizados.</summary> [JsonPropertyName("deferredDelivery")] public bool? DeferredDelivery { get; init; }
/// <summary>`<MontoNoFacturable>` — reembolsos, propina voluntaria. Puede ser negativo.</summary> [JsonPropertyName("nonInvoiceableAmount")] public decimal? NonInvoiceableAmount { get; init; }
/// <summary>`<NumeroFacturaInterna>` — clave de dedup de negocio.</summary> [JsonPropertyName("internalNumber")] public string? InternalNumber { get; init; }
/// <summary>`<CodigoVendedor>`.</summary> [JsonPropertyName("sellerCode")] public string? SellerCode { get; init; }
[JsonPropertyName("additionalInfo")] public EcfAdditionalInfoPayload? AdditionalInfo { get; init; }
[JsonPropertyName("buyer")] public EcfBuyerPayload? Buyer { get; init; }
[JsonPropertyName("payment")] public EcfPaymentPayload? Payment { get; init; }
[JsonPropertyName("lines")] public required IReadOnlyList<EcfLinePayload> Lines { get; init; }
[JsonPropertyName("reference")] public EcfReferencePayload? Reference { get; init; }
[JsonPropertyName("globalAdjustments")] public IReadOnlyList<EcfGlobalAdjustmentPayload>? GlobalAdjustments { get; init; }
[JsonPropertyName("foreignCurrency")] public EcfForeignCurrencyPayload? ForeignCurrency { get; init; }
[JsonPropertyName("shipping")] public EcfShippingPayload? Shipping { get; init; }
[JsonPropertyName("transport")] public EcfTransportPayload? Transport { get; init; }
[JsonPropertyName("subtotals")] public IReadOnlyList<EcfSubtotalPayload>? Subtotals { get; init; }
[JsonPropertyName("pagination")] public IReadOnlyList<EcfPagePayload>? Pagination { get; init; }
/// <summary>Totales declarados por el cliente (chequeo de tolerancia; nunca bloquea).</summary> [JsonPropertyName("declaredTotals")] public EcfDeclaredTotalsPayload? DeclaredTotals { get; init; }}
public sealed record PagedResultOfEcfSummaryDto{ [JsonPropertyName("items")] public required IReadOnlyList<EcfSummaryDto> Items { get; init; }
[JsonPropertyName("totalCount")] public required int TotalCount { get; init; }
[JsonPropertyName("page")] public required int Page { get; init; }
[JsonPropertyName("pageSize")] public required int PageSize { get; init; }
[JsonPropertyName("totalPages")] public int? TotalPages { get; init; }
[JsonPropertyName("hasNextPage")] public bool? HasNextPage { get; init; }
[JsonPropertyName("hasPreviousPage")] public bool? HasPreviousPage { get; init; }}
public sealed record RepresentationLayout{}
/// <summary>Un error o aviso de `POST /api/v1/ecf/validate`.</summary>public sealed record ValidateEcfIssueDto{ /// <summary>Campo del payload al que se refiere (p. ej. `Lines[0].Quantity`); `null` si es de todo el comprobante.</summary> [JsonPropertyName("field")] public string? Field { get; init; }
/// <summary>Código estable, apto para lógica del cliente.</summary> [JsonPropertyName("code")] public required string Code { get; init; }
/// <summary>Descripción lista para mostrar.</summary> [JsonPropertyName("message")] public required string Message { get; init; }}
/// <summary>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.</summary>public sealed record ValidateEcfPreviewDto{ [JsonPropertyName("type")] public required int Type { get; init; }
[JsonPropertyName("typeName")] public required string TypeName { get; init; }
/// <summary>Placeholder (`E{tipo}0000000000`) — nunca un e-NCF real, no se asignó ninguna secuencia para construir este documento.</summary> [JsonPropertyName("sampleEncf")] public required string SampleEncf { get; init; }
[JsonPropertyName("issueDate")] public required string IssueDate { get; init; }
/// <summary>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).</summary> [JsonPropertyName("sequenceExpiresOnEstimate")] public string? SequenceExpiresOnEstimate { get; init; }
[JsonPropertyName("montoGravadoTotal")] public required decimal MontoGravadoTotal { get; init; }
[JsonPropertyName("montoExento")] public required decimal MontoExento { get; init; }
[JsonPropertyName("totalItbis")] public required decimal TotalItbis { get; init; }
[JsonPropertyName("montoImpuestoAdicional")] public required decimal MontoImpuestoAdicional { get; init; }
[JsonPropertyName("montoTotal")] public required decimal MontoTotal { get; init; }
/// <summary>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í.</summary> [JsonPropertyName("expectConditionalAcceptance")] public required bool ExpectConditionalAcceptance { get; init; }}
/// <summary>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.</summary>public sealed record ValidateEcfResultDto{ /// <summary>`true` si el comprobante se podría emitir tal cual en cuanto a su contenido.</summary> [JsonPropertyName("valid")] public required bool Valid { get; init; }
/// <summary>Resumen en una frase, listo para mostrar.</summary> [JsonPropertyName("message")] public required string Message { get; init; }
/// <summary>Todos los problemas encontrados, no solo el primero. Vacío si bool ValidateEcfResultDto.Valid.</summary> [JsonPropertyName("errors")] public required IReadOnlyList<ValidateEcfIssueDto> Errors { get; init; }
/// <summary>Avisos que no impiden emitir (p. ej. aceptación condicional probable).</summary> [JsonPropertyName("warnings")] public required IReadOnlyList<ValidateEcfIssueDto> Warnings { get; init; }
/// <summary>La vista previa de lo que se emitiría; `null` si el comprobante no es válido.</summary> [JsonPropertyName("preview")] public ValidateEcfPreviewDto? Preview { get; init; }}