ECFDllVB6 SDK

Biblioteca COM para manejo de e-CF DGII • Resúmenes • Envíos • QR Oficial

Desarrollada por: Natanael Sanchez M.

📌 Descripción General

ECFDllVB6 es una biblioteca COM desarrollada en C# (.NET Framework 4.x), diseñada para ser utilizada desde Delphi, VB6, VB.NET, C++ y cualquier lenguaje compatible COM.

Permite procesar comprobantes electrónicos DGII (e-CF), generar resúmenes RFCE, enviar XML firmados y generar códigos QR oficiales.

⬇️ Descargas

⚙️ Instalación y Registro COM

1. Copie los archivos en una carpeta segura.

2. Abra una consola como Administrador.

C:\Windows\Microsoft.NET\Framework\v4.0.30319\regasm.exe "ECFDllVB6.dll" /codebase /tlb

3. Importe la Type Library en Delphi:

Component → Import Component → Import a Type Library

📚 Métodos Disponibles

string FirmarXml(string rutaXml, string rutaCertifcado, string claveCertificado)

Firmar Documento XML.

string GenerarResumen(string rutaXmlFirmado)

Generar el XML RFCE dinámico a partir de un e-CF firmado.

void GuardarXml(string ruta, string texto)

Guarda texto UTF-8 sin BOM.

void GuardarResumen(string ruta, string texto)

Guarda texto UTF-8 sin BOM.

string GenerarQr(string xmlInput)

Genera la URL oficial DGII para el QR.

void GenerarQRImagen(string texto, string rutaImagenPng)

Descarga y guarda el QR como imagen PNG.

string ObtenerCodigoSeguridad(string rutaXmlFirmado)

Extrae los primeros 6 caracteres del SignatureValue.

string ObtenerFechaHoraFirma(string rutaXmlFirmado)

Extrae Fecha y Hora de la Firma XML.

string EnviarXml(string xmlFile, string urlEnvio, string token)

Envía un XML firmado a DGII usando multipart/form-data.

DgiiRespuesta RespuestaDGII(string json)

Obtener respuesta de la DGII:

  -Success 
  -TrackId 
  -Codigo 
  -Estado 
  -Rnc 
  -Encf 
  -SecuenciaUtilizada 
  -FechaRecepcion 
  -CodigoMensaje 
  -Mensaje 
  -Qr

🧪 Ejemplo Completo en Oracle (PL/SQL)

Ejemplo equivalente en Oracle usando PL/SQL. Este ejemplo simula el comportamiento de la DLL utilizando herramientas nativas como UTL_HTTP y DBMS_CRYPTO.


SET SERVEROUTPUT ON;

DECLARE
    -- Configuración
    ruta_xml        VARCHAR2(500) := '133398771E320000000875.xml';
    url_envio       VARCHAR2(500) := 'https://portalmultiprod.com/api/certecf/enviar/2dbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
    token           VARCHAR2(200) := 'token';

    xml_content     CLOB;
    respuesta       CLOB := EMPTY_CLOB();
    codigo_seguridad VARCHAR2(200);
    qr_text         VARCHAR2(500);

    req   UTL_HTTP.req;
    resp  UTL_HTTP.resp;
    buffer VARCHAR2(32767);

BEGIN
    DBMS_OUTPUT.PUT_LINE('--- INICIO PROCESO ---');

    -------------------------------------------------
    -- 1. Generar Resumen (Simulado)
    -------------------------------------------------
    xml_content := '<xml>Contenido de prueba</xml>';
    DBMS_OUTPUT.PUT_LINE('Resumen generado correctamente');

    -------------------------------------------------
    -- 2. Guardar XML (Simulado)
    -------------------------------------------------
    DBMS_OUTPUT.PUT_LINE('XML guardado correctamente');

    -------------------------------------------------
    -- 3. Enviar XML (HTTP POST)
    -------------------------------------------------
    req := UTL_HTTP.begin_request(url_envio, 'POST');

    UTL_HTTP.set_header(req, 'Content-Type', 'application/xml');
    UTL_HTTP.set_header(req, 'Authorization', 'Bearer ' || token);

    UTL_HTTP.write_text(req, xml_content);

    resp := UTL_HTTP.get_response(req);

    BEGIN
        LOOP
            UTL_HTTP.read_text(resp, buffer);
            respuesta := respuesta || buffer;
        END LOOP;
    EXCEPTION
        WHEN UTL_HTTP.end_of_body THEN
            UTL_HTTP.end_response(resp);
    END;

    DBMS_OUTPUT.PUT_LINE('Resultado Envío: ' || SUBSTR(respuesta,1,200));

    -------------------------------------------------
    -- 4. Código de Seguridad
    -------------------------------------------------
    codigo_seguridad := RAWTOHEX(
        DBMS_CRYPTO.HASH(
            UTL_RAW.cast_to_raw(xml_content),
            DBMS_CRYPTO.HASH_SH256
        )
    );

    DBMS_OUTPUT.PUT_LINE('Código Seguridad: ' || codigo_seguridad);

    -------------------------------------------------
    -- 5. Generar QR (Simulado)
    -------------------------------------------------
    qr_text := 'QR|' || codigo_seguridad;
    DBMS_OUTPUT.PUT_LINE('QR generado: ' || qr_text);

    -------------------------------------------------
    -- 6. Firma XML (Simulada)
    -------------------------------------------------
    DBMS_OUTPUT.PUT_LINE('XML firmado correctamente (simulado)');

    DBMS_OUTPUT.PUT_LINE('--- FIN PROCESO ---');

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('ERROR: ' || SQLERRM);
END;
/
      

⚠️ Requisitos en Oracle

Antes de ejecutar, debes habilitar permisos de red:


BEGIN
  DBMS_NETWORK_ACL_ADMIN.APPEND_HOST_ACE(
    host => '*',
    ace  => xs$ace_type(
      privilege_list => xs$name_list('http'),
      principal_name => 'TU_USUARIO',
      principal_type => xs_acl.ptype_db
    )
  );
END;
/
      

🚨 Nota Importante

Oracle no soporta de forma nativa:

  • Firma digital XML (XAdES)
  • Generación de QR en imagen
  • Uso de DLL COM

Para producción se recomienda usar un API intermedio (C#, Node o Delphi) que consuma esta DLL y sea llamado desde Oracle.

🧪 Ejemplo Completo en Delphi

unit HelloWorld;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, 
  Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Imaging.pngimage, 
  ECFDllVB6_TLB; // Librería de Facturación Electrónica

type
  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  util            : IUtilidades;
  rutaXml         : string;
  rutaSalidaXml   : string;
  xmlName         : string;
  resultadoXml    : string;
  urlEnvio        : string;
  token           : string;
  resultadoEnvio  : string;
  codigoSeguridad : string;
  qr              : string;
  qrPath          : string;
  xmlFirmado, rutaCert, passcert : string;
begin
  // --- Configuración de parámetros ---
  rutaCert := 'C:\Users\MeMeBigBoy\Desktop\certificado_identity.p12';
  passCert := 'clave1234';
  token    := 'token';
  urlEnvio := 'https://portalmultiprod.com/api/certecf/enviar/2dbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
  xmlName  := '133398771E320000000875.xml';
  
  // Definición de rutas
  rutaXml       := 'C:\Users\MeMeBigBoy\Desktop\' + xmlName;
  rutaSalidaXml := 'C:\Users\MeMeBigBoy\Desktop\resumen\' + xmlName;
  qrPath        := 'C:\Users\MeMeBigBoy\Desktop\qr\' + xmlName + '.png';

  // --- Ejecución de procesos con la DLL ---
  util := CoUtilidades.Create;

  // 1. Generar y Guardar Resumen
  resultadoXml := util.GenerarResumen(rutaXml);
  ShowMessage('Resumen generado: ' + resultadoXml);
  
  util.GuardarResumen(rutaSalidaXml, resultadoXml);
  ShowMessage('XML guardado correctamente en: ' + rutaSalidaXml);

  // 2. Enviar XML al Web Service
  resultadoEnvio := util.EnviarXml(rutaXml, urlEnvio, token);
  ShowMessage('Resultado Envío: ' + resultadoEnvio);

  // 3. Obtener Código de Seguridad
  codigoSeguridad := util.ObtenerCodigoSeguridad(rutaXml);
  ShowMessage('Código de Seguridad: ' + codigoSeguridad);

  // 4. Manejo de QR
  qr := util.GenerarQr(rutaXml);
  util.GenerarQRImagen(qr, qrPath);
  
  // Cargar imagen en el formulario
  Image1.Picture.LoadFromFile(qrPath);
  Edit1.Text := qr;

  // 5. Firma y Guardar XML
  xmlName := '133398771E310000000004.xml';

  rutaXml := 'C:\Users\MeMeBigBoy\Desktop\' + xmlName;
  xmlFirmado := util.FirmarXml(rutaXml, rutaCert, passCert);

  rutaXml := 'C:\Users\MeMeBigBoy\Desktop\firmados\' + xmlName;
  util.GuardarXml(rutaXml, xmlFirmado);

end;

end.

🧪 Delphi TLB



unit ECFDllVB6_TLB;

// ************************************************************************ //
// WARNING                                                                    
// -------                                                                    
// The types declared in this file were generated from data read from a       
// Type Library. If this type library is explicitly or indirectly (via        
// another type library referring to this type library) re-imported, or the   
// 'Refresh' command of the Type Library Editor activated while editing the   
// Type Library, the contents of this file will be regenerated and all        
// manual modifications will be lost.                                         
// ************************************************************************ //

// $Rev: 98336 $
// File generated on 1/1/2026 7:01:37 PM from Type Library described below.

// ************************************************************************  //
// Type Lib: C:\Windows\SysWow64\ECFDllVB6.tlb (1)
// LIBID: {707DA5C4-0A53-33D5-8CD3-CDB703374C0E}
// LCID: 0
// Helpfile: 
// HelpString: 
// DepndLst: 
//   (1) v2.0 stdole, (C:\Windows\SysWOW64\stdole2.tlb)
//   (2) v2.4 mscorlib, (C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb)
// SYS_KIND: SYS_WIN32
// ************************************************************************ //
{$TYPEDADDRESS OFF} // Unit must be compiled without type-checked pointers. 
{$WARN SYMBOL_PLATFORM OFF}
{$WRITEABLECONST ON}
{$VARPROPSETTER ON}
{$ALIGN 4}

interface

uses Winapi.Windows, mscorlib_TLB, System.Classes, System.Variants, System.Win.StdVCL, Vcl.Graphics, Vcl.OleServer, Winapi.ActiveX;
  


// *********************************************************************//
// GUIDS declared in the TypeLibrary. Following prefixes are used:        
//   Type Libraries     : LIBID_xxxx                                      
//   CoClasses          : CLASS_xxxx                                      
//   DISPInterfaces     : DIID_xxxx                                       
//   Non-DISP interfaces: IID_xxxx                                        
// *********************************************************************//
const
  // TypeLibrary Major and minor versions
  ECFDllVB6MajorVersion = 1;
  ECFDllVB6MinorVersion = 0;

  LIBID_ECFDllVB6: TGUID = '{707DA5C4-0A53-33D5-8CD3-CDB703374C0E}';

  DIID_IUtilidades: TGUID = '{E2F6C6F2-2C2A-4A9C-9F89-111111111111}';
  CLASS_Utilidades: TGUID = '{A9B4FDC4-9C3F-4B8E-8A77-222222222222}';
type

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary                    
// *********************************************************************//
  IUtilidades = dispinterface;

// *********************************************************************//
// Declaration of CoClasses defined in Type Library                       
// (NOTE: Here we map each CoClass to its Default Interface)              
// *********************************************************************//
  Utilidades = IUtilidades;


// *********************************************************************//
// DispIntf:  IUtilidades
// Flags:     (4096) Dispatchable
// GUID:      {E2F6C6F2-2C2A-4A9C-9F89-111111111111}
// *********************************************************************//
  IUtilidades = dispinterface
    ['{E2F6C6F2-2C2A-4A9C-9F89-111111111111}']
    function GenerarResumen(const rutaXmlFirmado: WideString): WideString; dispid 1610743808;
    function EnviarXml(const xmlFile: WideString; const urlEnvio: WideString; 
                       const token: WideString): WideString; dispid 1610743809;
    procedure GuardarResumen(const ruta: WideString; const texto: WideString); dispid 1610743810;
    procedure GuardarXml(const ruta: WideString; const texto: WideString); dispid 1610743811;
    function ObtenerCodigoSeguridad(const rutaXmlFirmado: WideString): WideString; dispid 1610743812;
    function GenerarQr(const xmlInput: WideString): WideString; dispid 1610743813;
    procedure GenerarQRImagen(const texto: WideString; const rutaImagenPng: WideString); dispid 1610743814;
    function FirmarXml(const rutaXml: WideString; const rutaP12: WideString; const clave: WideString): WideString; dispid 1610743815;
  end;

// *********************************************************************//
// The Class CoUtilidades provides a Create and CreateRemote method to          
// create instances of the default interface IUtilidades exposed by              
// the CoClass Utilidades. The functions are intended to be used by             
// clients wishing to automate the CoClass objects exposed by the         
// server of this typelibrary.                                            
// *********************************************************************//
  CoUtilidades = class
    class function Create: IUtilidades;
    class function CreateRemote(const MachineName: string): IUtilidades;
  end;

implementation

uses System.Win.ComObj;

class function CoUtilidades.Create: IUtilidades;
begin
  Result := CreateComObject(CLASS_Utilidades) as IUtilidades;
end;

class function CoUtilidades.CreateRemote(const MachineName: string): IUtilidades;
begin
  Result := CreateRemoteComObject(MachineName, CLASS_Utilidades) as IUtilidades;
end;

end.
            

🧪 Ejemplo Completo en Visual Basic 6

' Ejemplo de uso en VB6

Option Explicit

Private Sub Command1_Click()
    ' Declaración de variables (utilizando el objeto de la DLL)
    Dim util            As ECFDllVB6.Utilidades
    Dim rutaXml         As String
    Dim rutaSalidaXml   As String
    Dim xmlName         As String
    Dim resultadoXml    As String
    Dim urlEnvio        As String
    Dim token           As String
    Dim resultadoEnvio  As String
    Dim codigoSeguridad As String
    Dim qr              As String
    Dim qrPath          As String
    Dim cert            As String
    Dim pass            As String
    Dim xmlFirmado      As String
    Dim rutaXmlFirmado  As String
    Dim respuestaDGII As Object
    cert = "C:\Users\MeMeBigBoy\Desktop\certificado_identity.p12"
    pass = "clave1234"

    On Error GoTo ErrorHandler

    ' --- Configuración de parámetros ---
    token = "token"
    urlEnvio = "https://portalmultiprod.com/api/certecf/enviar/2dbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    xmlName = "133398771E320000000875.xml"
    
    ' Definición de rutas (ajusta según tu entorno)
    rutaXml = "C:\Users\MeMeBigBoy\Desktop\" & xmlName
    rutaSalidaXml = "C:\Users\MeMeBigBoy\Desktop\resumen\" & xmlName
    qrPath = "C:\Users\MeMeBigBoy\Desktop\qr\" & xmlName & ".png"

    rutaXmlFirmado = "C:\Users\MeMeBigBoy\Desktop\firmados\" & xmlName

    ' --- Inicialización del objeto COM ---
    Set util = New ECFDllVB6.Utilidades

    ' 1. Generar y Guardar Resumen
    resultadoXml = util.GenerarResumen(rutaXml)
    MsgBox "Resumen generado: " & resultadoXml, vbInformation
    
    util.GuardarResumen rutaSalidaXml, resultadoXml
    MsgBox "XML guardado correctamente en: " & rutaSalidaXml, vbInformation

    ' 2. Enviar XML al Web Service
    resultadoEnvio = util.EnviarXml(rutaXml, urlEnvio, token)
    MsgBox "Resultado Envío: " & resultadoEnvio, vbInformation

    ' Respuestad DGII
    Set respuestaDGII = util.respuestaDGII(resultadoEnvio)
    MsgBox (respuestaDGII.Estado & " / " & respuestaDGII.Trackid)

    ' 3. Obtener Código de Seguridad
    codigoSeguridad = util.ObtenerCodigoSeguridad(rutaXml)
    MsgBox "Código de Seguridad: " & codigoSeguridad, vbInformation

    ' 4. Manejo de QR
    qr = util.GenerarQr(rutaXml)
    util.GenerarQRImagen qr, qrPath
    
    ' Mostrar en los controles del formulario
    ' Nota: VB6 nativo no soporta PNG en el control Image estándar sin ayuda de GDI+
    ' pero si la DLL genera el archivo, intentamos cargarlo:
    On Error Resume Next
    Set Image1.Picture = LoadPicture(qrPath)
    On Error GoTo ErrorHandler
    
    Text1.Text = qr

    ' 5. Firmar y Guardar XML
    xmlFirmado = u.FirmarXml(rutaXml, cert, pass)
    u.GuardarXml rutaXmlFirmado, xmlFirmado
    Debug.Print xmlFirmado

    ' Limpieza
    Set util = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "Error: " & Err.Description, vbCritical, "Error de Ejecución"
    Set util = Nothing
End Sub

🧪 Ejemplo Completo en C#


using System;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
// Asegúrate de agregar la referencia COM para que este namespace esté disponible
using ECFDllVB6; 

namespace ProyectoFacturacion
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnProcesar_Click(object sender, EventArgs e)
        {
            // Declaración de variables
            // En C#, la interfaz IUtilidades suele ser expuesta por la clase Utilidades
            Utilidades util;
            string rutaXml;
            string rutaSalidaXml;
            string xmlName;
            string resultadoXml;
            string urlEnvio;
            string token;
            string resultadoEnvio;
            string codigoSeguridad;
            string qr;
            string qrPath;

            try
            {
                // --- Configuración de parámetros ---
                token = "token";
                urlEnvio = "https://portalmultiprod.com/api/certecf/enviar/2dbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                xmlName = "133398771E320000000875.xml";

                // Definición de rutas (Usando @ para ignorar caracteres de escape)
                string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
                rutaXml = Path.Combine(desktopPath, xmlName);
                rutaSalidaXml = Path.Combine(desktopPath, "resumen", xmlName);
                qrPath = Path.Combine(desktopPath, "qr", xmlName + ".png");

                // --- Ejecución de procesos con la DLL ---
                util = new Utilidades();

                // 1. Generar y Guardar Resumen
                resultadoXml = util.GenerarResumen(rutaXml);
                MessageBox.Show("Resumen generado: " + resultadoXml, "Información", MessageBoxButtons.OK, MessageBoxIcon.Information);

                util.GuardarResumen(rutaSalidaXml, resultadoXml);
                MessageBox.Show("XML guardado correctamente en: " + rutaSalidaXml);

                // 2. Enviar XML al Web Service
                resultadoEnvio = util.EnviarXml(rutaXml, urlEnvio, token);
                MessageBox.Show("Resultado Envío: " + resultadoEnvio);

                // 3. Obtener Código de Seguridad
                codigoSeguridad = util.ObtenerCodigoSeguridad(rutaXml);
                MessageBox.Show("Código de Seguridad: " + codigoSeguridad);

                // 4. Manejo de QR
                qr = util.GenerarQr(rutaXml);
                util.GenerarQRImagen(qr, qrPath);

                // Cargar imagen en el PictureBox (Equivalente a TImage)
                if (File.Exists(qrPath))
                {
                    pictureBox1.Image = Image.FromFile(qrPath);
                }
                
                // Mostrar texto del QR en un TextBox (Equivalente a TEdit)
                txtQr.Text = qr;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: " + ex.Message, "Error de ejecución", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

🧪 Ejemplo Completo en C# (Consola)


using System;
using System.IO;
// Asegúrate de haber agregado la referencia COM a ECFDllVB6
using ECFDllVB6; 

namespace FacturacionConsola
{
    class Program
    {
        static void Main(string[] args)
        {
            // --- Declaración de variables ---
            Utilidades util;
            string rutaXml;
            string rutaSalidaXml;
            string xmlName;
            string resultadoXml;
            string urlEnvio;
            string token;
            string resultadoEnvio;
            string codigoSeguridad;
            string qr;
            string qrPath;

            Console.WriteLine("========================================");
            Console.WriteLine("   PROCESAMIENTO DE E-CF (CONSOLA)      ");
            Console.WriteLine("========================================\n");

            try
            {
                // --- Configuración de parámetros ---
                token = "token";
                urlEnvio = "https://portalmultiprod.com/api/certecf/enviar/2dbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                xmlName = "133398771E320000000875.xml";

                // Definición de rutas dinámicas al Escritorio
                string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
                rutaXml = Path.Combine(desktopPath, xmlName);
                rutaSalidaXml = Path.Combine(desktopPath, "resumen", xmlName);
                qrPath = Path.Combine(desktopPath, "qr", xmlName + ".png");

                // --- Inicialización del objeto COM ---
                util = new Utilidades();

                // 1. Generar Resumen
                Console.WriteLine("-> Generando Resumen...");
                resultadoXml = util.GenerarResumen(rutaXml);
                Console.WriteLine("Resumen generado con éxito.");

                // Guardar Resumen
                util.GuardarResumen(rutaSalidaXml, resultadoXml);
                Console.WriteLine($"Archivo guardado en: {rutaSalidaXml}");

                // 2. Enviar XML
                Console.WriteLine("\n-> Enviando XML a DGII...");
                resultadoEnvio = util.EnviarXml(rutaXml, urlEnvio, token);
                Console.WriteLine($"Resultado Envío: {resultadoEnvio}");

                // 3. Obtener Código de Seguridad
                codigoSeguridad = util.ObtenerCodigoSeguridad(rutaXml);
                Console.WriteLine($"\n-> Código de Seguridad: {codigoSeguridad}");

                // 4. Manejo de QR
                Console.WriteLine("\n-> Generando QR...");
                qr = util.GenerarQr(rutaXml);
                util.GenerarQRImagen(qr, qrPath);
                
                Console.WriteLine($"URL del QR: {qr}");
                Console.WriteLine($"Imagen QR guardada en: {qrPath}");

                Console.WriteLine("\n========================================");
                Console.WriteLine("   PROCESO FINALIZADO CORRECTAMENTE     ");
                Console.WriteLine("========================================");
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"\n[ERROR]: {ex.Message}");
                Console.ResetColor();
            }

            Console.WriteLine("\nPresione cualquier tecla para salir...");
            Console.ReadKey();
        }
    }
}

🧪 Ejemplo Completo en C# (Consola 2)


using System;
using System.IO;
using ECFDllVB6; 

namespace FacturacionBatch
{
    class Program
    {
        static void Main(string[] args)
        {
            // Instancia de la DLL
            Utilidades util = new Utilidades();

            // --- Configuración de Parámetros ---
            string token = "token";
            string urlEnvio = "https://portalmultiprod.com/api/certecf/enviar/2dbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
            
            // Rutas principales
            string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            string carpetaEntrada = Path.Combine(desktopPath, "Facturas_Para_Procesar");
            string carpetaResumen = Path.Combine(desktopPath, "resumen");
            string carpetaQR = Path.Combine(desktopPath, "qr");

            // Crear carpetas si no existen
            Directory.CreateDirectory(carpetaEntrada);
            Directory.CreateDirectory(carpetaResumen);
            Directory.CreateDirectory(carpetaQR);

            Console.WriteLine("========================================");
            Console.WriteLine("    BATCH PROCESSOR - e-CF DGII");
            Console.WriteLine("========================================\n");
            Console.WriteLine($"Buscando XMLs en: {carpetaEntrada}\n");

            // Obtener todos los archivos XML de la carpeta de entrada
            string[] archivos = Directory.GetFiles(carpetaEntrada, "*.xml");

            if (archivos.Length == 0)
            {
                Console.WriteLine("No se encontraron archivos XML para procesar.");
            }
            else
            {
                foreach (string rutaXml in archivos)
                {
                    string nombreArchivo = Path.GetFileName(rutaXml);
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine($"--- Procesando: {nombreArchivo} ---");
                    Console.ResetColor();

                    try
                    {
                        // 1. Generar y Guardar Resumen
                        string resultadoXml = util.GenerarResumen(rutaXml);
                        string rutaSalidaXml = Path.Combine(carpetaResumen, nombreArchivo);
                        util.GuardarResumen(rutaSalidaXml, resultadoXml);
                        Console.WriteLine("[OK] Resumen generado y guardado.");

                        // 2. Enviar XML
                        string resultadoEnvio = util.EnviarXml(rutaXml, urlEnvio, token);
                        Console.WriteLine($"[OK] Envío DGII: {resultadoEnvio}");

                        // 3. Obtener Código de Seguridad
                        string codigoSeguridad = util.ObtenerCodigoSeguridad(rutaXml);
                        Console.WriteLine($"[OK] Código Seguridad: {codigoSeguridad}");

                        // 4. Generar Imagen QR
                        string qrUrl = util.GenerarQr(rutaXml);
                        string rutaImagenQr = Path.Combine(carpetaQR, nombreArchivo + ".png");
                        util.GenerarQRImagen(qrUrl, rutaImagenQr);
                        Console.WriteLine("[OK] Imagen QR generada.");

                        Console.WriteLine("----------------------------------------\n");
                    }
                    catch (Exception ex)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine($"[ERROR] en {nombreArchivo}: {ex.Message}");
                        Console.ResetColor();
                    }
                }
            }

            Console.WriteLine("========================================");
            Console.WriteLine("        PROCESAMIENTO FINALIZADO");
            Console.WriteLine("========================================");
            Console.WriteLine("Presione cualquier tecla para salir...");
            Console.ReadKey();
        }
    }
}

🧪 CODIGO FUENTE DLL (C#)


using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using System.Net;
using System.Globalization;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Web.Script.Serialization;

namespace ECFDllVB6
{
    // ======================
    // INTERFAZ COM
    // ======================
    [ComVisible(true)]
    [Guid("E2F6C6F2-2C2A-4A9C-9F89-111111111111")]
    // [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IUtilidades
    {
        string GenerarResumen(string rutaXmlFirmado);
        string EnviarXml(string xmlFile, string urlEnvio, string token);
        void GuardarResumen(string ruta, string texto);
        void GuardarXml(string ruta, string texto);

        string ObtenerCodigoSeguridad(string rutaXmlFirmado);
        string ObtenerFechaHoraFirma(string rutaXmlFirmado);
        string GenerarQr(string xmlInput);
        void GenerarQRImagen(string texto, string rutaImagenPng);
        string FirmarXml(string rutaXml, string rutaP12, string clave);

        DgiiRespuesta RespuestaDGII(string json);

    }

    // ======================
    // CLASE COM
    // ======================
    [ComVisible(true)]
    [Guid("A9B4FDC4-9C3F-4B8E-8A77-222222222222")]
    [ProgId("ECFDllVB6.Utilidades")]
    [ClassInterface(ClassInterfaceType.None)]
    public class Utilidades : IUtilidades
    {
        // ======================
        // CONVERSIÓN ANSI (VB6)
        // ======================
        private string ToAnsi(string texto)
        {
            if (string.IsNullOrEmpty(texto))
                return texto;

            byte[] bytes = Encoding.Default.GetBytes(texto);
            return Encoding.Default.GetString(bytes);
        }

        // ======================
        // GUARDA TEXTO UTF-8
        // ======================
        public void GuardarResumen(string ruta, string texto)
        {
            File.WriteAllText(ruta, texto, new UTF8Encoding(false));
        }

        public void GuardarXml(string ruta, string texto)
        {
            File.WriteAllText(ruta, texto, new UTF8Encoding(false));
        }

        // ======================
        // OBTENER CÓDIGO SEGURIDAD (6 PRIMEROS)
        // ======================
        public string ObtenerCodigoSeguridad(string rutaXmlFirmado)
        {
            if (!File.Exists(rutaXmlFirmado))
                throw new Exception(ToAnsi($"No se encontró el archivo XML en: {rutaXmlFirmado}"));

            XmlDocument dom = new XmlDocument();
            dom.PreserveWhitespace = false;
            dom.Load(rutaXmlFirmado);

            XmlNode sig = dom.SelectSingleNode("//*[local-name()='SignatureValue']");
            if (sig == null || string.IsNullOrWhiteSpace(sig.InnerText))
                throw new Exception(ToAnsi("No se encontró SignatureValue en el XML."));

            string valor = sig.InnerText.Trim();
            return valor.Length >= 6 ? valor.Substring(0, 6) : valor;
        }

        // ======================
        // OBTENER FECHA HORA FIRMA
        // ======================    
        public string ObtenerFechaHoraFirma(string rutaXmlFirmado)
        {
            if (!File.Exists(rutaXmlFirmado))
                throw new Exception(ToAnsi($"No se encontró el archivo XML en: {rutaXmlFirmado}"));

            XmlDocument dom = new XmlDocument();
            dom.PreserveWhitespace = false;
            dom.Load(rutaXmlFirmado);

            XmlNode nodo = dom.SelectSingleNode("//*[local-name()='FechaHoraFirma']");

            if (nodo == null || string.IsNullOrWhiteSpace(nodo.InnerText))
                throw new Exception(ToAnsi("No se encontró el tag FechaHoraFirma en el XML."));

            return nodo.InnerText.Trim();
        }


        // ======================
        // GENERAR RESUMEN RFCE
        // ======================
       
public string GenerarResumen(string rutaXmlFirmado)
{
    if (!File.Exists(rutaXmlFirmado))
        throw new Exception(ToAnsi($"No se encontró el archivo XML en: {rutaXmlFirmado}"));

    XmlDocument dom = new XmlDocument();
    dom.PreserveWhitespace = false;
    dom.Load(rutaXmlFirmado);

    XmlNode sig = dom.SelectSingleNode("//*[local-name()='SignatureValue']");
    if (sig == null)
        throw new Exception(ToAnsi("No se encontró SignatureValue en el XML."));

    string codigoSeguridad = sig.InnerText.Trim();
    codigoSeguridad = codigoSeguridad.Length >= 6
        ? codigoSeguridad.Substring(0, 6)
        : codigoSeguridad;

    XmlNode enc = dom.SelectSingleNode("//*[local-name()='Encabezado']");
    if (enc == null)
        throw new Exception(ToAnsi("No se encontró el nodo Encabezado."));

    XmlDocument res = new XmlDocument();
    res.AppendChild(res.CreateXmlDeclaration("1.0", "UTF-8", null));

    XmlElement rfceNode = res.CreateElement("RFCE");
    res.AppendChild(rfceNode);

    XmlElement encNode = res.CreateElement("Encabezado");
    rfceNode.AppendChild(encNode);

    void Add(XmlNode parent, string name, string value)
    {
        if (string.IsNullOrWhiteSpace(value)) return;
        XmlElement e = res.CreateElement(name);
        e.InnerText = value;
        parent.AppendChild(e);
    }

    // ======================
    // Version
    // ======================
    Add(encNode, "Version", enc["Version"]?.InnerText);

    // ======================
    // IdDoc
    // ======================
    XmlElement idDocNode = res.CreateElement("IdDoc");
    XmlNode srcIdDoc = enc["IdDoc"];

    Add(idDocNode, "TipoeCF", srcIdDoc["TipoeCF"]?.InnerText);
    Add(idDocNode, "eNCF", srcIdDoc["eNCF"]?.InnerText);
    Add(idDocNode, "TipoIngresos", srcIdDoc["TipoIngresos"]?.InnerText);
    Add(idDocNode, "TipoPago", srcIdDoc["TipoPago"]?.InnerText);

    XmlNode tablaPagoSrc = srcIdDoc["TablaFormasPago"];
    if (tablaPagoSrc != null)
    {
        XmlElement tablaPagoNode = res.CreateElement("TablaFormasPago");

        foreach (XmlNode fp in tablaPagoSrc.SelectNodes("FormaDePago"))
        {
            XmlElement formaNode = res.CreateElement("FormaDePago");
            Add(formaNode, "FormaPago", fp["FormaPago"]?.InnerText);
            Add(formaNode, "MontoPago", fp["MontoPago"]?.InnerText);
            tablaPagoNode.AppendChild(formaNode);
        }

        idDocNode.AppendChild(tablaPagoNode);
    }

    encNode.AppendChild(idDocNode);

    // ======================
    // Emisor
    // ======================
    XmlElement emisorNode = res.CreateElement("Emisor");
    Add(emisorNode, "RNCEmisor", enc["Emisor"]?["RNCEmisor"]?.InnerText);
    Add(emisorNode, "RazonSocialEmisor", enc["Emisor"]?["RazonSocialEmisor"]?.InnerText);
    Add(emisorNode, "FechaEmision", enc["Emisor"]?["FechaEmision"]?.InnerText);
    encNode.AppendChild(emisorNode);

    // ======================
    // Comprador
    // ======================
    XmlNode srcComprador = enc["Comprador"];
    if (srcComprador != null)
    {
        XmlElement compradorNode = res.CreateElement("Comprador");
        Add(compradorNode, "RNCComprador", srcComprador["RNCComprador"]?.InnerText);
        Add(compradorNode, "IdentificadorExtranjero", srcComprador["IdentificadorExtranjero"]?.InnerText);
        Add(compradorNode, "RazonSocialComprador", srcComprador["RazonSocialComprador"]?.InnerText);
        encNode.AppendChild(compradorNode);
    }

    // ======================
    // Totales (FILTRADO POR XSD)
    // ======================
    XmlElement totalesNode = res.CreateElement("Totales");
    XmlNode t = enc["Totales"];

    Add(totalesNode, "MontoGravadoTotal", t["MontoGravadoTotal"]?.InnerText);
    Add(totalesNode, "MontoGravadoI1", t["MontoGravadoI1"]?.InnerText);
    Add(totalesNode, "MontoGravadoI2", t["MontoGravadoI2"]?.InnerText);
    Add(totalesNode, "MontoGravadoI3", t["MontoGravadoI3"]?.InnerText);
    Add(totalesNode, "MontoExento", t["MontoExento"]?.InnerText);
    Add(totalesNode, "TotalITBIS", t["TotalITBIS"]?.InnerText);
    Add(totalesNode, "TotalITBIS1", t["TotalITBIS1"]?.InnerText);
    Add(totalesNode, "TotalITBIS2", t["TotalITBIS2"]?.InnerText);
    Add(totalesNode, "TotalITBIS3", t["TotalITBIS3"]?.InnerText);
    Add(totalesNode, "MontoImpuestoAdicional", t["MontoImpuestoAdicional"]?.InnerText);

    XmlNode impAds = t["ImpuestosAdicionales"];
    if (impAds != null)
    {
        XmlElement impAdsNode = res.CreateElement("ImpuestosAdicionales");

        foreach (XmlNode imp in impAds.SelectNodes("ImpuestoAdicional"))
        {
            XmlElement impNode = res.CreateElement("ImpuestoAdicional");
            Add(impNode, "TipoImpuesto", imp["TipoImpuesto"]?.InnerText);
            Add(impNode, "MontoImpuestoSelectivoConsumoEspecifico", imp["MontoImpuestoSelectivoConsumoEspecifico"]?.InnerText);
            Add(impNode, "MontoImpuestoSelectivoConsumoAdvalorem", imp["MontoImpuestoSelectivoConsumoAdvalorem"]?.InnerText);
            Add(impNode, "OtrosImpuestosAdicionales", imp["OtrosImpuestosAdicionales"]?.InnerText);
            impAdsNode.AppendChild(impNode);
        }

        totalesNode.AppendChild(impAdsNode);
    }

    Add(totalesNode, "MontoTotal", t["MontoTotal"]?.InnerText);
    Add(totalesNode, "MontoNoFacturable", t["MontoNoFacturable"]?.InnerText);
    Add(totalesNode, "MontoPeriodo", t["MontoPeriodo"]?.InnerText);

    encNode.AppendChild(totalesNode);

    // ======================
    // Código Seguridad
    // ======================
    Add(encNode, "CodigoSeguridadeCF", codigoSeguridad);

    using (var sw = new StringWriter())
    using (var xw = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true }))
    {
        res.WriteTo(xw);
        xw.Flush();
        return ToAnsi(sw.ToString());
    }
}


        

        // ======================
        // ENVÍO DGII
        // ======================
        public string EnviarXml(string xmlFile, string urlEnvio, string token)
        {
            return EnviarXmlDesdeArchivo(xmlFile, urlEnvio, token);
        }

        private string EnviarXmlDesdeArchivo(string xmlFile, string urlEnvio, string token)
        {
            try
            {
                if (!File.Exists(xmlFile))
                    return "ERROR: El archivo XML no existe";

                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

                string boundary = "----VB6Boundary" + DateTime.Now.Ticks;
                byte[] xmlBytes = File.ReadAllBytes(xmlFile);
                string fileName = Path.GetFileName(xmlFile);

                string pre =
                    "--" + boundary + "\r\n" +
                    "Content-Disposition: form-data; name=\"xml\"; filename=\"" + fileName + "\"\r\n" +
                    "Content-Type: application/xml\r\n\r\n";

                string post = "\r\n--" + boundary + "--\r\n";

                byte[] preBytes = Encoding.UTF8.GetBytes(pre);
                byte[] postBytes = Encoding.UTF8.GetBytes(post);

                byte[] body = new byte[preBytes.Length + xmlBytes.Length + postBytes.Length];

                Buffer.BlockCopy(preBytes, 0, body, 0, preBytes.Length);
                Buffer.BlockCopy(xmlBytes, 0, body, preBytes.Length, xmlBytes.Length);
                Buffer.BlockCopy(postBytes, 0, body, preBytes.Length + xmlBytes.Length, postBytes.Length);

                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(urlEnvio);
                req.Method = "POST";
                req.Accept = "application/json";
                req.ContentType = "multipart/form-data; boundary=" + boundary;
                req.Headers.Add("Authorization", "Bearer " + token);
                req.ContentLength = body.Length;

                using (Stream rs = req.GetRequestStream())
                    rs.Write(body, 0, body.Length);

                using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
                using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
                    return sr.ReadToEnd();
            }
            catch (WebException ex)
            {
                if (ex.Response == null)
                    return "ERROR: DGII cerró la conexión";

                using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream()))
                    return sr.ReadToEnd();
            }
        }

         // ======================
        // GENERAR QR DGII
        // ======================
        public string GenerarQr(string xmlInput)
        {
            try
            {
                string xmlString = xmlInput.Contains("= 3 ? encf.Substring(0, 3) : "";
                decimal montoTotal = decimal.Parse(
                    totales["MontoTotal"]?.InnerText ?? "0",
                    CultureInfo.InvariantCulture
                );

                string fechaFirma = dom.SelectSingleNode("//*[local-name()='FechaHoraFirma']")?.InnerText ?? "";
                string signature = dom.SelectSingleNode("//*[local-name()='SignatureValue']")?.InnerText ?? "";
                string codigoSeguridad = signature.Length >= 6 ? signature.Substring(0, 6) : "";

                // ===== Catálogo =====
                var catalogo = new Dictionary
                {
                    {"E31","Factura de Crédito Fiscal Electrónica"},
                    {"E32","Factura de Consumo Electrónica"},
                    {"E33","Nota de Débito Electrónica"},
                    {"E34","Nota de Crédito Electrónica"},
                    {"E41","Compras Electrónico"},
                    {"E43","Gastos Menores Electrónico"},
                    {"E44","Regímenes Especiales Electrónico"},
                    {"E45","Gubernamental Electrónico"},
                    {"E46","Exportaciones Electrónico"},
                    {"E47","Pagos al Exterior Electrónico"},
                    {"B01","Factura de Crédito Fiscal"},
                    {"B15","Factura Gubernamental"}
                };

                // ===== NCF Tradicional =====
                if (etype == "B01" || etype == "B15")
                {
                    return "https://lipuve.com.do/consultaNCF.php?ncf=" + encf;
                }

                // ===== Endpoint DGII =====
                string timbreUrl;
                if (etype == "E32" && montoTotal < 250000)
                    timbreUrl = "https://fc.dgii.gov.do/certecf/consultatimbrefc?";
                else
                    timbreUrl = "https://ecf.dgii.gov.do/certecf/ConsultaTimbre?";

                var parametros = new Dictionary
                {
                    {"RncEmisor", emisor["RNCEmisor"]?.InnerText ?? ""},
                    {"RncComprador", comprador?["RNCComprador"]?.InnerText ?? ""},
                    {"ENCF", encf},
                    {"FechaEmision", emisor["FechaEmision"]?.InnerText ?? ""},
                    {"MontoTotal", montoTotal.ToString("0.00", CultureInfo.InvariantCulture)},
                    {"FechaFirma", fechaFirma},
                    {"CodigoSeguridad", codigoSeguridad}
                };

                if (etype == "E43")
                    parametros.Remove("RncComprador");

                // ===== Query =====
                var sb = new StringBuilder();
                foreach (var p in parametros)
                {
                    if (sb.Length > 0) sb.Append("&");
                    sb.Append(p.Key).Append("=")
                      .Append(Uri.EscapeDataString(p.Value));
                }

                return timbreUrl + sb.ToString();
            }
            catch
            {
                return "https://fc.dgii.gov.do/certecf/consultatimbrefc?";
            }
        }

        public void GenerarQRImagen(string texto, string rutaImagenPng)
        {
            if (string.IsNullOrWhiteSpace(texto))
                throw new Exception(ToAnsi("Texto QR vacío"));

            // 🔥 FORZAR TLS 1.2
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            string url =
                "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=" +
                Uri.EscapeDataString(texto);

            using (WebClient wc = new WebClient())
            {
                wc.DownloadFile(url, rutaImagenPng);
            }
        }

        // ======================
        // FIRMA XML
        // ======================
        public string FirmarXml(string rutaXml, string rutaP12, string clave)
        {
            XmlDocument xml = new XmlDocument();
            xml.PreserveWhitespace = false;
            xml.Load(rutaXml);

            X509Certificate2 cert = new X509Certificate2(
                rutaP12,
                clave,
                X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet
            );

            RSA rsa = cert.GetRSAPrivateKey();
            if (rsa == null)
                throw new Exception("Certificado sin clave privada");

            SignedXml signedXml = new SignedXml(xml);
            signedXml.SigningKey = rsa;

            signedXml.SignedInfo.CanonicalizationMethod =
                "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";

            signedXml.SignedInfo.SignatureMethod =
                SignedXml.XmlDsigRSASHA256Url;

            Reference reference = new Reference("");
            reference.DigestMethod = SignedXml.XmlDsigSHA256Url;
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());

            signedXml.AddReference(reference);

            KeyInfo ki = new KeyInfo();
            ki.AddClause(new KeyInfoX509Data(cert));
            signedXml.KeyInfo = ki;

            signedXml.ComputeSignature();

            XmlElement signature = signedXml.GetXml();
            signature.RemoveAttribute("xmlns:ds");

            xml.DocumentElement.AppendChild(
                xml.ImportNode(signature, true)
            );

            return xml.OuterXml;
        }

        // Elimina ds:, ns0, etc.
        static void LimpiarNamespaces(XmlElement element)
        {
            element.Prefix = "";
            element.RemoveAttribute("xmlns:ds");

            foreach (XmlNode n in element.ChildNodes)
            {
                if (n is XmlElement e)
                    LimpiarNamespaces(e);
            }
        }

        // ======================
        // PARSEAR RESPUESTA DGII (OBJETO)
        // ======================
       public DgiiRespuesta RespuestaDGII(string json)
        {
            if (string.IsNullOrWhiteSpace(json))
                throw new Exception("JSON vacío");

            JavaScriptSerializer serializer = new JavaScriptSerializer();

            DgiiJsonRaiz raiz;
            try
            {
                raiz = serializer.Deserialize(json);
            }
            catch
            {
                throw new Exception("JSON inválido o mal formado");
            }

            var r = new DgiiRespuesta
            {
                Success = raiz != null && raiz.result != null ? raiz.result.success : false,

                TrackId = raiz?.result?.response?.trackId ?? "",
                Codigo = raiz?.result?.response?.codigo ?? "",
                Estado = raiz?.result?.response?.estado ?? "",
                Rnc = raiz?.result?.response?.rnc ?? "",
                Encf = raiz?.result?.response?.encf ?? "",
                Qr = raiz?.result?.qr ?? "",  

                SecuenciaUtilizada =
                    raiz?.result?.response?.secuenciaUtilizada ?? false,

                FechaRecepcion =
                    raiz?.result?.response?.fechaRecepcion ?? "",

               

                CodigoMensaje = 0,
                Mensaje = ""
            };

            if (raiz?.result?.response?.mensajes != null &&
                raiz.result.response.mensajes.Count > 0)
            {
                r.CodigoMensaje =
                    raiz.result.response.mensajes[0]?.codigo ?? 0;

                r.Mensaje =
                    raiz.result.response.mensajes[0]?.valor ?? "";
            }

            return r;
        }


    }

    internal class DgiiJsonRaiz
    {
        public DgiiJsonResult result { get; set; }
       
    }

    internal class DgiiJsonResult
    {
        public bool success { get; set; }
        public DgiiJsonResponse response { get; set; }
        public string qr { get; set; }  
    }

    internal class DgiiJsonResponse
    {
        public string trackId { get; set; }
        public string codigo { get; set; }
        public string estado { get; set; }
        public string rnc { get; set; }
        public string encf { get; set; }
        public bool secuenciaUtilizada { get; set; }
        public string fechaRecepcion { get; set; }
        public List mensajes { get; set; }
        
    }

    internal class DgiiJsonMensaje
    {
        public string valor { get; set; }
        public int codigo { get; set; }
    }

    public class DgiiRespuesta
    {
        public bool Success { get; set; }
        public string TrackId { get; set; }
        public string Codigo { get; set; }
        public string Estado { get; set; }
        public string Rnc { get; set; }
        public string Encf { get; set; }
        public bool SecuenciaUtilizada { get; set; }
        public string FechaRecepcion { get; set; }

        public int CodigoMensaje { get; set; }
        public string Mensaje { get; set; }

        public string Qr { get; set; }
    }

       
}


🧪 CODIGO FUENTE DLL (C#) CONFIGURACION


<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
  <TargetFramework>net46</TargetFramework>
  <OutputType>Library</OutputType>
  <PlatformTarget>x86</PlatformTarget>
  <ComVisible>true</ComVisible>
  <RegisterForComInterop>false</RegisterForComInterop>
  </PropertyGroup>
</Project>