> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emite.do/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK C# / .NET

> Cliente oficial EcfService.Sdk. Emisión E31–E47, portal, inbox, ACECF y ANECF.

Paquete: [`EcfService.Sdk`](https://www.nuget.org/packages/EcfService.Sdk) · Código: [ecf-service-csharp](https://github.com/yasmanycastillo/ecf-service-csharp)

```bash theme={null}
dotnet add package EcfService.Sdk --version 0.2.0
```

Target: `net8.0`. Host: `https://api.emite.do/api/v1`.

## Cliente

```csharp theme={null}
using EcfService.Sdk;

using var client = new EcfClient("ecf_...");
var health = await client.HealthAsync();
```

`apiKey` es opcional si solo usas `HealthAsync` o `client.Dgii`.

```csharp theme={null}
// Inyección: reutiliza sockets / IHttpClientFactory
builder.Services.AddHttpClient<EcfClient>((sp, http) =>
{
    http.BaseAddress = new Uri("https://api.emite.do/api/v1/");
    http.DefaultRequestHeaders.Add("X-API-Key", config["Emite:ApiKey"]);
});
// Luego: new EcfClient(httpClient) — no dispose el HttpClient del factory
```

| Constructor                                       | Uso                            |
| ------------------------------------------------- | ------------------------------ |
| `EcfClient(apiKey, baseUrl, timeoutSeconds)`      | Crea y dispone su `HttpClient` |
| `EcfClient(HttpClient, disposeHttpClient: false)` | DI / tests                     |

## Primer e-CF

El e-NCF lo pones tú. `FechaEmision` va en `Emisor` como **DD-MM-YYYY**. El builder escribe `TipoeCF` (no `TipoEcf`).

```csharp theme={null}
using EcfService.Sdk;
using EcfService.Sdk.Models;

using var client = new EcfClient("ecf_...");

var payload = new EcfPayloadBuilder(EcfType.E31)
    .IdDoc(new Dictionary<string, object?>
    {
        ["eNCF"] = "E310000000001",
        ["FechaVencimientoSecuencia"] = "31-12-2028",
        ["IndicadorMontoGravado"] = 0,
        ["TipoIngresos"] = IncomeType.Operational,
        ["TipoPago"] = PaymentType.Cash,
    })
    .Emisor(new Dictionary<string, object?>
    {
        ["RNCEmisor"] = "130478031",
        ["RazonSocialEmisor"] = "Mi Empresa SRL",
        ["FechaEmision"] = DateTime.Today.ToString("dd-MM-yyyy"),
    })
    .Comprador(new Dictionary<string, object?>
    {
        ["RNCComprador"] = "131098193",
        ["RazonSocialComprador"] = "Cliente SRL",
    })
    .Totales(new Dictionary<string, object?>
    {
        ["MontoTotal"] = 11800.0,
        ["TotalITBIS"] = 1800.0,
    })
    .AddItem(new Dictionary<string, object?>
    {
        ["NumeroLinea"] = 1,
        ["NombreItem"] = "SERVICIO DEMO",
        ["IndicadorFacturacion"] = BillingIndicator.Itbis18,
        ["IndicadorBienoServicio"] = BienOServicio.Service,
        ["CantidadItem"] = 1.0,
        ["MontoItem"] = 10000.0,
    })
    .Build();

EcfDocument doc = await client.Ecf.CreateAsync(
    idempotencyKey: "INV-2026-0001",
    ecfType: EcfType.E31,
    payload: payload,
    environment: EcfEnvironment.Test
);
Console.WriteLine($"{doc.PublicId} {doc.Status}"); // Received

doc = await client.Ecf.GetAsync(doc.PublicId);
byte[] xml = await client.Ecf.DownloadXmlAsync(doc.PublicId);
```

Misma `idempotencyKey` → `200` del original. El payload no se compara.

`EcfType` cubre E31–E47. Cambia `TipoeCF` y el e-NCF (`E32…`, `E47…`).

Alias del builder: `RNC` / `RazonSocial` → `RNCEmisor` o `RNCComprador`. `Descripcion` en un ítem → `NombreItem`.

```csharp theme={null}
var profile = await client.Client.CompanyAsync();
var emisor = Emisor.Build(profile, new Dictionary<string, object?>
{
    ["FechaEmision"] = "15-08-2026",
});
```

## Después del 201

1. Persiste `PublicId`.
2. Configura [webhooks](/guides/webhooks) o haz poll a `GetAsync`.
3. Descarga XML/PDF cuando el estado lo permita.

`EcfState`: `Received` → `Signed` → `Submitted` → `Accepted` | `ConditionallyAccepted` | `Rejected`.

## Empresa, rangos, inbox

```csharp theme={null}
var profile = await client.Client.CompanyAsync();
var seqs = await client.Client.SequencesAsync(activeOnly: true);
var inbox = await client.Client.InboxAsync(acked: false);
await client.Client.AckInboxAsync(inbox.Items[0].PublicId);
```

Los rangos se listan; no se crean por API.

## ACECF y ANECF

```csharp theme={null}
await client.Ecf.SubmitAcecfAsync(
    idempotencyKey: "ac-001",
    encf: "E310000000001",
    rncEmisor: "130478031",
    rncComprador: "131098193",
    fechaEmision: "15-08-2026",
    montoTotal: "11800.00",
    estado: "1"
);

await client.Ecf.CancelSequencesAsync("an-001", new[]
{
    new CancellationLine
    {
        EcfType = EcfType.E31,
        SequenceFrom = "E310000000010",
        SequenceTo = "E310000000012",
        Quantity = 3
    }
});
```

## Webhooks

```csharp theme={null}
using EcfService.Sdk.Webhooks;

var wh = await client.Client.CreateWebhookAsync(
    "https://mi-app.example/webhooks/ecf",
    new List<string> { "accepted", "conditionally_accepted", "rejected" }
);
bool ok = WebhookVerifier.VerifySignature(
    requestBody,
    Request.Headers["X-ECF-Signature"].ToString(),
    wh.Secret
);
```

## Errores

| HTTP | Excepción                                    |
| ---- | -------------------------------------------- |
| 401  | `EcfAuthException`                           |
| 404  | `EcfNotFoundException`                       |
| 409  | `EcfConflictException`                       |
| 422  | `EcfValidationException`                     |
| otro | `EcfException` (`StatusCode`, `RawResponse`) |

## Mapa rápido

| Método                                  | Endpoint                         |
| --------------------------------------- | -------------------------------- |
| `Ecf.CreateAsync`                       | `POST /api/v1/ecf`               |
| `Ecf.GetAsync`                          | `GET /api/v1/ecf/{id}`           |
| `Ecf.DownloadXmlAsync` / `Pdf` / `Rfce` | artefactos                       |
| `Ecf.SubmitAcecfAsync`                  | `POST /api/v1/ecf/acecf/submit`  |
| `Ecf.CancelSequencesAsync`              | `POST /api/v1/ecf/cancellations` |
| `Client.CompanyAsync`                   | `GET /api/v1/client/company`     |
| `Client.InboxAsync`                     | `GET /api/v1/client/inbox`       |
| `Dgii.RncAsync`                         | `GET /api/v1/dgii/rnc/{rnc}`     |
| `HealthAsync`                           | `GET /api/v1/health`             |

IntelliSense cubre estos tipos: el paquete incluye el XML de documentación.

Siguiente: [primer documento](/guides/first-document) o [webhooks](/guides/webhooks).
