ecf-dgii
SDK de Ruby para la API ECF DGII — Comprobantes Fiscales Electrónicos de la República Dominicana.
Instalación
Añade esta línea al Gemfile de tu aplicación:
gem 'ecf-dgii'
Y luego ejecuta:
bundle install
O instálalo tú mismo con:
gem install ecf-dgii
Inicio rápido
require 'ecf-dgii'
# 1. Configurar el cliente
client = EcfDgii::Client.new(api_key: "tu-token-jwt", environment: :test)
# 2. Construir el objeto eCF (en este caso un Ecf31ECF)
ecf = EcfDgii::Generated::Ecf31ECF.new(
encabezado: EcfDgii::Generated::Ecf31Encabezado.new(
version: "Version1_0",
id_doc: EcfDgii::Generated::Ecf31IdDoc.new(
tipoe_cf: "FacturaDeCreditoFiscalElectronica",
encf: "E310000051630",
tipo_pago: "Contado",
tipo_ingresos: "01",
tabla_formas_pago: [
EcfDgii::Generated::Ecf31FormaDePago.new(
forma_pago: "Efectivo",
monto_pago: 1015.25
)
],
indicador_monto_gravado: "ConITBISIncluido",
fecha_vencimiento_secuencia: "2028-12-31T00:00:00"
),
emisor: EcfDgii::Generated::Ecf31Emisor.new(
rnc_emisor: "131460941",
razon_social_emisor: "DOCUMENTOS ELECTRONICOS DE 02",
direccion_emisor: "AVE. ISABEL AGUIAR NO. 269, ZONA INDUSTRIAL DE HERRERA",
fecha_emision: "2026-01-10"
),
comprador: EcfDgii::Generated::Ecf31Comprador.new(
rnc_comprador: "131880681",
razon_social_comprador: "DOCUMENTOS ELECTRONICOS DE 03"
),
totales: EcfDgii::Generated::Ecf31Totales.new(
itbis1: 18,
monto_gravado_i1: 762.71,
monto_gravado_total: 762.71,
total_itbis1: 137.29,
total_itbis: 137.29,
monto_no_facturable: 100.0,
impuestos_adicionales: [
EcfDgii::Generated::Ecf31ImpuestoAdicional2.new(
tipo_impuesto: "002",
tasa_impuesto_adicional: 2,
otros_impuestos_adicionales: 15.25
)
],
monto_impuesto_adicional: 15.25,
monto_total: 1015.25,
monto_periodo: 1015.25
)
),
detalles_items: [
EcfDgii::Generated::Ecf31Item.new(
numero_linea: 1,
nombre_item: "Iphone 18 Pro max",
indicador_facturacion: "ITBIS1_18Percent",
indicador_bieno_servicio: "Bien",
cantidad_item: 1,
unidad_medida: "Unidad",
precio_unitario_item: 1016.95,
monto_item: 1016.95,
tabla_impuesto_adicional: [
EcfDgii::Generated::Ecf31ImpuestoAdicional.new(tipo_impuesto: "002")
]
),
EcfDgii::Generated::Ecf31Item.new(
numero_linea: 2,
nombre_item: "Costo de Envío",
indicador_facturacion: "NoFacturable_18Percent",
indicador_bieno_servicio: "Servicio",
cantidad_item: 1,
unidad_medida: "Unidad",
precio_unitario_item: 100.0,
monto_item: 100.0
)
],
descuentos_o_recargos: [
EcfDgii::Generated::Ecf31DescuentoORecargo.new(
tipo_valor: "$",
tipo_ajuste: "D",
numero_linea: 1,
monto_descuentoo_recargo: 84.75,
descripcion_descuentoo_recargo: "Descuento",
indicador_facturacion_descuentoo_recargo: "ITBIS1_18Percent"
)
]
)
# 3. Enviar el eCF con enrutamiento automático y polling hasta completar
result = client.send_ecf_and_poll(ecf)
puts "eCF Procesado: #{result.encf} - Estado: #{result.progress}"
Configuración
Autenticación
El API key (token JWT Bearer provisto por SSD) puede pasarse de forma directa o mediante variables de entorno:
# Parámetro directo
client = EcfDgii::Client.new(api_key: "tu-token-jwt")
# Variable de entorno ECF_API_KEY
# export ECF_API_KEY=tu-token-jwt
client = EcfDgii::Client.new
Ambientes de trabajo
client = EcfDgii::Client.new(environment: :test) # Pruebas (Predeterminado)
client = EcfDgii::Client.new(environment: :cert) # Certificación
client = EcfDgii::Client.new(environment: :prod) # Producción
# URL base personalizada (sobreescribe la del ambiente)
client = EcfDgii::Client.new(base_url: "https://mi-servidor.local/v1")
Integración en Ruby on Rails
El SDK incluye un generador para Rails que configura de forma global el cliente de facturación electrónica.
Ejecuta el generador en tu terminal:
bundle exec rails generate ecf_dgii:install
Esto creará el archivo de configuración config/initializers/ecf_dgii.rb con el siguiente contenido:
# config/initializers/ecf_dgii.rb
EcfDgii.configure do |config|
# Token JWT (Bearer) proveído por SSD
config.api_key = ENV["ECF_API_KEY"]
# Ambiente de trabajo: :test, :cert, o :prod (por defecto es :test)
config.environment = ENV.fetch("ECF_ENVIRONMENT", "test").to_sym
# URL base personalizada (opcional, sobreescribe el ambiente)
# config.base_url = ENV["ECF_API_URL"]
# Tiempo de espera máximo para solicitudes HTTP (por defecto es 30 segundos)
# config.timeout = 30
end
Una vez configurado el inicializador, puedes acceder al cliente en cualquier parte de tu aplicación de Rails:
client = EcfDgii.client
Funcionalidades de envío y Polling
El método send_ecf_and_poll maneja de forma transparente:
- Enrutamiento dinámico — selecciona el endpoint correcto según el atributo
tipoe_cfde la cabecera. - Polling con backoff exponencial — consulta repetidamente el estado de procesamiento del eCF hasta llegar a un estado terminal (
Completed,FailedoRejected). - Manejo de tiempos límite y reintentos — lanza excepciones controladas si se excede el timeout de espera.
Para personalizar los tiempos de espera y el backoff exponencial:
= EcfDgii::PollingOptions.new(
initial_delay: 2.0, # Segundos antes de la primera consulta
max_delay: 30.0, # Límite máximo de espera entre consultas
max_retries: 50, # Intentos máximos de consulta (0 para ilimitado)
backoff_multiplier: 1.5, # Multiplicador del retraso en cada intento
timeout: 300.0 # Timeout total en segundos
)
result = client.send_ecf_and_poll(ecf, )
Arquitectura Backend / Frontend
sequenceDiagram
participant C as Cliente (Browser/App)
participant BE as Backend (Rails)
participant ECF as ECF API
C->>BE: POST /invoice (datos de factura)
Note over BE: Valida, guarda y convierte a formato eCF
BE->>ECF: POST /ecf/{tipo} (enviar eCF)
ECF-->>BE: { messageId }
BE-->>C: { messageId }
Note over C: No espera — puede continuar
alt Token en cache
C->>C: Usar token existente
else Sin token o expirado
C->>BE: GET /ecf-token
BE->>ECF: POST /apikey (solo lectura, scoped a RNC)
ECF-->>BE: { apiKey }
BE-->>C: { apiKey }
C->>C: Almacenar token en cache
end
loop Polling hasta completar
C->>ECF: GET /ecf/{rnc}/{encf} (token solo lectura)
ECF-->>C: { progress, codSec, ... }
end
- Tu backend en Rails convierte las facturas internas a los modelos del eCF y los transmite con
client.send_ecf(ecf). - La API de ECF responde de inmediato con un
messageIdpara que el backend Rails responda al cliente web sin bloquearse. - El frontend del browser puede pedir un token de API de sólo lectura llamando a un endpoint en Rails (el cual usa
client.new_company_api_key), para después hacer polling directamente contra la API de eCF de forma segura.
Gestión de Empresas y Certificados
Operaciones de Empresa
# Listar empresas registradas
companies = client.get_companies(page: 1, limit: 10)
# Obtener los datos de una empresa específica por RNC
company = client.get_company_by_rnc("101001010")
# Registrar o actualizar datos de una empresa
req = EcfDgii::Generated::UpsertCompanyRequest.new(
rnc: "101001010",
legal_name: "Empresa de Pruebas SRL",
name: "Empresa de Pruebas"
)
client.upsert_company(req)
# Eliminar empresa
client.delete_company("101001010")
Certificados P12
# Obtener el certificado actual de la empresa
certificate = client.get_current_certificate("101001010")
# Subir/actualizar un certificado de firma digital P12
file = File.open("ruta/al/certificado.p12", "r+b")
client.update_certificate_company("101001010", file, "clave-del-certificado")
Consultas DGII
# Consultar estado actual del eCF
estado = client.consulta_estado("101001010", "101001010", "E310000051630", "131880681", "ABC123")
# Consultar directorio de emisores electrónicos activos en DGII
directorio = client.consulta_directorio("101001010")
# Consultar estado de procesamiento mediante el track_id obtenido
resultado = client.consulta_resultado("101001010", "track-id-obtenido")
# Obtener estatus general de los servicios de la DGII
estatus = client.estatus_servicio("101001010")
Manejo de errores
El SDK utiliza el control estructurado de excepciones. Puedes capturar los errores comunes del API Client y los tiempos límite del polling de la siguiente manera:
begin
result = client.send_ecf_and_poll(ecf)
rescue EcfDgii::PollingTimeoutError => e
puts "El polling tomó más tiempo de lo permitido: #{e.}"
rescue EcfDgii::PollingMaxRetriesError => e
puts "Se superó el número máximo de reintentos: #{e.}"
rescue EcfDgii::Generated::ApiError => e
puts "Error de API de ECF (Estatus: #{e.code})"
puts "Respuesta: #{e.response_body}"
rescue => e
puts "Ocurrió un error inesperado: #{e.}"
end
Licencia
La gema está disponible como software de código abierto bajo los términos de la Licencia MIT.