Ir al contenido

SDK y modelosSDK y modelos: C#

SDK y modelos: C#

Ver como Markdown

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.

Emitir.cs
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}");

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

Ecf.cs
// 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:&lt;código&gt;` (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 `&lt;Comprador&gt;`. `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 (`&lt;OtraMoneda&gt;`). 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 `&lt;Item&gt;` — 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>`&lt;OtraMonedaDetalle&gt;` — 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 `&lt;DetallesItems&gt;`.</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 `&lt;Retencion&gt;` 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 (`&lt;ECF&gt;`).</summary>
[JsonPropertyName("xml")]
public required string Xml { get; init; }
/// <summary>El resumen firmado (`&lt;RFCE&gt;`); 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>`&lt;Mineria&gt;` — 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 (`&lt;Paginacion&gt;`).</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 `&lt;InformacionReferencia&gt;` — 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 (`&lt;InformacionesAdicionales&gt;`). 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 (`&lt;Transporte&gt;`). 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>`&lt;FechaVencimientoSecuencia&gt;` 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>`&lt;TipoIngresos&gt;` — "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>`&lt;IndicadorEnvioDiferido&gt;` — solo contribuyentes autorizados.</summary>
[JsonPropertyName("deferredDelivery")]
public bool? DeferredDelivery { get; init; }
/// <summary>`&lt;MontoNoFacturable&gt;` — reembolsos, propina voluntaria. Puede ser negativo.</summary>
[JsonPropertyName("nonInvoiceableAmount")]
public decimal? NonInvoiceableAmount { get; init; }
/// <summary>`&lt;NumeroFacturaInterna&gt;` — clave de dedup de negocio.</summary>
[JsonPropertyName("internalNumber")]
public string? InternalNumber { get; init; }
/// <summary>`&lt;CodigoVendedor&gt;`.</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; }
}