Saltar al pie de página
VIDEOS

Cómo utilizar Async y Multithreading en C#

La migración de Mindeea IronOCR traslada el procesamiento de documentos de los servidores externos en la nube a su propia infraestructura, eliminando la facturación por página, la transmisión de datos de terceros y la disponibilidad de la red como dependencia en tiempo de ejecución. Las secciones siguientes abarcan la sustitución de paquetes, las actualizaciones de espacios de nombres y cuatro escenarios prácticos de código antes y después: extracción de zonas de facturas, procesamiento de recibos en formato PDF de varias páginas, preprocesamiento de documentos financieros y archivado de PDF con capacidad de búsqueda.

¿Por qué migrar desde Mindee?

Mindee resuelve un problema real de forma elegante: enviar un documento y recibir campos JSON estructurados. Para los equipos en los que la transmisión de documentos financieros a una API externa es aceptable y el volumen se mantiene moderado, ofrece resultados rápidos. El desencadenante de la migración suele producirse cuando cambia alguna de varias condiciones.

Los datos financieros cruzan un límite de cumplimiento. ParseAsync<InvoiceV4> de Mindeesube el documento completo a servidores externos. Los códigos IBAN, los números de ruta, los números EIN de los proveedores y los detalles de las partidas se transmiten a través de Internet y se procesan en hardware que está fuera de su control. La certificación SOC 2 Tipo II confirma que Mindeecumple con las prácticas de seguridad. No mantiene sus documentos dentro de su perímetro de seguridad. Cuando una revisión de cumplimiento, un nuevo contrato con un cliente o una normativa sobre la residencia de datos establecen límites al procesamiento en la nube por parte de terceros, la arquitectura de Mindeedeja de ser válida, independientemente de la precisión de su extracción.

El costo por página aumenta según tus necesidades. El plan Básico ofrece 1000 páginas al mes por $49/mes. El plan Pro ofrece 5.000 páginas al mes por 499 dólares al mes. Los archivos PDF de varias páginas cuentan todas las páginas. Las pruebas de desarrollo se contabilizan dentro de la cuota. Un equipo que procesa 4.000 facturas al mes paga 499 dólares al mes (5.988 dólares al año), cifra que se reinicia cada año y aumenta en función del volumen de transacciones. Con 24.000 páginas al mes, estás negociando precios Enterprise . La licencia perpetua de IronOCR, con un precio de 1499 dólares (nivel Professional ), cubre un volumen ilimitado de documentos y recupera su coste frente a MindeePro en menos de cuatro meses.

El sondeo asíncrono se propaga a través de la pila. Cada operación de Mindeees asíncrona porque cada operación es una solicitud de red. Esta cadena asíncrona obligatoria se propaga en cascada a través de los controladores, las capas de servicio y las clases de trabajadores. Cuando la red no está disponible o el servicio de Mindeesufre una interrupción, la cascada de fallos produce una avería sin posibilidad de recuperación local.IronOCR procesa sincrónicamente — trabajo de CPU local — y envuelve en Task.Run donde se requieren interfaces asíncronas para mantener la consistencia arquitectónica.

El catálogo de API pre-construido tiene un límite duro. Mindeecubre InvoiceV4, ReceiptV5, PassportV1, y un pequeño catálogo de otros tipos de documentos. Cualquier tipo de documento fuera de esta lista requiere entrenamiento de modelo personalizado del plan Enterprise: recolección de muestras, etiquetado, tiempo de entrenamiento y tiempo de entrega antes de que la API esté disponible. Cada nuevo tipo de documento que su empresa necesite implica la contratación de un proveedor independiente.IronOCR procesa cualquier tipo de documento usando el mismo llamado IronTesseract().Read(); Agregar un nuevo tipo de documento implica escribir patrones de extracción, no negociar un contrato de capacitación.

La gestión de claves API amplía la superficie operativa. Las credenciales de Mindeedeben almacenarse de forma segura en el servidor, rotarse periódicamente y protegerse de cualquier exposición. Las reglas de salida de red deben permitir el tráfico HTTPS saliente hacia los puntos finales de Mindee. La lógica de reintento debe manejar HttpRequestException y MindeeException de las inevitables fallas de red. Eliminar Mindeeelimina toda esa superficie operativa: sin credenciales, sin reglas de salida, sin mecanismos de reintento para las operaciones de entrada/salida de red.

Se excluyen los entornos aislados y con acceso restringido. Los contratistas gubernamentales, los subcontratistas de defensa, las redes sanitarias con estrictos controles de internet saliente y los sistemas industriales implementados en instalaciones sin conectividad a internet fiable no pueden utilizar Mindeesegún lo previsto.IronOCR funciona de forma idéntica en contenedores Docker sin acceso saliente, en Azure Functions con restricciones de salida y en entornos totalmente aislados de la red. No existe dependencia de la nube en tiempo de ejecución. Consulte la página de licencias de IronOCR para obtener detalles sobre los niveles de implementación.

El problema fundamental

Cada documento financiero que procesa Mindeecruza un límite de red que usted no controla:

// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
// Mindee: invoice with bank details leaves your infrastructure on this line
var response = await _client.ParseAsync<InvoiceV4>(new LocalInputSource("invoice.pdf"));
// IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
Imports System.Threading.Tasks

' Mindee: invoice with bank details leaves your infrastructure on this line
Dim response = Await _client.ParseAsync(Of InvoiceV4)(New LocalInputSource("invoice.pdf"))
' IBAN, routing number, EIN, line items — all transmitted to Mindeecloud
$vbLabelText   $csharpLabel
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
// IronOCR: same invoice, same result, zero data transmission
var result = new IronTesseract().Read("invoice.pdf");
// All processing local — bank details never leave your machine
' IronOCR: same invoice, same result, zero data transmission
Dim result = New IronTesseract().Read("invoice.pdf")
' All processing local — bank details never leave your machine
$vbLabelText   $csharpLabel

IronOCR vs Mindee: Comparación de características

La siguiente tabla resume las diferencias arquitectónicas y de capacidades que influyen en las decisiones de migración.

Característica Mindee IronOCR
Lugar de procesamiento Servidores en la nube de Mindee Máquina local / su infraestructura
Se requiere conexión a Internet en tiempo de ejecución. Siempre Nunca
Transmisión de datos Documento completo subido por llamada None
Coste por documento Sí (por página, según el plan) No (licencia perpetua)
Precio inicial $49/mes (1.000 páginas) $999 único (Lite)
Soporte sin conexión / aislado de la red No soportado Soporte completo
Implementación en las instalaciones No disponible Única opción
Análisis de facturas API estructurada pre-construida (InvoiceV4) OCR + extracción basada en regiones o expresiones regulares
Análisis de recibos API estructurada pre-construida (ReceiptV5) Patrones de extracción y OCR
Tipos de documentos arbitrarios No se admite sin formación personalizada. Soporte completo, cualquier documento
Formación en modelos personalizados Plan Enterprise + plazo de entrega No es necesario
Patrón de llamada a la API Asíncrono obligatorio (E/S de red) Síncrono (asíncrono opcional)
limitación de velocidad Plan-dependent None
Entrada de PDF Sí (nativo, sin conversión)
Procesamiento de archivos PDF de varias páginas
Salida en PDF con capacidad de búsqueda No Sí (result.SaveAsSearchablePdf())
Preprocesamiento de imágenes Ninguno expuesto Corregir inclinación, Reducir ruido, Contraste, Binarizar, Enfocar, Escalar
Extracción basada en regiones Coordenadas de campo en respuesta CropRectangle en la entrada
Soporte para más de 125 idiomas Limitado Sí (incluido, sin gestión de datos de prueba)
Lectura de BarCodes No Sí (simultáneamente con el OCR)
Coordenadas de palabras estructuradas No Sí (Páginas, Párrafos, Líneas, Palabras)
Seguridad de los hilos N/D (E/S de red por llamada) Incorporado en
Multiplataforma Nube (independiente de la plataforma) Windows, Linux, macOS, Docker, Azure, AWS
Compatibilidad con .NET .NET Standard 2.0 o superior .NET Framework 4.6.2+, .NET 5/6/7/8/9
Soporte comercial

Inicio rápido: Migración de Mindeea IronOCR

Paso 1: Sustituir el paquete NuGet

Elimine el paquete Mindee:

dotnet remove package Mindee
dotnet remove package Mindee
SHELL

Instala IronOCR desde NuGet :

dotnet add package IronOcr

Paso 2: Actualizar los espacios de nombres

Reemplace el bloque de espacio de nombres Mindeecon el espacio de nombres IronOCR:

// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;

// After (IronOCR)
using IronOcr;
// Before (Mindee)
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;
using Mindee.Product.Receipt;
using Mindee.Product.FinancialDocument;

// After (IronOCR)
using IronOcr;
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports Mindee.Product.Receipt
Imports Mindee.Product.FinancialDocument

Imports IronOcr
$vbLabelText   $csharpLabel

Paso 3: Inicializar licencia

Agregue la clave de licencia al iniciar la aplicación (antes de la primera llamada de OCR):

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
$vbLabelText   $csharpLabel

Ejemplos de migración de código

Extracción de campos de factura mediante zonas de documento

Mindee devuelve los campos preprocesados ​​en ubicaciones conocidas porque su modelo del lado del servidor sabe dónde suelen aparecer los números de factura, las fechas y los totales. El equivalente de IronOCR usa CropRectangle para leer zonas específicas del documento, igualando la conciencia espacial de la extracción de Mindeesin transmitir el documento.

Enfoque de Mindee:

using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeZoneExtractor
{
    private readonly MindeeClient _client;

    public MindeeZoneExtractor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<(string invoiceNumber, string supplierName, decimal? total)>
        ExtractKeyFieldsAsync(string filePath)
    {
        // Document transmitted to Mindee— spatial field detection happens server-side
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // Fields arrive pre-parsed; no zone configuration required on your end
        return (
            prediction.InvoiceNumber?.Value,
            prediction.SupplierName?.Value,
            prediction.TotalAmount?.Value
        );
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeZoneExtractor
{
    private readonly MindeeClient _client;

    public MindeeZoneExtractor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<(string invoiceNumber, string supplierName, decimal? total)>
        ExtractKeyFieldsAsync(string filePath)
    {
        // Document transmitted to Mindee— spatial field detection happens server-side
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // Fields arrive pre-parsed; no zone configuration required on your end
        return (
            prediction.InvoiceNumber?.Value,
            prediction.SupplierName?.Value,
            prediction.TotalAmount?.Value
        );
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks

Public Class MindeeZoneExtractor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ExtractKeyFieldsAsync(filePath As String) As Task(Of (invoiceNumber As String, supplierName As String, total As Decimal?))
        ' Document transmitted to Mindee— spatial field detection happens server-side
        Dim inputSource = New LocalInputSource(filePath)
        Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        ' Fields arrive pre-parsed; no zone configuration required on your end
        Return (
            prediction.InvoiceNumber?.Value,
            prediction.SupplierName?.Value,
            prediction.TotalAmount?.Value
        )
    End Function
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrZoneExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public (string invoiceNumber, string supplierName, string total)
        ExtractKeyFields(string filePath)
    {
        // Zone 1: supplier name — top 12% of document, full width
        var headerRegion = new CropRectangle(0, 0, 850, 120);
        using var headerInput = new OcrInput();
        headerInput.LoadImage(filePath, headerRegion);
        var supplierResult = _ocr.Read(headerInput);

        // Zone 2: invoice number and date — upper-right quadrant
        var metaRegion = new CropRectangle(450, 80, 400, 100);
        using var metaInput = new OcrInput();
        metaInput.LoadImage(filePath, metaRegion);
        var metaResult = _ocr.Read(metaInput);

        // Zone 3: totals block — bottom-right 20%
        var totalsRegion = new CropRectangle(500, 800, 350, 200);
        using var totalsInput = new OcrInput();
        totalsInput.LoadImage(filePath, totalsRegion);
        var totalsResult = _ocr.Read(totalsInput);

        var invoiceNumber = Regex.Match(
            metaResult.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            totalsResult.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        // Supplier name is the first substantial text line in the header zone
        var supplierName = supplierResult.Text
            .Split('\n')
            .FirstOrDefault(l => l.Trim().Length > 4)
            ?.Trim();

        return (invoiceNumber, supplierName, total);
    }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrZoneExtractor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public (string invoiceNumber, string supplierName, string total)
        ExtractKeyFields(string filePath)
    {
        // Zone 1: supplier name — top 12% of document, full width
        var headerRegion = new CropRectangle(0, 0, 850, 120);
        using var headerInput = new OcrInput();
        headerInput.LoadImage(filePath, headerRegion);
        var supplierResult = _ocr.Read(headerInput);

        // Zone 2: invoice number and date — upper-right quadrant
        var metaRegion = new CropRectangle(450, 80, 400, 100);
        using var metaInput = new OcrInput();
        metaInput.LoadImage(filePath, metaRegion);
        var metaResult = _ocr.Read(metaInput);

        // Zone 3: totals block — bottom-right 20%
        var totalsRegion = new CropRectangle(500, 800, 350, 200);
        using var totalsInput = new OcrInput();
        totalsInput.LoadImage(filePath, totalsRegion);
        var totalsResult = _ocr.Read(totalsInput);

        var invoiceNumber = Regex.Match(
            metaResult.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            totalsResult.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        // Supplier name is the first substantial text line in the header zone
        var supplierName = supplierResult.Text
            .Split('\n')
            .FirstOrDefault(l => l.Trim().Length > 4)
            ?.Trim();

        return (invoiceNumber, supplierName, total);
    }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrZoneExtractor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ExtractKeyFields(filePath As String) As (invoiceNumber As String, supplierName As String, total As String)
        ' Zone 1: supplier name — top 12% of document, full width
        Dim headerRegion As New CropRectangle(0, 0, 850, 120)
        Using headerInput As New OcrInput()
            headerInput.LoadImage(filePath, headerRegion)
            Dim supplierResult = _ocr.Read(headerInput)

            ' Zone 2: invoice number and date — upper-right quadrant
            Dim metaRegion As New CropRectangle(450, 80, 400, 100)
            Using metaInput As New OcrInput()
                metaInput.LoadImage(filePath, metaRegion)
                Dim metaResult = _ocr.Read(metaInput)

                ' Zone 3: totals block — bottom-right 20%
                Dim totalsRegion As New CropRectangle(500, 800, 350, 200)
                Using totalsInput As New OcrInput()
                    totalsInput.LoadImage(filePath, totalsRegion)
                    Dim totalsResult = _ocr.Read(totalsInput)

                    Dim invoiceNumber = Regex.Match(metaResult.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value
                    Dim total = Regex.Match(totalsResult.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.?\d*)", RegexOptions.IgnoreCase).Groups(1).Value

                    ' Supplier name is the first substantial text line in the header zone
                    Dim supplierName = supplierResult.Text.Split(vbLf).FirstOrDefault(Function(l) l.Trim().Length > 4)?.Trim()

                    Return (invoiceNumber, supplierName, total)
                End Using
            End Using
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

El OCR basado en zonas coincide con la intención de la detección de campos espaciales de Mindee: leer áreas específicas del documento en lugar de aplicar expresiones regulares a toda la página. CropRectangle(x, y, width, height) toma coordenadas de píxeles, por lo que la afinación requiere medir contra una factura de muestra representativa. La guía de OCR basada en región cubre la medición de coordenadas y las estrategias de superposición de zonas. Para facturas con diseños variables, combinar la extracción de zonas con regex de página completa en result.Text produce los resultados más confiables.

Procesamiento de informes de gastos de varias páginas en formato PDF

Mindee acepta un archivo PDF y procesa cada página como una unidad de documento independiente, devolviendo un resultado estructurado por página.IronOCR lee PDFs de múltiples páginas de manera nativa a través de OcrInput.LoadPdf(), procesando todas las páginas en una sola llamada y devolviendo result.Pages con contenido por página y coordenadas de palabras.

Enfoque de Mindee:

using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;

public class MindeeExpenseReportProcessor
{
    private readonly MindeeClient _client;

    public MindeeExpenseReportProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
    {
        var summaries = new List<ExpenseSummary>();

        // Mindeeprocesses per-document; each page of a PDF is a separate upload
        // For a 10-page expense report: 10 API calls, 10 pages billed
        var inputSource = new LocalInputSource(pdfPath);
        var response    = await _client.ParseAsync<ReceiptV5>(inputSource);

        var prediction = response.Document.Inference.Prediction;

        summaries.Add(new ExpenseSummary
        {
            MerchantName  = prediction.SupplierName?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value ?? 0m,
            Category      = prediction.Category?.Value
        });

        return summaries;
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Receipt;

public class MindeeExpenseReportProcessor
{
    private readonly MindeeClient _client;

    public MindeeExpenseReportProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<List<ExpenseSummary>> ProcessExpenseReportAsync(string pdfPath)
    {
        var summaries = new List<ExpenseSummary>();

        // Mindeeprocesses per-document; each page of a PDF is a separate upload
        // For a 10-page expense report: 10 API calls, 10 pages billed
        var inputSource = new LocalInputSource(pdfPath);
        var response    = await _client.ParseAsync<ReceiptV5>(inputSource);

        var prediction = response.Document.Inference.Prediction;

        summaries.Add(new ExpenseSummary
        {
            MerchantName  = prediction.SupplierName?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value ?? 0m,
            Category      = prediction.Category?.Value
        });

        return summaries;
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Receipt
Imports System.Threading.Tasks

Public Class MindeeExpenseReportProcessor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ProcessExpenseReportAsync(pdfPath As String) As Task(Of List(Of ExpenseSummary))
        Dim summaries As New List(Of ExpenseSummary)()

        ' Mindee processes per-document; each page of a PDF is a separate upload
        ' For a 10-page expense report: 10 API calls, 10 pages billed
        Dim inputSource As New LocalInputSource(pdfPath)
        Dim response = Await _client.ParseAsync(Of ReceiptV5)(inputSource)

        Dim prediction = response.Document.Inference.Prediction

        summaries.Add(New ExpenseSummary With {
            .MerchantName = prediction.SupplierName?.Value,
            .Date = prediction.Date?.Value?.ToString(),
            .TotalAmount = If(prediction.TotalAmount?.Value, 0D),
            .Category = prediction.Category?.Value
        })

        Return summaries
    End Function
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrExpenseReportProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
    {
        // Load entire multi-page PDF in one call — no per-page upload
        using var input = new OcrInput();
        input.LoadPdf(pdfPath);    // all pages loaded locally

        var result    = _ocr.Read(input);
        var summaries = new List<ExpenseSummary>();

        // Each page maps to one receipt in the expense report
        foreach (var page in result.Pages)
        {
            var pageText = page.Text;

            // Skip pages without receipt content
            if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
                continue;

            var merchantName = page.Lines
                .FirstOrDefault(l => l.Text.Trim().Length > 4)
                ?.Text.Trim();

            var totalMatch = Regex.Match(
                pageText,
                @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
                RegexOptions.IgnoreCase);

            var dateMatch = Regex.Match(
                pageText,
                @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");

            var categoryMatch = Regex.Match(
                pageText,
                @"(?:Category|Dept|Department)\s*:?\s*(.+)",
                RegexOptions.IgnoreCase);

            summaries.Add(new ExpenseSummary
            {
                PageNumber   = page.PageNumber,
                MerchantName = merchantName,
                Date         = dateMatch.Success ? dateMatch.Value : null,
                TotalAmount  = totalMatch.Success
                    ? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
                    : 0m,
                Category     = categoryMatch.Success
                    ? categoryMatch.Groups[1].Value.Trim()
                    : null,
                Confidence   = page.Words.Any()
                    ? page.Words.Average(w => (double)w.Confidence)
                    : 0
            });
        }

        return summaries;
    }
}

public class ExpenseSummary
{
    public int    PageNumber   { get; set; }
    public string MerchantName { get; set; }
    public string Date         { get; set; }
    public decimal TotalAmount { get; set; }
    public string Category     { get; set; }
    public double Confidence   { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrExpenseReportProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public List<ExpenseSummary> ProcessExpenseReport(string pdfPath)
    {
        // Load entire multi-page PDF in one call — no per-page upload
        using var input = new OcrInput();
        input.LoadPdf(pdfPath);    // all pages loaded locally

        var result    = _ocr.Read(input);
        var summaries = new List<ExpenseSummary>();

        // Each page maps to one receipt in the expense report
        foreach (var page in result.Pages)
        {
            var pageText = page.Text;

            // Skip pages without receipt content
            if (!pageText.Contains("Total", StringComparison.OrdinalIgnoreCase))
                continue;

            var merchantName = page.Lines
                .FirstOrDefault(l => l.Text.Trim().Length > 4)
                ?.Text.Trim();

            var totalMatch = Regex.Match(
                pageText,
                @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
                RegexOptions.IgnoreCase);

            var dateMatch = Regex.Match(
                pageText,
                @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b");

            var categoryMatch = Regex.Match(
                pageText,
                @"(?:Category|Dept|Department)\s*:?\s*(.+)",
                RegexOptions.IgnoreCase);

            summaries.Add(new ExpenseSummary
            {
                PageNumber   = page.PageNumber,
                MerchantName = merchantName,
                Date         = dateMatch.Success ? dateMatch.Value : null,
                TotalAmount  = totalMatch.Success
                    ? decimal.Parse(totalMatch.Groups[1].Value.Replace(",", ""))
                    : 0m,
                Category     = categoryMatch.Success
                    ? categoryMatch.Groups[1].Value.Trim()
                    : null,
                Confidence   = page.Words.Any()
                    ? page.Words.Average(w => (double)w.Confidence)
                    : 0
            });
        }

        return summaries;
    }
}

public class ExpenseSummary
{
    public int    PageNumber   { get; set; }
    public string MerchantName { get; set; }
    public string Date         { get; set; }
    public decimal TotalAmount { get; set; }
    public string Category     { get; set; }
    public double Confidence   { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrExpenseReportProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessExpenseReport(pdfPath As String) As List(Of ExpenseSummary)
        ' Load entire multi-page PDF in one call — no per-page upload
        Using input As New OcrInput()
            input.LoadPdf(pdfPath) ' all pages loaded locally

            Dim result = _ocr.Read(input)
            Dim summaries As New List(Of ExpenseSummary)()

            ' Each page maps to one receipt in the expense report
            For Each page In result.Pages
                Dim pageText = page.Text

                ' Skip pages without receipt content
                If Not pageText.Contains("Total", StringComparison.OrdinalIgnoreCase) Then
                    Continue For
                End If

                Dim merchantName = page.Lines _
                    .FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
                    ?.Text.Trim()

                Dim totalMatch = Regex.Match(
                    pageText,
                    "(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})",
                    RegexOptions.IgnoreCase)

                Dim dateMatch = Regex.Match(
                    pageText,
                    "\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b")

                Dim categoryMatch = Regex.Match(
                    pageText,
                    "(?:Category|Dept|Department)\s*:?\s*(.+)",
                    RegexOptions.IgnoreCase)

                summaries.Add(New ExpenseSummary With {
                    .PageNumber = page.PageNumber,
                    .MerchantName = merchantName,
                    .Date = If(dateMatch.Success, dateMatch.Value, Nothing),
                    .TotalAmount = If(totalMatch.Success, Decimal.Parse(totalMatch.Groups(1).Value.Replace(",", "")), 0D),
                    .Category = If(categoryMatch.Success, categoryMatch.Groups(1).Value.Trim(), Nothing),
                    .Confidence = If(page.Words.Any(), page.Words.Average(Function(w) CDbl(w.Confidence)), 0)
                })
            Next

            Return summaries
        End Using
    End Function
End Class

Public Class ExpenseSummary
    Public Property PageNumber As Integer
    Public Property MerchantName As String
    Public Property Date As String
    Public Property TotalAmount As Decimal
    Public Property Category As String
    Public Property Confidence As Double
End Class
$vbLabelText   $csharpLabel

El procesamiento de un informe de gastos en formato PDF de 10 páginas con Mindeegenera 10 llamadas a la API y 10 páginas facturadas.IronOCR procesa el mismo archivo en una sola llamada local, sin coste por página y sin viajes de ida y vuelta a la red por página. La colección result.Pages proporciona texto por página, cuadros delimitadores a nivel de palabra y posiciones de líneas para extracción consciente del diseño. Consulte la guía de entrada de PDF para la carga de PDF protegidos con contraseña y la selección de rangos de páginas, y la guía de resultados de lectura estructurados para trabajar con coordenadas de palabras.

Proceso de preprocesamiento de facturas escaneadas

El procesamiento en la nube de Mindeeaplica su propia normalización de imagen antes de realizar la extracción de campos. La calidad de esa normalización es opaca: no tienes control sobre ella ni visibilidad sobre el preprocesamiento que se ejecutó. Cuando una factura escaneada presenta distorsión, sombras o bajo contraste, Mindeedevuelve puntuaciones de confianza más bajas sin ofrecer ningún mecanismo para mejorar el escaneo antes de enviarlo.IronOCR expone explícitamente el proceso de preprocesamiento, lo que permite aplicar exactamente las correcciones que necesita un documento determinado antes de que se ejecute el reconocimiento.

Enfoque de Mindee:

using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeScanProcessor
{
    private readonly MindeeClient _client;

    public MindeeScanProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
    {
        // Mindeeapplies internal normalization — no developer control over preprocessing
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new ScanResult
        {
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Total         = prediction.TotalAmount?.Value,
            // Per-field confidence: only proxy for scan quality
            InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
            TotalConfidence         = prediction.TotalAmount?.Confidence
        };
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeScanProcessor
{
    private readonly MindeeClient _client;

    public MindeeScanProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<ScanResult> ProcessScannedInvoiceAsync(string scanPath)
    {
        // Mindeeapplies internal normalization — no developer control over preprocessing
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new ScanResult
        {
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Total         = prediction.TotalAmount?.Value,
            // Per-field confidence: only proxy for scan quality
            InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
            TotalConfidence         = prediction.TotalAmount?.Confidence
        };
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks

Public Class MindeeScanProcessor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ProcessScannedInvoiceAsync(scanPath As String) As Task(Of ScanResult)
        ' Mindee applies internal normalization — no developer control over preprocessing
        Dim inputSource = New LocalInputSource(scanPath)
        Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        Return New ScanResult With {
            .InvoiceNumber = prediction.InvoiceNumber?.Value,
            .Total = prediction.TotalAmount?.Value,
            ' Per-field confidence: only proxy for scan quality
            .InvoiceNumberConfidence = prediction.InvoiceNumber?.Confidence,
            .TotalConfidence = prediction.TotalAmount?.Confidence
        }
    End Function
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrScanProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ScanResult ProcessScannedInvoice(string scanPath)
    {
        // First pass: read without preprocessing
        var quickResult = _ocr.Read(scanPath);

        OcrResult result;

        if (quickResult.Confidence < 75)
        {
            // Low confidence — apply full preprocessing pipeline
            using var input = new OcrInput();
            input.LoadImage(scanPath);
            input.Deskew();       // correct page rotation up to ±45 degrees
            input.DeNoise();      // remove scanner artifacts and speckle
            input.Contrast();     // normalize contrast for faded or overexposed scans
            input.Sharpen();      // sharpen blurred text edges

            result = _ocr.Read(input);
        }
        else
        {
            result = quickResult;
        }

        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            result.Text,
            @"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase).Groups[1].Value;

        return new ScanResult
        {
            InvoiceNumber    = invoiceNumber,
            Total            = total,
            DocumentConfidence = result.Confidence,
            PreprocessingApplied = quickResult.Confidence < 75
        };
    }
}

public class ScanResult
{
    public string InvoiceNumber        { get; set; }
    public string Total                { get; set; }
    public double DocumentConfidence   { get; set; }
    public bool   PreprocessingApplied { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrScanProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ScanResult ProcessScannedInvoice(string scanPath)
    {
        // First pass: read without preprocessing
        var quickResult = _ocr.Read(scanPath);

        OcrResult result;

        if (quickResult.Confidence < 75)
        {
            // Low confidence — apply full preprocessing pipeline
            using var input = new OcrInput();
            input.LoadImage(scanPath);
            input.Deskew();       // correct page rotation up to ±45 degrees
            input.DeNoise();      // remove scanner artifacts and speckle
            input.Contrast();     // normalize contrast for faded or overexposed scans
            input.Sharpen();      // sharpen blurred text edges

            result = _ocr.Read(input);
        }
        else
        {
            result = quickResult;
        }

        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var total = Regex.Match(
            result.Text,
            @"(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase).Groups[1].Value;

        return new ScanResult
        {
            InvoiceNumber    = invoiceNumber,
            Total            = total,
            DocumentConfidence = result.Confidence,
            PreprocessingApplied = quickResult.Confidence < 75
        };
    }
}

public class ScanResult
{
    public string InvoiceNumber        { get; set; }
    public string Total                { get; set; }
    public double DocumentConfidence   { get; set; }
    public bool   PreprocessingApplied { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrScanProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessScannedInvoice(scanPath As String) As ScanResult
        ' First pass: read without preprocessing
        Dim quickResult = _ocr.Read(scanPath)

        Dim result As OcrResult

        If quickResult.Confidence < 75 Then
            ' Low confidence — apply full preprocessing pipeline
            Using input As New OcrInput()
                input.LoadImage(scanPath)
                input.Deskew()       ' correct page rotation up to ±45 degrees
                input.DeNoise()      ' remove scanner artifacts and speckle
                input.Contrast()     ' normalize contrast for faded or overexposed scans
                input.Sharpen()      ' sharpen blurred text edges

                result = _ocr.Read(input)
            End Using
        Else
            result = quickResult
        End If

        Dim invoiceNumber = Regex.Match(
            result.Text,
            "Invoice\s*(?:Number|#|No\.?)?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups(1).Value

        Dim total = Regex.Match(
            result.Text,
            "(?:Total|Amount\s+Due)\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase).Groups(1).Value

        Return New ScanResult With {
            .InvoiceNumber = invoiceNumber,
            .Total = total,
            .DocumentConfidence = result.Confidence,
            .PreprocessingApplied = quickResult.Confidence < 75
        }
    End Function
End Class

Public Class ScanResult
    Public Property InvoiceNumber As String
    Public Property Total As String
    Public Property DocumentConfidence As Double
    Public Property PreprocessingApplied As Boolean
End Class
$vbLabelText   $csharpLabel

El método de dos pasadas —primero una lectura rápida y luego el preprocesamiento en caso de baja confianza— evita la sobrecarga que supone el preprocesamiento completo en escaneos limpios. Para los escenarios de calidad de escaneo más comunes en cuentas por pagar (escaneos ligeramente inclinados en cama plana, artefactos de fax, facturas fotocopiadas), Deskew() y DeNoise() juntos recuperan la mayor parte de la precisión de reconocimiento. La guía de corrección de la calidad de imagen documenta todos los filtros disponibles e incluye indicaciones sobre qué combinaciones funcionan mejor para los diferentes defectos de escaneo. La guía de corrección de la orientación de la imagen abarca la rotación automática para documentos escaneados al revés.

Archivo PDF con capacidad de búsqueda para documentos financieros

Mindee no genera ningún archivo PDF. Su arquitectura es de solo entrada: se envía un documento y se reciben campos JSON. Los equipos que archivan facturas escaneadas en un formato que permita realizar búsquedas deben mantener ese proceso por separado de la extracción que realiza Mindee.IronOCR genera archivos PDF con capacidad de búsqueda directamente a partir del mismo proceso de reconocimiento utilizado para la extracción de campos, sin necesidad de bibliotecas adicionales ni de un segundo paso de procesamiento.

Enfoque de Mindee:

using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeArchiveProcessor
{
    private readonly MindeeClient _client;

    public MindeeArchiveProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
    {
        // Extract fields via Mindee(document transmitted to cloud)
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // Mindeereturns no PDF output — searchable PDF requires a separate library
        // (e.g., iTextSharp, PdfSharp, or a separate OCR library)
        // The scan at scanPath is the only PDF available for archiving;
        // it remains image-only with no embedded text layer
        Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
        Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.Invoice;

public class MindeeArchiveProcessor
{
    private readonly MindeeClient _client;

    public MindeeArchiveProcessor(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task ArchiveInvoiceAsync(string scanPath, string archivePath)
    {
        // Extract fields via Mindee(document transmitted to cloud)
        var inputSource = new LocalInputSource(scanPath);
        var response    = await _client.ParseAsync<InvoiceV4>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        // Mindeereturns no PDF output — searchable PDF requires a separate library
        // (e.g., iTextSharp, PdfSharp, or a separate OCR library)
        // The scan at scanPath is the only PDF available for archiving;
        // it remains image-only with no embedded text layer
        Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.");
        Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}");
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.Invoice
Imports System.Threading.Tasks

Public Class MindeeArchiveProcessor
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ArchiveInvoiceAsync(scanPath As String, archivePath As String) As Task
        ' Extract fields via Mindee(document transmitted to cloud)
        Dim inputSource = New LocalInputSource(scanPath)
        Dim response = Await _client.ParseAsync(Of InvoiceV4)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        ' Mindeereturns no PDF output — searchable PDF requires a separate library
        ' (e.g., iTextSharp, PdfSharp, or a separate OCR library)
        ' The scan at scanPath is the only PDF available for archiving;
        ' it remains image-only with no embedded text layer
        Console.WriteLine($"Fields extracted. Searchable PDF: not available from Mindee.")
        Console.WriteLine($"Invoice: {prediction.InvoiceNumber?.Value}")
    End Function
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrArchiveProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
    {
        // Apply preprocessing before recognition and archiving
        using var input = new OcrInput();
        input.LoadPdf(scanPath);   // works with both PDF scans and image files
        input.Deskew();
        input.DeNoise();

        var result = _ocr.Read(input);

        // Extract fields from the same recognition pass
        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var vendor = result.Pages.FirstOrDefault()?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();

        var totalMatch = Regex.Match(
            result.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase);

        // Write searchable PDF to archive in one call — no separate library needed
        result.SaveAsSearchablePdf(archiveOutputPath);

        return new ArchiveResult
        {
            InvoiceNumber    = invoiceNumber,
            VendorName       = vendor,
            Total            = totalMatch.Success ? totalMatch.Groups[1].Value : null,
            ArchivePath      = archiveOutputPath,
            DocumentConfidence = result.Confidence,
            PageCount        = result.Pages.Count()
        };
    }
}

public class ArchiveResult
{
    public string InvoiceNumber      { get; set; }
    public string VendorName         { get; set; }
    public string Total              { get; set; }
    public string ArchivePath        { get; set; }
    public double DocumentConfidence { get; set; }
    public int    PageCount          { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrArchiveProcessor
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public ArchiveResult ArchiveInvoice(string scanPath, string archiveOutputPath)
    {
        // Apply preprocessing before recognition and archiving
        using var input = new OcrInput();
        input.LoadPdf(scanPath);   // works with both PDF scans and image files
        input.Deskew();
        input.DeNoise();

        var result = _ocr.Read(input);

        // Extract fields from the same recognition pass
        var invoiceNumber = Regex.Match(
            result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;

        var vendor = result.Pages.FirstOrDefault()?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();

        var totalMatch = Regex.Match(
            result.Text,
            @"Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})",
            RegexOptions.IgnoreCase);

        // Write searchable PDF to archive in one call — no separate library needed
        result.SaveAsSearchablePdf(archiveOutputPath);

        return new ArchiveResult
        {
            InvoiceNumber    = invoiceNumber,
            VendorName       = vendor,
            Total            = totalMatch.Success ? totalMatch.Groups[1].Value : null,
            ArchivePath      = archiveOutputPath,
            DocumentConfidence = result.Confidence,
            PageCount        = result.Pages.Count()
        };
    }
}

public class ArchiveResult
{
    public string InvoiceNumber      { get; set; }
    public string VendorName         { get; set; }
    public string Total              { get; set; }
    public string ArchivePath        { get; set; }
    public double DocumentConfidence { get; set; }
    public int    PageCount          { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions
Imports System.Linq

Public Class IronOcrArchiveProcessor
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ArchiveInvoice(scanPath As String, archiveOutputPath As String) As ArchiveResult
        ' Apply preprocessing before recognition and archiving
        Using input As New OcrInput()
            input.LoadPdf(scanPath) ' works with both PDF scans and image files
            input.Deskew()
            input.DeNoise()

            Dim result = _ocr.Read(input)

            ' Extract fields from the same recognition pass
            Dim invoiceNumber = Regex.Match(result.Text, "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", RegexOptions.IgnoreCase).Groups(1).Value

            Dim vendor = result.Pages.FirstOrDefault()?.Lines.FirstOrDefault(Function(l) l.Text.Trim().Length > 4)?.Text.Trim()

            Dim totalMatch = Regex.Match(result.Text, "Total\s*(?:Due)?\s*:?\s*\$?([\d,]+\.\d{2})", RegexOptions.IgnoreCase)

            ' Write searchable PDF to archive in one call — no separate library needed
            result.SaveAsSearchablePdf(archiveOutputPath)

            Return New ArchiveResult With {
                .InvoiceNumber = invoiceNumber,
                .VendorName = vendor,
                .Total = If(totalMatch.Success, totalMatch.Groups(1).Value, Nothing),
                .ArchivePath = archiveOutputPath,
                .DocumentConfidence = result.Confidence,
                .PageCount = result.Pages.Count()
            }
        End Using
    End Function
End Class

Public Class ArchiveResult
    Public Property InvoiceNumber As String
    Public Property VendorName As String
    Public Property Total As String
    Public Property ArchivePath As String
    Public Property DocumentConfidence As Double
    Public Property PageCount As Integer
End Class
$vbLabelText   $csharpLabel

result.SaveAsSearchablePdf() incrusta directamente la capa de texto reconocido en el PDF de salida, produciendo un archivo donde cada palabra es seleccionable y buscable en cualquier visor de PDF o sistema de gestión de contenido empresarial. La extracción de campos y el archivo se ejecutan en la misma pasada — una llamada _ocr.Read(input) proporciona tanto result.Text para la coincidencia de patrones como result.SaveAsSearchablePdf() para el archivo. La guía de PDF buscable cubre las opciones de salida incluyendo la exportación de HOCR y la página de casos de uso de PDF OCR cubre patrones de despliegue de producción para tuberías de archivo de documentos.

Eliminación del punto final personalizado FinancialDocumentV1

La API FinancialDocumentV1 de Mindeemaneja tanto facturas como recibos al detectar automáticamente el tipo de documento. Los equipos que lo utilizan eliminan la lógica de ramificación a costa de una mayor dependencia de la nube: se cargan todos los documentos, independientemente de su tipo. Sustituirlo por IronOCR utiliza datos posicionales a nivel de palabra para detectar la estructura del documento y la lógica de extracción de rutas sin necesidad de realizar ninguna llamada a la nube.

Enfoque de Mindee:

using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;

public class MindeeFinancialRouter
{
    private readonly MindeeClient _client;

    public MindeeFinancialRouter(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
    {
        // All financial documents uploaded for auto-detection and parsing
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new FinancialSummary
        {
            DocumentType  = prediction.DocumentType?.Value,  // "INVOICE" or "RECEIPT"
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value,
            SupplierName  = prediction.SupplierName?.Value,
            CustomerName  = prediction.CustomerName?.Value
        };
    }
}
using Mindee;
using Mindee.Input;
using Mindee.Product.FinancialDocument;

public class MindeeFinancialRouter
{
    private readonly MindeeClient _client;

    public MindeeFinancialRouter(string apiKey)
    {
        _client = new MindeeClient(apiKey);
    }

    public async Task<FinancialSummary> ProcessFinancialDocumentAsync(string filePath)
    {
        // All financial documents uploaded for auto-detection and parsing
        var inputSource = new LocalInputSource(filePath);
        var response    = await _client.ParseAsync<FinancialDocumentV1>(inputSource);
        var prediction  = response.Document.Inference.Prediction;

        return new FinancialSummary
        {
            DocumentType  = prediction.DocumentType?.Value,  // "INVOICE" or "RECEIPT"
            InvoiceNumber = prediction.InvoiceNumber?.Value,
            Date          = prediction.Date?.Value?.ToString(),
            TotalAmount   = prediction.TotalAmount?.Value,
            SupplierName  = prediction.SupplierName?.Value,
            CustomerName  = prediction.CustomerName?.Value
        };
    }
}
Imports Mindee
Imports Mindee.Input
Imports Mindee.Product.FinancialDocument
Imports System.Threading.Tasks

Public Class MindeeFinancialRouter
    Private ReadOnly _client As MindeeClient

    Public Sub New(apiKey As String)
        _client = New MindeeClient(apiKey)
    End Sub

    Public Async Function ProcessFinancialDocumentAsync(filePath As String) As Task(Of FinancialSummary)
        ' All financial documents uploaded for auto-detection and parsing
        Dim inputSource = New LocalInputSource(filePath)
        Dim response = Await _client.ParseAsync(Of FinancialDocumentV1)(inputSource)
        Dim prediction = response.Document.Inference.Prediction

        Return New FinancialSummary With {
            .DocumentType = prediction.DocumentType?.Value,  ' "INVOICE" or "RECEIPT"
            .InvoiceNumber = prediction.InvoiceNumber?.Value,
            .Date = prediction.Date?.Value?.ToString(),
            .TotalAmount = prediction.TotalAmount?.Value,
            .SupplierName = prediction.SupplierName?.Value,
            .CustomerName = prediction.CustomerName?.Value
        }
    End Function
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrFinancialRouter
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public FinancialSummary ProcessFinancialDocument(string filePath)
    {
        var result = _ocr.Read(filePath);
        var text   = result.Text;

        // Detect document type using structural keywords from word data
        var isInvoice = DetectInvoice(result);

        if (isInvoice)
        {
            return new FinancialSummary
            {
                DocumentType  = "INVOICE",
                InvoiceNumber = Regex.Match(text,
                    @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                Date          = Regex.Match(text,
                    @"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                TotalAmount   = ParseAmount(text,
                    @"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName  = ExtractTopLine(result),
                CustomerName  = Regex.Match(text,
                    @"Bill\s+To\s*:?\s*\n?(.+)",
                    RegexOptions.IgnoreCase).Groups[1].Value.Trim()
            };
        }
        else
        {
            // Receipt structure: merchant at top, no "Bill To" section
            return new FinancialSummary
            {
                DocumentType = "RECEIPT",
                Date         = Regex.Match(text,
                    @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
                TotalAmount  = ParseAmount(text,
                    @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName = ExtractTopLine(result)
            };
        }
    }

    private bool DetectInvoice(OcrResult result)
    {
        // Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
        var text = result.Text;
        var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
        return invoiceSignals.Any(s =>
            text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
    }

    private string ExtractTopLine(OcrResult result)
    {
        return result.Pages
            .FirstOrDefault()
            ?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();
    }

    private decimal? ParseAmount(string text, string pattern)
    {
        var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
        if (match.Success &&
            decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
            return val;
        return null;
    }
}

public class FinancialSummary
{
    public string   DocumentType  { get; set; }
    public string   InvoiceNumber { get; set; }
    public string   Date          { get; set; }
    public decimal? TotalAmount   { get; set; }
    public string   SupplierName  { get; set; }
    public string   CustomerName  { get; set; }
}
using IronOcr;
using System.Text.RegularExpressions;

public class IronOcrFinancialRouter
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public FinancialSummary ProcessFinancialDocument(string filePath)
    {
        var result = _ocr.Read(filePath);
        var text   = result.Text;

        // Detect document type using structural keywords from word data
        var isInvoice = DetectInvoice(result);

        if (isInvoice)
        {
            return new FinancialSummary
            {
                DocumentType  = "INVOICE",
                InvoiceNumber = Regex.Match(text,
                    @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                Date          = Regex.Match(text,
                    @"(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
                    RegexOptions.IgnoreCase).Groups[1].Value,
                TotalAmount   = ParseAmount(text,
                    @"Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName  = ExtractTopLine(result),
                CustomerName  = Regex.Match(text,
                    @"Bill\s+To\s*:?\s*\n?(.+)",
                    RegexOptions.IgnoreCase).Groups[1].Value.Trim()
            };
        }
        else
        {
            // Receipt structure: merchant at top, no "Bill To" section
            return new FinancialSummary
            {
                DocumentType = "RECEIPT",
                Date         = Regex.Match(text,
                    @"\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
                TotalAmount  = ParseAmount(text,
                    @"(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
                SupplierName = ExtractTopLine(result)
            };
        }
    }

    private bool DetectInvoice(OcrResult result)
    {
        // Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
        var text = result.Text;
        var invoiceSignals = new[] { "Invoice", "Bill To", "Due Date", "Purchase Order" };
        return invoiceSignals.Any(s =>
            text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0);
    }

    private string ExtractTopLine(OcrResult result)
    {
        return result.Pages
            .FirstOrDefault()
            ?.Lines
            .FirstOrDefault(l => l.Text.Trim().Length > 4)
            ?.Text.Trim();
    }

    private decimal? ParseAmount(string text, string pattern)
    {
        var match = Regex.Match(text, pattern, RegexOptions.IgnoreCase);
        if (match.Success &&
            decimal.TryParse(match.Groups[1].Value.Replace(",", ""), out var val))
            return val;
        return null;
    }
}

public class FinancialSummary
{
    public string   DocumentType  { get; set; }
    public string   InvoiceNumber { get; set; }
    public string   Date          { get; set; }
    public decimal? TotalAmount   { get; set; }
    public string   SupplierName  { get; set; }
    public string   CustomerName  { get; set; }
}
Imports IronOcr
Imports System.Text.RegularExpressions

Public Class IronOcrFinancialRouter
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ProcessFinancialDocument(filePath As String) As FinancialSummary
        Dim result = _ocr.Read(filePath)
        Dim text = result.Text

        ' Detect document type using structural keywords from word data
        Dim isInvoice = DetectInvoice(result)

        If isInvoice Then
            Return New FinancialSummary With {
                .DocumentType = "INVOICE",
                .InvoiceNumber = Regex.Match(text,
                    "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
                    RegexOptions.IgnoreCase).Groups(1).Value,
                .Date = Regex.Match(text,
                    "(?:Invoice\s+)?Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
                    RegexOptions.IgnoreCase).Groups(1).Value,
                .TotalAmount = ParseAmount(text,
                    "Total\s*(?:Due|Amount)?\s*:?\s*\$?([\d,]+\.\d{2})"),
                .SupplierName = ExtractTopLine(result),
                .CustomerName = Regex.Match(text,
                    "Bill\s+To\s*:?\s*\n?(.+)",
                    RegexOptions.IgnoreCase).Groups(1).Value.Trim()
            }
        Else
            ' Receipt structure: merchant at top, no "Bill To" section
            Return New FinancialSummary With {
                .DocumentType = "RECEIPT",
                .Date = Regex.Match(text,
                    "\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})\b").Value,
                .TotalAmount = ParseAmount(text,
                    "(?:Total|Amount\s+Due|Grand\s+Total)\s*:?\s*\$?([\d,]+\.\d{2})"),
                .SupplierName = ExtractTopLine(result)
            }
        End If
    End Function

    Private Function DetectInvoice(result As OcrResult) As Boolean
        ' Invoice indicators: "Invoice", "Bill To", "Due Date", line items with quantities
        Dim text = result.Text
        Dim invoiceSignals = {"Invoice", "Bill To", "Due Date", "Purchase Order"}
        Return invoiceSignals.Any(Function(s) text.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0)
    End Function

    Private Function ExtractTopLine(result As OcrResult) As String
        Return result.Pages _
            .FirstOrDefault() _
            ?.Lines _
            .FirstOrDefault(Function(l) l.Text.Trim().Length > 4) _
            ?.Text.Trim()
    End Function

    Private Function ParseAmount(text As String, pattern As String) As Decimal?
        Dim match = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
        If match.Success AndAlso
            Decimal.TryParse(match.Groups(1).Value.Replace(",", ""), val) Then
            Return val
        End If
        Return Nothing
    End Function
End Class

Public Class FinancialSummary
    Public Property DocumentType As String
    Public Property InvoiceNumber As String
    Public Property Date As String
    Public Property TotalAmount As Decimal?
    Public Property SupplierName As String
    Public Property CustomerName As String
End Class
$vbLabelText   $csharpLabel

La lógica de detección de tipo de documento reemplaza el campo DocumentType del servidor de Mindeecon un escaneo de palabras clave simple contra result.Text. En la gran mayoría de los documentos financieros, la presencia de "Factura" o "Facturar a" identifica inequívocamente las facturas; la ausencia identifica recibos. Los datos de posición a nivel de palabra en result.Pages permiten una detección más sofisticada basada en el diseño si su conjunto de documentos incluye casos extremos. La guía de lectura de resultados explica el modelo completo de objetos incluyendo Words, Lines, y Paragraphs con sus coordenadas delimitantes.

Referencia de mapeo de la API de Mindeea IronOCR

Mindee Equivalente a IronOCR
new MindeeClient(apiKey) new IronTesseract() — no se requiere clave de API
new LocalInputSource(filePath) new OcrInput() + input.LoadImage(filePath) o ocr.Read(filePath)
_client.ParseAsync<InvoiceV4>(input) _ocr.Read(filePath) + extracción de regex en result.Text
_client.ParseAsync<ReceiptV5>(input) _ocr.Read(filePath) + patrones de extracción de recibos
_client.ParseAsync<PassportV1>(input) _ocr.Read(filePath) + extracción de zona MRZ vía CropRectangle
_client.ParseAsync<FinancialDocumentV1>(input) _ocr.Read(filePath) + detección de tipo de documento en result.Text
response.Document.Inference.Prediction result.Text, result.Pages, result.Lines, result.Words
prediction.InvoiceNumber?.Value Regex.Match(result.Text, @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)")
prediction.SupplierName?.Value Línea superior de la primera página vía result.Pages[0].Lines.First()
prediction.TotalAmount?.Value Regex.Match(result.Text, @"Total\s*:?\s*\$?([\d,]+\.\d{2})")
prediction.LineItems result.Lines filtrado por patrones de regex de línea de artículos
prediction.SupplierPaymentDetails[].Iban Regex.Match(result.Text, @"IBAN\s*:?\s*([\w\s]+)")
prediction.InvoiceDate?.Value Regex.Match(result.Text, @"Date\s*:?\s*(\d{1,2}[/-]\d{1,2}[/-]\d{4})")
field.Confidence result.Confidence (a nivel de documento) o word.Confidence (por palabra)
catch (MindeeException ex) Sin equivalente: no hay errores de red en el momento del reconocimiento.
await Task.Delay(100) (límite de tasa) No es obligatorio; no hay límites de tarifa.
input.LoadPdf(path) ocrInput.LoadPdf(path) — misma operación, completamente local
Clave API en la configuración Clave de licencia vía IronOcr.License.LicenseKey = "..." — no se necesita rotación

Problemas comunes de migración y soluciones

Problema 1: Las coordenadas del rectángulo de recorte no coinciden con el diseño del documento.

Mindee: Las coordenadas espaciales se gestionan en el servidor. El modelo de Mindeese adapta a diferentes diseños de facturas sin necesidad de que el desarrollador configure las coordenadas.

Solución: Mida las coordenadas de la zona comparándolas con una muestra representativa de documentos de su conjunto de proveedores. Abra una factura de muestra en cualquier editor de imágenes, lea las dimensiones en píxeles, y defina CropRectangle(x, y, width, height) en consecuencia. Para facturas con diseños variables, lea primero el documento completo y utilice los datos de posición de la palabra para ubicar las zonas dinámicamente:

var result  = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;

// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
    w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));

if (totalLabel != null)
{
    var valueRegion = new CropRectangle(
        totalLabel.X + totalLabel.Width + 5,
        totalLabel.Y - 5,
        200,
        totalLabel.Height + 10);

    using var valueInput = new OcrInput();
    valueInput.LoadImage("invoice.jpg", valueRegion);
    var valueResult = _ocr.Read(valueInput);
    Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
var result  = _ocr.Read("invoice.jpg");
var allWords = result.Pages.First().Words;

// Find the word "Total" and read the region 50px to its right
var totalLabel = allWords.FirstOrDefault(w =>
    w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase));

if (totalLabel != null)
{
    var valueRegion = new CropRectangle(
        totalLabel.X + totalLabel.Width + 5,
        totalLabel.Y - 5,
        200,
        totalLabel.Height + 10);

    using var valueInput = new OcrInput();
    valueInput.LoadImage("invoice.jpg", valueRegion);
    var valueResult = _ocr.Read(valueInput);
    Console.WriteLine($"Total: {valueResult.Text.Trim()}");
}
Imports System
Imports System.Linq

Dim result = _ocr.Read("invoice.jpg")
Dim allWords = result.Pages.First().Words

' Find the word "Total" and read the region 50px to its right
Dim totalLabel = allWords.FirstOrDefault(Function(w) w.Text.Equals("Total", StringComparison.OrdinalIgnoreCase))

If totalLabel IsNot Nothing Then
    Dim valueRegion = New CropRectangle(totalLabel.X + totalLabel.Width + 5, totalLabel.Y - 5, 200, totalLabel.Height + 10)

    Using valueInput As New OcrInput()
        valueInput.LoadImage("invoice.jpg", valueRegion)
        Dim valueResult = _ocr.Read(valueInput)
        Console.WriteLine($"Total: {valueResult.Text.Trim()}")
    End Using
End If
$vbLabelText   $csharpLabel

El ejemplo de OCR basado en regiones muestra este patrón de zona dinámico en un ejemplo completamente funcional.

Problema 2: Los sitios de llamadas asíncronas fallan después de eliminar Mindee.

Mindee: Toda la interfaz API de Mindeees asíncrona porque se basa en E/S de red. Los controladores y métodos de servicio construidos sobre await _client.ParseAsync<t>() son asíncronos a lo largo de la cadena de llamadas.

Solución: el método Read() de IronOCR es sincrónico. Las llamadas asíncronas existentes funcionan inmediatamente envolviendo la llamada sincrónica en Task.Run:

// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
    return await Task.Run(() =>
    {
        var result = new IronTesseract().Read(filePath);
        return Regex.Match(result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;
    });
}
// Existing async signature preserved for callers
public async Task<string> ExtractInvoiceNumberAsync(string filePath)
{
    return await Task.Run(() =>
    {
        var result = new IronTesseract().Read(filePath);
        return Regex.Match(result.Text,
            @"Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)",
            RegexOptions.IgnoreCase).Groups[1].Value;
    });
}
Imports System.Text.RegularExpressions
Imports System.Threading.Tasks

' Existing async signature preserved for callers
Public Async Function ExtractInvoiceNumberAsync(filePath As String) As Task(Of String)
    Return Await Task.Run(Function()
                              Dim result = New IronTesseract().Read(filePath)
                              Return Regex.Match(result.Text, 
                                                 "Invoice\s*#?\s*:?\s*([A-Z0-9\-]+)", 
                                                 RegexOptions.IgnoreCase).Groups(1).Value
                          End Function)
End Function
$vbLabelText   $csharpLabel

Para los escenarios de alto rendimiento de ASP.NET, revise la guía de OCR asíncrono antes de decidir entre envolturas Task.Run y el camino asíncrono nativo.

Problema 3: La precisión de la extracción disminuye en escaneos de baja calidad.

Mindee: El preprocesamiento de Mindeees interno y automático. Los problemas de calidad del escaneo reducen las puntuaciones de confianza del campo sin que el desarrollador pueda intervenir antes de volver a enviarlo.

Solución: Aplicar el proceso de preprocesamiento de IronOCR antes del reconocimiento. La combinación de Deskew() y DeNoise() recupera la mayor parte de la precisión en los defectos típicos de escaneo en cama plana vistos en los flujos de trabajo de cuentas por pagar:

using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();    // for faded or low-contrast thermal paper receipts
input.Binarize();    // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
using var input = new OcrInput();
input.LoadImage("faded-invoice.jpg");
input.Deskew();
input.DeNoise();
input.Contrast();    // for faded or low-contrast thermal paper receipts
input.Binarize();    // convert to clean black-and-white before recognition
var result = new IronTesseract().Read(input);
Imports IronOcr

Using input As New OcrInput()
    input.LoadImage("faded-invoice.jpg")
    input.Deskew()
    input.DeNoise()
    input.Contrast() ' for faded or low-contrast thermal paper receipts
    input.Binarize() ' convert to clean black-and-white before recognition
    Dim result = New IronTesseract().Read(input)
End Using
$vbLabelText   $csharpLabel

Para escaneos gravemente degradados, input.DeepCleanBackgroundNoise() aplica una pasada de eliminación de fondo más intensiva. La guía de corrección de la calidad de imagen documenta los umbrales y el asistente de filtros ayuda a identificar qué filtros mejoran la precisión en una muestra de documento determinada.

Problema 4: Se eliminó la clave API y la configuración de salida de red.

Mindee: La clave de API almacenada en la configuración de la aplicación, las reglas de salida de la red que permiten HTTPS hacia api.mindee.net, y la lógica de reintento envolviendo HttpRequestException son toda la infraestructura necesaria.

Solución: Se eliminan los tres elementos a la vez. La entrada de la clave API de Mindeese ha eliminado de la configuración. Se revoca la regla de salida de red. El bloque try/catch (HttpRequestException) se elimina. La única credencial restante es la clave de licencia de IronOCR, que se establece una sola vez al iniciar el sistema:

// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// No rotation schedule, no secure storage beyond standard config secret management
// appsettings.json: remove "Mindee:ApiKey"
// Firewall: revoke egress rule for api.mindee.net
// Startup.cs or Program.cs:
IronOcr.License.LicenseKey = Configuration["IronOcr:LicenseKey"];
// No rotation schedule, no secure storage beyond standard config secret management
$vbLabelText   $csharpLabel

Número 5: Facturación mediante recuento de páginas en PDF de varias páginas

Mindee: un extracto de proveedor de 12 páginas cargado en Mindeeconsume 12 páginas de la cuota mensual. Con las tarifas de exceso de consumo del nivel Pro, ese único documento genera un costo adicional de $1.20 más allá de la asignación mensual si está cerca del límite.

Solución:IronOCR procesa archivos PDF de varias páginas sin coste por página. Cargue el documento completo e itere las páginas:

using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf");   // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);

foreach (var page in result.Pages)
    Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
using var input = new OcrInput();
input.LoadPdf("vendor-statement.pdf");   // 12 pages — no billing unit increment
var result = new IronTesseract().Read(input);

foreach (var page in result.Pages)
    Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized");
Imports IronOcr

Using input As New OcrInput()
    input.LoadPdf("vendor-statement.pdf") ' 12 pages — no billing unit increment
    Dim result = New IronTesseract().Read(input)

    For Each page In result.Pages
        Console.WriteLine($"Page {page.PageNumber}: {page.Text.Length} characters recognized")
    Next
End Using
$vbLabelText   $csharpLabel

Problema 6: La dependencia FinancialDocumentV1 no se puede sustituir con un solo patrón.

Mindee: FinancialDocumentV1 detecta automáticamente el tipo de documento y extrae campos sin que el código llamado deba ramificarse por tipo de documento.

Solución: Crear un detector ligero que utilice la presencia de palabras clave. Los documentos financieros se dividen claramente en facturas (que contienen "Factura", "Facturar a" o "Fecha de vencimiento") y recibos (que contienen "Recibo", "Gracias" o carecen de los marcadores de factura). Dos métodos de extracción, Plus con un detector de tres líneas, reemplazan la llamada a la API de Mindeesin sacrificar la precisión en documentos bien formados:

var result  = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
    ? ExtractInvoiceFields(result)
    : ExtractReceiptFields(result);
var result  = _ocr.Read(filePath);
var summary = result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0
    ? ExtractInvoiceFields(result)
    : ExtractReceiptFields(result);
Dim result = _ocr.Read(filePath)
Dim summary = If(result.Text.IndexOf("Invoice", StringComparison.OrdinalIgnoreCase) >= 0, ExtractInvoiceFields(result), ExtractReceiptFields(result))
$vbLabelText   $csharpLabel

Lista de verificación de migración de Mindee

Pre-Migración

Auditar todo el uso de Mindeeen todo el código fuente:

grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
grep -r "using Mindee" --include="*.cs" .
grep -r "MindeeClient\|ParseAsync\|InvoiceV4\|ReceiptV5\|PassportV1\|FinancialDocumentV1" --include="*.cs" .
grep -r "LocalInputSource\|MindeeException" --include="*.cs" .
grep -r "Mindee:ApiKey\|mindee_api_key\|MINDEE_API_KEY" --include="*.json" --include="*.env" --include="*.yml" .
SHELL

Resultados del inventario:

  • Cuenta sitios de llamada únicos usando ParseAsync<t>
  • Nota qué tipos de documentos se usan (InvoiceV4, ReceiptV5, PassportV1, FinancialDocumentV1)
  • Identificar todas las cadenas de métodos asíncronos que se propagan desde las llamadas a Mindee.
  • Localice las referencias a las claves API en los archivos de configuración y en los almacenes de secretos.
  • Identificar la lógica de reintento que encapsula las llamadas de red de Mindee
  • Identificar las reglas de salida de red que permiten el tráfico saliente hacia los puntos finales de Mindee.

Migración de código

  1. Elimina el paquete NuGet Mindee del archivo del proyecto
  2. Ejecuta dotnet add package IronOcr para instalar IronOCR
  3. Agrega IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" al inicio de la aplicación
  4. Elimina todas las directivas using Mindee, using Mindee.Input, y using Mindee.Product.*
  5. Reemplaza la inyección de constructor MindeeClient con la instanciación o registro DI IronTesseract
  6. Reemplaza cada llamada ParseAsync<InvoiceV4> con _ocr.Read(filePath) más métodos de extracción de facturas
  7. Reemplaza cada llamada ParseAsync<ReceiptV5> con _ocr.Read(filePath) más métodos de extracción de recibos
  8. Reemplaza cada llamada ParseAsync<PassportV1> con OCR basado en zonas usando CropRectangle en el área MRZ
  9. Reemplaza cada llamada ParseAsync<FinancialDocumentV1> con el detector de tipo de documento + patrón de enrutamiento
  10. Elimina todas las líneas de cumplimiento de límite de tasa await Task.Delay(...)
  11. Elimina todos los bloques catch (MindeeException) y catch (HttpRequestException) de los caminos de OCR
  12. Añade llamadas de preprocesamiento OcrInput (Deskew, DeNoise, Contrast) para caminos de documentos escaneados
  13. Añade result.SaveAsSearchablePdf(archivePath) a cualquier camino de archivo de documentos
  14. Elimine la clave API de Mindeede todos los archivos de configuración y bóvedas de secretos.
  15. Actualizar las pruebas de integración para que se ejecuten de forma síncrona sin simulación de red.

Posmigración

  • Verificar la precisión de la extracción comparándola con un conjunto de muestra de 20 a 30 documentos que abarque cada tipo de documento.
  • Confirma valores result.Confidence por encima de 80 en escaneos limpios representativos; aplicar preprocesamiento si se indica a continuación
  • Prueba la tubería de preprocesamiento (Deskew, DeNoise) en muestras de escaneos sesgados y degradados
  • Verifica que PDFs de varias páginas produzcan result.Pages.Count correctos y contenido por página
  • Confirma que la salida result.SaveAsSearchablePdf() sea buscable en Adobe Acrobat y visores de PDF del sistema
  • Prueba toda la extracción basada en zonas (CropRectangle) contra las variaciones de diseño de documentos en tu conjunto de proveedores
  • Ejecuta procesamiento por lotes sin llamadas Task.Delay y verifica que no haya problemas de subprocesos
  • Confirme que la aplicación se inicia y funciona correctamente sin acceso a Internet saliente.
  • Verifica que la inicialización de la clave de licencia se ejecute antes de la primera llamada _ocr.Read() en cada punto de entrada
  • Compruebe que no queden referencias a claves API de Mindeeen la configuración, variables de entorno o secretos.

Principales ventajas de migrar a IronOCR

Control total de los datos de los documentos financieros. Tras la migración, los códigos IBAN de los proveedores, los números de ruta, los números EIN de los clientes y los detalles de las partidas permanecen siempre dentro de su infraestructura. El alcance del cumplimiento normativo abarca únicamente a su organización. Los acuerdos de procesamiento con proveedores externos de IA se eliminan de su registro de auditoría. Los equipos que operan bajo los requisitos de GLBA, CMMC, FedRAMP o residencia de datos pueden procesar documentos financieros sin la revisión de cumplimiento que requiere la transmisión a la nube externa.

Costo predecible sin importar el volumen. La licencia Lite de IronOCR en $999 cubre un desarrollador con procesamiento de documentos ilimitado. Pasar de 500 facturas al mes a 50.000 facturas al mes no supone ningún coste adicional. Los picos de procesamiento de fin de año, las ejecuciones de corrección por lotes y los ciclos de pruebas de desarrollo no incrementan ningún contador de facturación. El coste total de propiedad durante tres años para un equipo que procesa 5.000 páginas al mes se reduce de aproximadamente 17.964 dólares (Mindee Pro) a entre 1.499 y 2.999 dólares (licencia perpetua de IronOCR). Consulte la página completa del producto IronOCR para obtener detalles sobre los diferentes niveles.

La lógica de extracción es de su propiedad de forma permanente. Los patrones de extracción de IronOCR son código C# simple en su repositorio. Son sistemas con control de versiones, revisables, comprobables e implementables independientemente del calendario de lanzamientos o las decisiones de versionado de la API de cualquier proveedor externo. Cuando Mindeelance InvoiceV5 y descontinúe InvoiceV4, eso es una fecha límite de migración impuesta por el proveedor. Cuando mantienes patrones de extracción, la mejora es git commit.

Control de preprocesamiento para una calidad de escaneo en condiciones reales. Las operaciones de cuentas por pagar reciben facturas de docenas o cientos de proveedores, cada una escaneada con equipos diferentes y configuraciones distintas. La tubería de preprocesamiento de IronOCR— Deskew(), DeNoise(), Contrast(), Sharpen(), Binarize() — aborda los defectos específicos en cada categoría de escaneo. Una fotografía de un recibo tomada con impresora térmica presenta defectos diferentes a los de una factura de varias páginas escaneada con escáner plano; se aplican los filtros adecuados para cada caso. El preprocesamiento de Mindeees invisible y no configurable. Consulte la página de funciones de preprocesamiento para ver el catálogo completo de filtros.

Archivado de PDF buscable en un solo paso. Cada factura procesada a través de IronOCR puede archivarse como un PDF buscable con result.SaveAsSearchablePdf(). La capa de texto reconocida está integrada en el archivo de salida, lo que permite realizar búsquedas de texto completo de cada palabra en sistemas de gestión documental, bibliotecas de SharePoint y repositorios de contenido Enterprise . Mindeeno proporciona ningún tipo de salida en formato PDF; Anteriormente, el archivado con capacidad de búsqueda requería una biblioteca independiente, una fase de procesamiento independiente y un mantenimiento adicional. Después de la migración, la extracción y el archivado se colapsan en una sola llamada _ocr.Read(input).

Implementación en cualquier entorno sin cambios de arquitectura.IronOCR se implementa en Windows, Linux, macOS, Docker, Azure App Service, AWS Lambda y Google Cloud Run utilizando el mismo paquete NuGet y la misma inicialización. Los entornos aislados, los sistemas de las plantas de producción y las instalaciones gubernamentales de alta seguridad funcionan sin necesidad de modificaciones. La guía de implementación de Docker , la guía de Azure y la guía de AWS cubren la configuración específica del entorno para cada plataforma.

Por favor notaAdobe Acrobat, Mindee, PDFSharp, Tesseract e iText son marcas registradas de sus respectivos propietarios. Este sitio no está afiliado, respaldado ni patrocinado por Adobe Inc., Google, Mindee, empira Software GmbH ni iText Group. Todos los nombres de productos, logotipos y marcas son propiedad de sus respectivos dueños. Las comparaciones son solo para fines informativos y reflejan información públicamente disponible en el momento de la redacción.

Preguntas Frecuentes

¿Por qué debería migrar de Mindee OCR API a IronOCR?

Los impulsores comunes incluyen la eliminación de la complejidad de la interoperabilidad COM, la sustitución de la gestión de licencias basada en archivos, la evitación de la facturación por página, la habilitación de la implementación de Docker/contenedores y la adopción de un flujo de trabajo nativo de NuGet que se integre con las herramientas .NET estándar.

¿Cuáles son los principales cambios de código al migrar de Mindee OCR API a IronOCR?

Sustituya las secuencias de inicialización de Mindee por la instanciación de IronTesseract, elimine la gestión del ciclo de vida COM (patrones explícitos Create/Load/Close) y actualice los nombres de las propiedades de los resultados. El resultado es un número significativamente menor de líneas repetitivas.

¿Cómo instalo IronOCR para comenzar la migración?

Ejecute 'Install-Package IronOcr' en la consola del gestor de paquetes o 'dotnet add package IronOcr' en la CLI. Los paquetes de idiomas son paquetes independientes: 'dotnet add package IronOcr.Languages.French' para el francés, por ejemplo.

¿IronOCR alcanza la precisión de OCR de la API de OCR de Mindee para documentos comerciales estándar?

IronOCR consigue una gran precisión para contenido empresarial estándar, como facturas, contratos, recibos y formularios mecanografiados. Los filtros de preprocesamiento de imágenes (eliminación de distorsiones, eliminación de ruido, mejora del contraste) mejoran aún más el reconocimiento en entradas degradadas.

¿Cómo maneja IronOCR los datos de idioma que la API Mindee OCR instala por separado?

Los datos de idiomas en IronOCR se distribuyen como paquetes NuGet. dotnet add package IronOcr.Languages.German' instala la compatibilidad con el alemán. No es necesario colocar manualmente los archivos ni las rutas de los directorios.

¿La migración de Mindee OCR API a IronOCR requiere cambios en la infraestructura de implementación?

IronOCR requiere menos cambios de infraestructura que Mindee OCR API. No hay rutas binarias del SDK, ubicaciones de archivos de licencia ni configuraciones del servidor de licencias. El paquete NuGet contiene el motor OCR completo, y la clave de licencia es una cadena establecida en el código de la aplicación.

¿Cómo configuro las licencias de IronOCR después de la migración?

Asigne IronOcr.License.LicenseKey = "YOUR-KEY" en el código de inicio de la aplicación. En Docker o Kubernetes, almacene la clave como una variable de entorno y léala en el inicio. Utilice License.IsValidLicense para validar antes de aceptar tráfico.

¿Puede IronOCR procesar archivos PDF del mismo modo que Mindee?

Sí, IronOCR lee PDF nativos y escaneados. Instancie IronTesseract, llame a ocr.Read(input) donde input es una ruta PDF u OcrPdfInput, e itere las páginas OcrResult. No es necesario un proceso de renderizado de PDF independiente.

¿Cómo gestiona IronOCR los hilos en el procesamiento de grandes volúmenes?

IronTesseract puede instanciarse de forma segura por subproceso. Gire una instancia por hilo en un Parallel.ForEach o Task pool, ejecute OCR concurrentemente, y disponga de cada instancia cuando termine. No se requiere estado global o bloqueo.

¿Qué formatos de salida admite IronOCR tras la extracción de texto?

IronOCR devuelve resultados estructurados que incluyen texto, coordenadas de palabras, puntuaciones de confianza y estructura de páginas. Las opciones de exportación incluyen texto sin formato, PDF con opción de búsqueda y objetos de resultados estructurados para su procesamiento posterior.

¿Es el precio de IronOCR más predecible que el de Mindee OCR API para escalar cargas de trabajo?

IronOCR utiliza licencias perpetuas de tarifa plana sin cargos por página o volumen. Tanto si procesa 10.000 como 10 millones de páginas, el coste de la licencia permanece constante. Las opciones de licencias por volumen y por equipo se encuentran en la página de precios de IronOCR.

¿Qué sucede con mis pruebas existentes después de migrar de Mindee OCR API a IronOCR?

Las pruebas que validan el contenido de texto extraído deben seguir superándose tras la migración. Las pruebas que validan patrones de llamada a API o el ciclo de vida de objetos COM deberán actualizarse para reflejar el modelo de inicialización y resultados más sencillo de IronOCR.

Kannaopat Udonpant
Ingeniero de Software
Antes de convertirse en Ingeniero de Software, Kannapat completó un doctorado en Recursos Ambientales de la Universidad de Hokkaido en Japón. Mientras perseguía su grado, Kannapat también se convirtió en miembro del Laboratorio de Robótica de Vehículos, que es parte del Departamento de Ingeniería ...
Leer más

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame