Saltar al pie de página
VIDEOS

Cómo implementar OCR en C# usando bibliotecas de código abierto

Esta guía está dirigida a desarrolladores .NET que hayan integrado la API REST de Klippa y estén migrando a IronOCR para el procesamiento de documentos en sus propias instalaciones. Cubre los pasos prácticos para eliminar la infraestructura del cliente HTTP, suprimir la deserialización JSON y reemplazar las cargas de documentos dependientes de la nube con llamadas OCR locales que nunca acceden a la red.

¿Por qué migrar desde Klippa OCR?

Klippa es un servicio de inteligencia documental exclusivamente en la nube que no dispone de un SDK .NET . Cada integración es un cliente REST desarrollado manualmente. Esa realidad arquitectónica tiene consecuencias posteriores que se acumulan a lo largo de la vida útil de un sistema de producción.

Si no hay paquete NuGet , usted es el propietario de la capa de integración. No hay nada que instalar. El costo de entrada es escribir un envoltorio HttpClient, configurar los encabezados de autenticación X-Auth-Key, construir los cuerpos de solicitud MultipartFormDataContent, deserializar el esquema de respuesta JSON de Klippa y conectar la lógica de reintentos para fallos transitorios. Eso supone entre 2 y 4 días de trabajo de ajuste antes de que el primer documento se procese de forma fiable en producción. Cuando Klippa actualiza el esquema de su API, su código de deserialización deja de funcionar y requiere mantenimiento manual.

Cada carga de documento implica una dependencia de red. Klippa procesa los documentos exclusivamente en servidores alojados en la UE. Las interrupciones en la producción en el lado de Klippa, la latencia elevada o cualquier interrupción en el acceso saliente a Internet desde su servidor de aplicaciones detienen por completo el procesamiento de documentos. No existe ningún mecanismo de respaldo, ni modo local, ni reintento que resuelva el problema de que un servicio en la nube no esté disponible.

Los documentos confidenciales salen de su infraestructura. Los documentos financieros (recibos con detalles de pago, facturas con números de IVA e importes, documentos de identidad con datos del pasaporte) se transmiten a un servidor de terceros en cada llamada a la API. Las disposiciones del RGPD sobre transferencia de datos abordan parte de esto en el caso del procesamiento con sede en la UE, pero el alcance de la auditoría aún se extiende a la infraestructura de Klippa, las políticas de retención de datos y los subprocesadores. Para los equipos con contratos en los sectores sanitario, jurídico, financiero o gubernamental, la denominación "alojado en la UE" no cumple con el requisito de que los datos no salgan de la organización.

Los precios por documento varían sin límite máximo. Klippa no publica sus precios. Con cualquier volumen significativo de documentos (10.000 recibos al mes en un sistema de gestión de gastos, 500 facturas al día en un flujo de trabajo de automatización de cuentas por pagar), el modelo de facturación por documento genera costes que una licencia perpetua jamás generaría. La trayectoria de los costes está directamente ligada al crecimiento empresarial, lo cual es lo contrario de lo que debería ocurrir con el gasto en infraestructuras.

El alcance del especialista se rompe cuando los requisitos se amplían. Klippa está capacitado en recibos, facturas y documentos de identidad. Una aplicación que comienza como una herramienta de gestión de gastos rara vez se queda ahí. La primera vez que aparece un tipo de documento que no pertenece a esas tres categorías (un contrato de trabajo escaneado, un formulario médico, un dibujo técnico, una orden de compra con un formato no estándar), Klippa no devuelve ningún resultado útil.IronOCR procesa cualquier documento que contenga texto, sin restricciones de categoría.

Las llamadas REST que solo utilizan operaciones asíncronas añaden latencia en contextos síncronos. Cada llamada a Klippa es una operación HTTP asíncrona. El envío de un solo documento (ida y vuelta) tarda entre 500 ms y 2000 ms en la red.IronOCR procesa el mismo documento localmente en 100-400 ms sin la sobrecarga asíncrona en escenarios donde el procesamiento síncrono se ajusta mejor a la arquitectura.

El problema fundamental

Klippa no tiene SDK. OCR significa construir y enviar una solicitud HTTP, y luego deserializar JSON:

// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks

var response = await _client.PostAsync(
    "https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost

var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
// Klippa: 15+ lines of HTTP plumbing before you read a single character
var content = new MultipartFormDataContent();
content.Add(new ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg");
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey); // auth header — rotates, breaks, leaks

var response = await _client.PostAsync(
    "https://custom-ocr.klippa.com/api/v1/parseDocument", content);
response.EnsureSuccessStatusCode(); // throws on 4xx/5xx — no retry, document lost

var json = await response.Content.ReadAsStringAsync();
var parsed = JsonSerializer.Deserialize<KlippaResponse>(json); // your schema, your maintenance
var text = parsed?.Data?.ParsedDocument?.Text; // nullable chain — breaks when schema changes
Imports System.Net.Http
Imports System.IO
Imports System.Text.Json
Imports System.Threading.Tasks

' Klippa: 15+ lines of HTTP plumbing before you read a single character
Dim content As New MultipartFormDataContent()
content.Add(New ByteArrayContent(File.ReadAllBytes(imagePath)), "document", "receipt.jpg")
_client.DefaultRequestHeaders.Add("X-Auth-Key", _apiKey) ' auth header — rotates, breaks, leaks

Dim response As HttpResponseMessage = Await _client.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", content)
response.EnsureSuccessStatusCode() ' throws on 4xx/5xx — no retry, document lost

Dim json As String = Await response.Content.ReadAsStringAsync()
Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json) ' your schema, your maintenance
Dim text As String = parsed?.Data?.ParsedDocument?.Text ' nullable chain — breaks when schema changes
$vbLabelText   $csharpLabel

IronOCR lo reemplaza todo:

// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
// IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
var text = new IronTesseract().Read(imagePath).Text;
' IronOCR: no HTTP, no auth headers, no JSON — just text
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim text = New IronTesseract().Read(imagePath).Text
$vbLabelText   $csharpLabel

IronOCR vs Klippa OCR: Comparación de características

La tabla que aparece a continuación compara las dos bibliotecas en función de las dimensiones más importantes para tomar una decisión sobre la migración a producción.

Característica Klippa OCR IronOCR
Modelo de despliegue Solo en la nube (servidores de la UE) En el establecimiento, totalmente local.
SDK de .NET / Paquete NuGet None IronOcr paquete NuGet
Se requiere conexión a Internet Sí, en cada llamada Nunca
Los datos del documento salen de la red. Siempre Nunca
OCR de propósito general No (solo recibos, facturas e identificaciones) Sí (cualquier tipo de documento)
Configuración de autenticación X-Auth-Key encabezado HTTP IronOcr.License.LicenseKey cadena
Se requiere un cliente HTTP No
deserialización de respuesta Análisis manual de JSON Objeto tipado OcrResult
Lógica de reintento/tiempo de espera Enrollado a mano No es necesario (llamada local)
Soporte sin conexión / aislado de la red No
Entrada en PDF Sí (nube) Sí (nativo, local)
Entrada TIFF de varias páginas Desconocido
Formatos de entrada de imagen JPG, PNG (nube) JPG, PNG, BMP, TIFF, GIF y más
Entrada de flujo y matriz de bytes Sin SDK
preprocesamiento automático de imágenes Lado de las nubes (opaco) Sí (Desinclinar, Reducir ruido, Contraste, Binarizar, Nitidez)
Salida estructurada: coordenadas de palabras No
Puntuación de confianza por palabra No
Salida en PDF con capacidad de búsqueda No
Lectura de BarCodes durante el OCR No
Soporte multilingüe Limitado a tipos de documentos capacitados más de 125 idiomas
Seguridad de los hilos No disponible (llamadas HTTP) Sí (uno IronTesseract por hilo)
Implementación multiplataforma Independiente de REST Windows, Linux, macOS, Docker, Azure, AWS
Cumplimiento de HIPAA / ITAR / aislamiento de red No
Modelo de precios Software como servicio (SaaS) por documento (tarifas no publicadas) Licencia perpetua de $999
Coste por página a escala Sí, sin límites None

Inicio rápido: Migración de Klippa OCRa IronOCR

Paso 1: Sustituir el paquete NuGet

Klippa no tiene un paquete NuGet oficial. Elimine las dependencias del cliente HTTP que existen únicamente para admitir la integración con Klippa:

# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
# Remove Klippa-related packages (if installed for REST support)
dotnet remove package Newtonsoft.Json
dotnet remove package System.Net.Http.Json
SHELL

Instala IronOCR desde NuGet :

dotnet add package IronOcr

Paso 2: Actualizar los espacios de nombres

Elimine los espacios de nombres HTTP y JSON que requería la integración con Klippa. Agregue el espacio de nombres único de IronOCR:

// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;

// After (IronOCR)
using IronOcr;
// Before (Klippa integration)
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.Json.Serialization;

// After (IronOCR)
using IronOcr;
Imports System.Net.Http
Imports System.Net.Http.Headers
Imports System.Text.Json
Imports System.Text.Json.Serialization

Imports IronOcr
$vbLabelText   $csharpLabel

Paso 3: Inicializar licencia

Agrega la inicialización de la licencia una vez al inicio de la aplicación — en Program.cs, Startup.cs, o antes de la primera llamada a 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

Sustitución de la clase de servicio de cliente HTTP

La integración de Klippa requiere una clase de servicio completa que encapsule la infraestructura HTTP. No hay forma de evitar esto porque no existe un SDK.

Enfoque de Klippa:

// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";

    public KlippaOcrService(string apiKey)
    {
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
        _httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
    }

    public async Task<string> ReadDocumentTextAsync(string filePath)
    {
        using var form = new MultipartFormDataContent();
        var fileBytes = await File.ReadAllBytesAsync(filePath);
        form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));

        var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        // navigate Klippa's nested JSON schema
        return doc.RootElement
            .GetProperty("data")
            .GetProperty("parsed_document")
            .GetProperty("text")
            .GetString() ?? string.Empty;
    }

    public void Dispose() => _httpClient.Dispose();
}
// Klippa: entire service class just to send one HTTP request
public class KlippaOcrService : IDisposable
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl = "https://custom-ocr.klippa.com/api/v1";

    public KlippaOcrService(string apiKey)
    {
        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey);
        _httpClient.Timeout = TimeSpan.FromSeconds(30); // network timeout required
    }

    public async Task<string> ReadDocumentTextAsync(string filePath)
    {
        using var form = new MultipartFormDataContent();
        var fileBytes = await File.ReadAllBytesAsync(filePath);
        form.Add(new ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath));

        var response = await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form);
        response.EnsureSuccessStatusCode();

        var json = await response.Content.ReadAsStringAsync();
        using var doc = JsonDocument.Parse(json);
        // navigate Klippa's nested JSON schema
        return doc.RootElement
            .GetProperty("data")
            .GetProperty("parsed_document")
            .GetProperty("text")
            .GetString() ?? string.Empty;
    }

    public void Dispose() => _httpClient.Dispose();
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Threading.Tasks
Imports System.Text.Json

' Klippa: entire service class just to send one HTTP request
Public Class KlippaOcrService
    Implements IDisposable

    Private ReadOnly _httpClient As HttpClient
    Private ReadOnly _baseUrl As String = "https://custom-ocr.klippa.com/api/v1"

    Public Sub New(apiKey As String)
        _httpClient = New HttpClient()
        _httpClient.DefaultRequestHeaders.Add("X-Auth-Key", apiKey)
        _httpClient.Timeout = TimeSpan.FromSeconds(30) ' network timeout required
    End Sub

    Public Async Function ReadDocumentTextAsync(filePath As String) As Task(Of String)
        Using form As New MultipartFormDataContent()
            Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
            form.Add(New ByteArrayContent(fileBytes), "document", Path.GetFileName(filePath))

            Dim response = Await _httpClient.PostAsync($"{_baseUrl}/parseDocument", form)
            response.EnsureSuccessStatusCode()

            Dim json = Await response.Content.ReadAsStringAsync()
            Using doc = JsonDocument.Parse(json)
                ' navigate Klippa's nested JSON schema
                Return doc.RootElement _
                    .GetProperty("data") _
                    .GetProperty("parsed_document") _
                    .GetProperty("text") _
                    .GetString() OrElse String.Empty
            End Using
        End Using
    End Function

    Public Sub Dispose() Implements IDisposable.Dispose
        _httpClient.Dispose()
    End Sub
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ReadDocumentText(string filePath)
    {
        return _ocr.Read(filePath).Text;
    }
}

// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
// IronOCR: no HTTP, no JSON navigation, no Dispose plumbing
public class OcrService
{
    private readonly IronTesseract _ocr = new IronTesseract();

    public string ReadDocumentText(string filePath)
    {
        return _ocr.Read(filePath).Text;
    }
}

// At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Usage — identical call site, different internals:
var service = new OcrService();
var text = service.ReadDocumentText("invoice.jpg"); // local, synchronous, zero network
Imports IronOcr

Public Class OcrService
    Private ReadOnly _ocr As New IronTesseract()

    Public Function ReadDocumentText(filePath As String) As String
        Return _ocr.Read(filePath).Text
    End Function
End Class

' At startup:
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' Usage — identical call site, different internals:
Dim service As New OcrService()
Dim text As String = service.ReadDocumentText("invoice.jpg") ' local, synchronous, zero network
$vbLabelText   $csharpLabel

La clase de servicio Klippa existe únicamente porque la API requiere infraestructura HTTP. El equivalente de IronOCR se reduce a una única llamada Read(). Los tiempos de espera, los encabezados de autenticación y los patrones de eliminación desaparecen porque no hay red. Consulte la guía de configuración de IronTesseract para conocer las opciones de inicialización y el ejemplo básico de OCR para ver el código funcional.

Eliminación de la carga de formularios multipartes

Klippa recibe los documentos como cargas de formularios multipartes. El código de carga es mecánico pero frágil: lectura de archivos, encabezados de tipo de contenido, construcción de límites y gestión del tamaño de la carga.

Enfoque de Klippa:

// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
    string filePath, string documentType = "financial")
{
    using var form = new MultipartFormDataContent();

    // read file into memory — entire document in RAM before upload
    var fileBytes = await File.ReadAllBytesAsync(filePath);
    var byteContent = new ByteArrayContent(fileBytes);
    byteContent.Headers.ContentType =
        new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");

    form.Add(byteContent, "document", Path.GetFileName(filePath));
    form.Add(new StringContent(documentType), "DocumentType");

    // document leaves your server here
    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);

    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadAsStringAsync();
        throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
    }

    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<KlippaResult>(json,
        new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
// Klippa: multipart upload — every document is an HTTP form POST
public async Task<KlippaResult> UploadAndParseAsync(
    string filePath, string documentType = "financial")
{
    using var form = new MultipartFormDataContent();

    // read file into memory — entire document in RAM before upload
    var fileBytes = await File.ReadAllBytesAsync(filePath);
    var byteContent = new ByteArrayContent(fileBytes);
    byteContent.Headers.ContentType =
        new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");

    form.Add(byteContent, "document", Path.GetFileName(filePath));
    form.Add(new StringContent(documentType), "DocumentType");

    // document leaves your server here
    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);

    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadAsStringAsync();
        throw new InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}");
    }

    var json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<KlippaResult>(json,
        new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks

Public Class KlippaUploader
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    Public Async Function UploadAndParseAsync(filePath As String, Optional documentType As String = "financial") As Task(Of KlippaResult)
        Using form As New MultipartFormDataContent()
            ' read file into memory — entire document in RAM before upload
            Dim fileBytes = Await File.ReadAllBytesAsync(filePath)
            Dim byteContent = New ByteArrayContent(fileBytes)
            byteContent.Headers.ContentType = New System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg")

            form.Add(byteContent, "document", Path.GetFileName(filePath))
            form.Add(New StringContent(documentType), "DocumentType")

            ' document leaves your server here
            Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)

            If Not response.IsSuccessStatusCode Then
                Dim error = Await response.Content.ReadAsStringAsync()
                Throw New InvalidOperationException($"Klippa API error: {response.StatusCode} — {error}")
            End If

            Dim json = Await response.Content.ReadAsStringAsync()
            Return JsonSerializer.Deserialize(Of KlippaResult)(json, New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True})
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);

// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);

Console.WriteLine(result.Text);
// IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// From file path
using var input = new OcrInput();
input.LoadImage("invoice.jpg");
var result = new IronTesseract().Read(input);

// From byte array (same bytes Klippa was uploading)
byte[] fileBytes = await File.ReadAllBytesAsync("invoice.jpg");
using var inputFromBytes = new OcrInput();
inputFromBytes.LoadImage(fileBytes);
var resultFromBytes = new IronTesseract().Read(inputFromBytes);

Console.WriteLine(result.Text);
Imports IronOcr
Imports System.IO

' IronOCR: load from file path, byte array, or stream — no upload, no form
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' From file path
Using input As New OcrInput()
    input.LoadImage("invoice.jpg")
    Dim result = New IronTesseract().Read(input)

    ' From byte array (same bytes Klippa was uploading)
    Dim fileBytes As Byte() = Await File.ReadAllBytesAsync("invoice.jpg")
    Using inputFromBytes As New OcrInput()
        inputFromBytes.LoadImage(fileBytes)
        Dim resultFromBytes = New IronTesseract().Read(inputFromBytes)

        Console.WriteLine(result.Text)
    End Using
End Using
$vbLabelText   $csharpLabel

La construcción MultipartFormDataContent, los encabezados de tipo de contenido y la carga en sí han desaparecido.IronOCR lee directamente desde la ruta del archivo, desde una matriz de bytes o desde un Stream — los mismos datos que Klippa estaba transmitiendo a la nube se mantienen locales. La guía de entrada de imágenes abarca todos los formatos de entrada compatibles, y la guía de entrada de flujo abarca la ruta de flujo de memoria para los documentos que llegan como matrices de bytes desde procesos anteriores.

Reemplazar la deserialización de respuesta JSON

Klippa devuelve una estructura JSON anidada. Navegar por esa estructura requiere ya sea un modelo C# coincidente o un recorrido JsonDocument en línea, ambos los cuales se rompen cuando Klippa cambia su esquema de respuesta.

Enfoque de Klippa:

// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
    [JsonPropertyName("data")]
    public KlippaData Data { get; set; }
}

public class KlippaData
{
    [JsonPropertyName("parsed_document")]
    public KlippaParsedDocument ParsedDocument { get; set; }
}

public class KlippaParsedDocument
{
    [JsonPropertyName("text")]
    public string Text { get; set; }

    [JsonPropertyName("amount")]
    public decimal? Amount { get; set; }

    [JsonPropertyName("merchant")]
    public string Merchant { get; set; }

    [JsonPropertyName("date")]
    public string Date { get; set; }
}

// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
    var klippaResult = await UploadAndParseAsync(imagePath);
    // every property access is nullable — schema drift breaks this silently
    return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
// Klippa: deserialization model — breaks when API schema changes
public class KlippaResponse
{
    [JsonPropertyName("data")]
    public KlippaData Data { get; set; }
}

public class KlippaData
{
    [JsonPropertyName("parsed_document")]
    public KlippaParsedDocument ParsedDocument { get; set; }
}

public class KlippaParsedDocument
{
    [JsonPropertyName("text")]
    public string Text { get; set; }

    [JsonPropertyName("amount")]
    public decimal? Amount { get; set; }

    [JsonPropertyName("merchant")]
    public string Merchant { get; set; }

    [JsonPropertyName("date")]
    public string Date { get; set; }
}

// Usage: navigate the nullable chain every time
public async Task<string> GetExtractedTextAsync(string imagePath)
{
    var klippaResult = await UploadAndParseAsync(imagePath);
    // every property access is nullable — schema drift breaks this silently
    return klippaResult?.Data?.ParsedDocument?.Text ?? string.Empty;
}
Imports System.Text.Json.Serialization

' Klippa: deserialization model — breaks when API schema changes
Public Class KlippaResponse
    <JsonPropertyName("data")>
    Public Property Data As KlippaData
End Class

Public Class KlippaData
    <JsonPropertyName("parsed_document")>
    Public Property ParsedDocument As KlippaParsedDocument
End Class

Public Class KlippaParsedDocument
    <JsonPropertyName("text")>
    Public Property Text As String

    <JsonPropertyName("amount")>
    Public Property Amount As Decimal?

    <JsonPropertyName("merchant")>
    Public Property Merchant As String

    <JsonPropertyName("date")>
    Public Property Date As String
End Class

' Usage: navigate the nullable chain every time
Public Async Function GetExtractedTextAsync(imagePath As String) As Task(Of String)
    Dim klippaResult = Await UploadAndParseAsync(imagePath)
    ' every property access is nullable — schema drift breaks this silently
    Return If(klippaResult?.Data?.ParsedDocument?.Text, String.Empty)
End Function
$vbLabelText   $csharpLabel

Enfoque IronOCR:

// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");

// Direct property access — no deserialization, no nullable navigation
string fullText   = result.Text;
double confidence = result.Confidence;
int pageCount     = result.Pages.Count();

// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
    foreach (var line in page.Lines)
    {
        Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
    }
}
// IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

var ocr = new IronTesseract();
var result = ocr.Read("invoice.jpg");

// Direct property access — no deserialization, no nullable navigation
string fullText   = result.Text;
double confidence = result.Confidence;
int pageCount     = result.Pages.Count();

// Structured data: lines and words with coordinates
foreach (var page in result.Pages)
{
    foreach (var line in page.Lines)
    {
        Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}");
    }
}
Imports IronOcr

' IronOCR: typed result object — no JSON schema, no nullable chains
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Dim ocr As New IronTesseract()
Dim result = ocr.Read("invoice.jpg")

' Direct property access — no deserialization, no nullable navigation
Dim fullText As String = result.Text
Dim confidence As Double = result.Confidence
Dim pageCount As Integer = result.Pages.Count()

' Structured data: lines and words with coordinates
For Each page In result.Pages
    For Each line In page.Lines
        Console.WriteLine($"Line: '{line.Text}' at Y={line.Y}")
    Next
Next
$vbLabelText   $csharpLabel

OcrResult es un objeto tipado .NET. No hay que analizar ningún JSON, ni mantener ninguna clase de modelo, ni existe riesgo de que la desviación del esquema interrumpa la deserialización en producción. La guía de resultados de lectura documenta el modelo de objeto OcrResult completo, incluidos los coordenadas de palabras, las puntuaciones de confianza y la jerarquía de página estructurada. Para patrones de extracción de campos específicos de facturas construidos sobre OcrResult, el tutorial de OCR de facturas cubre la lógica de extracción de extremo a extremo.

Eliminación de la infraestructura de manejo de errores y reintentos

La integración de Klippa a través de HTTP requiere el manejo de errores para cada modo de fallo que pueda producir una llamada de red: tiempos de espera agotados, respuestas 4xx, respuestas 5xx, límites de velocidad y JSON parcial. Los equipos que gestionan integraciones de producción añaden políticas de reintento mediante Polly o lógica personalizada. Esa infraestructura desaparece cuando desaparece la llamada de red.

Enfoque de Klippa:

// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
    var delay = TimeSpan.FromSeconds(1);

    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            using var form = new MultipartFormDataContent();
            form.Add(
                new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
                "document",
                Path.GetFileName(filePath));

            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
            var response = await _httpClient.PostAsync(
                "https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);

            if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                // rate limited — back off and retry
                await Task.Delay(delay * attempt);
                continue;
            }

            response.EnsureSuccessStatusCode();
            var json = await response.Content.ReadAsStringAsync(cts.Token);
            var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
            return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
        }
        catch (HttpRequestException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // exponential backoff
        }
        catch (TaskCanceledException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // timeout — retry
        }
    }

    throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
// Klippa: retry policy required — cloud calls fail unpredictably
public async Task<string> ReadWithRetryAsync(string filePath, int maxRetries = 3)
{
    var delay = TimeSpan.FromSeconds(1);

    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            using var form = new MultipartFormDataContent();
            form.Add(
                new ByteArrayContent(await File.ReadAllBytesAsync(filePath)),
                "document",
                Path.GetFileName(filePath));

            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
            var response = await _httpClient.PostAsync(
                "https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token);

            if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
            {
                // rate limited — back off and retry
                await Task.Delay(delay * attempt);
                continue;
            }

            response.EnsureSuccessStatusCode();
            var json = await response.Content.ReadAsStringAsync(cts.Token);
            var parsed = JsonSerializer.Deserialize<KlippaResponse>(json);
            return parsed?.Data?.ParsedDocument?.Text ?? string.Empty;
        }
        catch (HttpRequestException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // exponential backoff
        }
        catch (TaskCanceledException) when (attempt < maxRetries)
        {
            await Task.Delay(delay * attempt); // timeout — retry
        }
    }

    throw new InvalidOperationException($"Klippa API failed after {maxRetries} attempts");
}
Imports System
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading
Imports System.Threading.Tasks

Public Class KlippaService
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    ' Klippa: retry policy required — cloud calls fail unpredictably
    Public Async Function ReadWithRetryAsync(filePath As String, Optional maxRetries As Integer = 3) As Task(Of String)
        Dim delay As TimeSpan = TimeSpan.FromSeconds(1)

        For attempt As Integer = 1 To maxRetries
            Try
                Using form As New MultipartFormDataContent()
                    form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(filePath)), "document", Path.GetFileName(filePath))

                    Using cts As New CancellationTokenSource(TimeSpan.FromSeconds(30))
                        Dim response As HttpResponseMessage = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form, cts.Token)

                        If response.StatusCode = System.Net.HttpStatusCode.TooManyRequests Then
                            ' rate limited — back off and retry
                            Await Task.Delay(delay * attempt)
                            Continue For
                        End If

                        response.EnsureSuccessStatusCode()
                        Dim json As String = Await response.Content.ReadAsStringAsync(cts.Token)
                        Dim parsed As KlippaResponse = JsonSerializer.Deserialize(Of KlippaResponse)(json)
                        Return If(parsed?.Data?.ParsedDocument?.Text, String.Empty)
                    End Using
                End Using
            Catch ex As HttpRequestException When attempt < maxRetries
                Await Task.Delay(delay * attempt) ' exponential backoff
            Catch ex As TaskCanceledException When attempt < maxRetries
                Await Task.Delay(delay * attempt) ' timeout — retry
            End Try
        Next

        Throw New InvalidOperationException($"Klippa API failed after {maxRetries} attempts")
    End Function
End Class

Public Class KlippaResponse
    Public Property Data As KlippaData
End Class

Public Class KlippaData
    Public Property ParsedDocument As ParsedDocument
End Class

Public Class ParsedDocument
    Public Property Text As String
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

public string ReadDocument(string filePath)
{
    // No retry loop. No CancellationTokenSource. No HTTP status checks.
    // No rate limit handling. No partial-JSON guards.
    var result = new IronTesseract().Read(filePath);
    return result.Text;
}
// IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

public string ReadDocument(string filePath)
{
    // No retry loop. No CancellationTokenSource. No HTTP status checks.
    // No rate limit handling. No partial-JSON guards.
    var result = new IronTesseract().Read(filePath);
    return result.Text;
}
Imports IronOcr

' IronOCR: no network, no retry policy needed — local call either works or throws once
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Public Function ReadDocument(filePath As String) As String
    ' No retry loop. No CancellationTokenSource. No HTTP status checks.
    ' No rate limit handling. No partial-JSON guards.
    Dim result = New IronTesseract().Read(filePath)
    Return result.Text
End Function
$vbLabelText   $csharpLabel

Toda la infraestructura de reintento — el bucle, el cálculo del retraso, el CancellationTokenSource, la bifurcación del código de estado HTTP, el bloque de captura TaskCanceledException — existe únicamente debido a la red. Elimina la llamada de red y todo desaparecerá. Una llamada OCR local falla rápidamente con una excepción tipificada si el archivo de entrada no existe o es ilegible, y se ejecuta correctamente en caso contrario. La guía de optimización de velocidad cubre el ajuste del rendimiento de IronOCR si el rendimiento es un factor importante después de la migración.

Procesamiento de archivos PDF de varias páginas sin carga en la nube

Klippa acepta cargas de PDF a través del mismo punto de acceso parseDocument. Los archivos PDF de varias páginas siguen saliendo de tu red.IronOCR lee archivos PDF de forma nativa, durante el proceso de procesamiento, con acceso a los resultados página por página.

Enfoque de Klippa:

// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
    var pages = new List<string>();

    // Klippa parses the entire PDF server-side and returns combined results
    // You cannot control per-page processing or access raw page text
    using var form = new MultipartFormDataContent();
    form.Add(
        new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
        "document",
        Path.GetFileName(pdfPath));

    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<KlippaResponse>(json);
    // Klippa returns the combined parsed text — no per-page breakdown in basic API
    pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);

    return pages;
}
// Klippa: PDF upload — entire document transmitted, results depend on cloud availability
public async Task<List<string>> ExtractPdfPagesAsync(string pdfPath)
{
    var pages = new List<string>();

    // Klippa parses the entire PDF server-side and returns combined results
    // You cannot control per-page processing or access raw page text
    using var form = new MultipartFormDataContent();
    form.Add(
        new ByteArrayContent(await File.ReadAllBytesAsync(pdfPath)),
        "document",
        Path.GetFileName(pdfPath));

    var response = await _httpClient.PostAsync(
        "https://custom-ocr.klippa.com/api/v1/parseDocument", form);
    response.EnsureSuccessStatusCode();

    var json = await response.Content.ReadAsStringAsync();
    var result = JsonSerializer.Deserialize<KlippaResponse>(json);
    // Klippa returns the combined parsed text — no per-page breakdown in basic API
    pages.Add(result?.Data?.ParsedDocument?.Text ?? string.Empty);

    return pages;
}
Imports System.IO
Imports System.Net.Http
Imports System.Text.Json
Imports System.Threading.Tasks

Public Class PdfExtractor
    Private ReadOnly _httpClient As HttpClient

    Public Sub New(httpClient As HttpClient)
        _httpClient = httpClient
    End Sub

    ' Klippa: PDF upload — entire document transmitted, results depend on cloud availability
    Public Async Function ExtractPdfPagesAsync(pdfPath As String) As Task(Of List(Of String))
        Dim pages As New List(Of String)()

        ' Klippa parses the entire PDF server-side and returns combined results
        ' You cannot control per-page processing or access raw page text
        Using form As New MultipartFormDataContent()
            form.Add(New ByteArrayContent(Await File.ReadAllBytesAsync(pdfPath)), "document", Path.GetFileName(pdfPath))

            Dim response = Await _httpClient.PostAsync("https://custom-ocr.klippa.com/api/v1/parseDocument", form)
            response.EnsureSuccessStatusCode()

            Dim json = Await response.Content.ReadAsStringAsync()
            Dim result = JsonSerializer.Deserialize(Of KlippaResponse)(json)
            ' Klippa returns the combined parsed text — no per-page breakdown in basic API
            pages.Add(If(result?.Data?.ParsedDocument?.Text, String.Empty))
        End Using

        Return pages
    End Function
End Class

Public Class KlippaResponse
    Public Property Data As KlippaData
End Class

Public Class KlippaData
    Public Property ParsedDocument As ParsedDocument
End Class

Public Class ParsedDocument
    Public Property Text As String
End Class
$vbLabelText   $csharpLabel

Enfoque IronOCR:

// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
    Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
    Console.WriteLine(page.Text);
}

// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
// IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadPdf("multi-page-invoice.pdf"); // reads locally — no HTTP

var ocr = new IronTesseract();
var result = ocr.Read(input);

// Per-page access — not available from Klippa's combined response
foreach (var page in result.Pages)
{
    Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines");
    Console.WriteLine(page.Text);
}

// Or produce a searchable PDF from the scanned original
result.SaveAsSearchablePdf("searchable-output.pdf");
Imports IronOcr

' IronOCR: native PDF OCR with per-page structured access — no upload
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Using input As New OcrInput()
    input.LoadPdf("multi-page-invoice.pdf") ' reads locally — no HTTP

    Dim ocr As New IronTesseract()
    Dim result = ocr.Read(input)

    ' Per-page access — not available from Klippa's combined response
    For Each page In result.Pages
        Console.WriteLine($"Page {page.PageNumber}: {page.Lines.Count()} lines")
        Console.WriteLine(page.Text)
    Next

    ' Or produce a searchable PDF from the scanned original
    result.SaveAsSearchablePdf("searchable-output.pdf")
End Using
$vbLabelText   $csharpLabel

IronOCR lee archivos PDF de forma nativa sin necesidad de conversión. Cada página es accesible individualmente, conservando su jerarquía completa de líneas, palabras y caracteres. La llamada SaveAsSearchablePdf() produce un PDF con capa de texto a partir de un documento escaneado — una capacidad que Klippa no ofrece. La guía de entrada de PDF abarca las opciones de carga, y la guía de PDF con función de búsqueda abarca las opciones de salida, incluido el formato PDF/A para el cumplimiento de los requisitos de archivo.

Referencia de mapeo de la API de OCR de Klippa a IronOCR

Klippa es una API REST, no un SDK tipado. El siguiente diagrama traduce la superficie de integración de Klippa a sus equivalentes en IronOCR.

Concepto Klippa Equivalente a IronOCR
HttpClient con encabezado X-Auth-Key Instancia IronTesseract — sin configuración de autenticación
MultipartFormDataContent OcrInput.LoadImage(path) o OcrInput.LoadPdf(path)
POST /api/v1/parseDocument IronTesseract.Read(input)
await _client.PostAsync(...) ocr.Read(input) — sincronización, no se necesita await
response.EnsureSuccessStatusCode() No es necesario: no hay respuesta HTTP.
JsonSerializer.Deserialize<KlippaResponse>(json) Objeto tipado OcrResult — sin deserialización
KlippaResponse.Data.ParsedDocument.Text OcrResult.Text
KlippaResponse.Data.ParsedDocument.Amount Regex personalizado en OcrResult.Text o OcrResult.Lines
KlippaResponse.Data.ParsedDocument.Merchant OcrResult.Pages[0].Lines[0].Text
Bucle de reintento con Task.Delay No es necesario: no hay modo de fallo de red.
CancellationTokenSource(TimeSpan.FromSeconds(30)) No es necesario: ejecución local
Gestión de límites de velocidad (HTTP 429) No es necesario: no hay límites de velocidad.
Enrutamiento de documentos en la nube a servidores de la UE Ejecución local en proceso
KlippaService.Dispose() / HttpClient.Dispose() Eliminación OcrInput vía declaración using
Campos de respuesta JSON estructurados OcrResult.Text + OcrResult.Pages + OcrResult.Words
Suscripción a la API SaaS Cadena IronOcr.License.LicenseKey — perpetua

Problemas comunes de migración y soluciones

Problema 1: Sitios de llamadas solo asíncronas después de la eliminación de HTTP

Klippa: Toda la integración con Klippa es asíncrona porque las llamadas HTTP lo requieren. Controladores, servicios y trabajadores en segundo plano en toda tu base de código llaman a await ProcessDocumentAsync(...). Eliminar la llamada HTTP significa que ya no se necesita await, pero las firmas de los métodos async permanecen.

Solución:IronOCR proporciona APIs tanto síncronas como asíncronas. Para los sitios de llamada que deben permanecer asíncronos (controladores de ASP.NET Core, servicios en segundo plano con CancellationToken), utiliza ReadAsync:

// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
    string filePath, CancellationToken cancellationToken = default)
{
    // Previously: await _httpClient.PostAsync(...)
    // Now: local call, same awaitable pattern
    var ocr = new IronTesseract();
    var result = await ocr.ReadAsync(filePath);
    return result.Text;
}
// Keep async method signatures — switch the implementation
public async Task<string> ProcessDocumentAsync(
    string filePath, CancellationToken cancellationToken = default)
{
    // Previously: await _httpClient.PostAsync(...)
    // Now: local call, same awaitable pattern
    var ocr = new IronTesseract();
    var result = await ocr.ReadAsync(filePath);
    return result.Text;
}
Imports System.Threading
Imports System.Threading.Tasks

Public Class DocumentProcessor
    Public Async Function ProcessDocumentAsync(filePath As String, Optional cancellationToken As CancellationToken = Nothing) As Task(Of String)
        Dim ocr As New IronTesseract()
        Dim result = Await ocr.ReadAsync(filePath)
        Return result.Text
    End Function
End Class
$vbLabelText   $csharpLabel

La guía de OCR asíncrono cubre ReadAsync y CancellationToken integración para patrones de ASP.NET Core y servicios alojados.

Problema 2: Registro de inyección de dependencias

Klippa: La clase KlippaService se registra en DI como un servicio singleton o de ámbito y envuelve HttpClient. Eliminarlo implica actualizar el registro DI y todos los puntos de inyección.

Solución: Registra IronTesseract como un singleton (es seguro para hilos) e inyecta directamente, o crea un envoltorio delgado que refleje tu interfaz de servicio existente:

// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();

// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();

public class IronOcrService : IOcrService
{
    private readonly IronTesseract _ocr;
    public IronOcrService(IronTesseract ocr) => _ocr = ocr;

    public string ReadDocument(string path) => _ocr.Read(path).Text;
}
// In Program.cs or Startup.cs
builder.Services.AddSingleton<IronTesseract>();

// Or wrap for interface compatibility
builder.Services.AddSingleton<IOcrService, IronOcrService>();

public class IronOcrService : IOcrService
{
    private readonly IronTesseract _ocr;
    public IronOcrService(IronTesseract ocr) => _ocr = ocr;

    public string ReadDocument(string path) => _ocr.Read(path).Text;
}
Imports Microsoft.Extensions.DependencyInjection

' In Program.vb or Startup.vb
builder.Services.AddSingleton(Of IronTesseract)()

' Or wrap for interface compatibility
builder.Services.AddSingleton(Of IOcrService, IronOcrService)()

Public Class IronOcrService
    Implements IOcrService

    Private ReadOnly _ocr As IronTesseract

    Public Sub New(ocr As IronTesseract)
        _ocr = ocr
    End Sub

    Public Function ReadDocument(path As String) As String Implements IOcrService.ReadDocument
        Return _ocr.Read(path).Text
    End Function
End Class
$vbLabelText   $csharpLabel

Una instancia IronTesseract registrada como un singleton maneja solicitudes concurrentes. Cada llamada a Read() es segura para hilos.

Problema 3: Extracción de campos estructurados sin JSON preanalizado

Klippa: Klippa devuelve amount, merchant, date y vat_amount como propiedades JSON tipadas. Migrar a IronOCR implica que esos campos ya no llegan preanalizados.

Solución: OcrResult de IronOCR proporciona el texto en bruto y las coordenadas a nivel de palabra para construir una extracción equivalente. Para documentos con diseños predecibles, el OCR basado en regiones se dirige directamente a campos específicos:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion   = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60);  // top header area

using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();

using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Target specific layout regions instead of relying on pre-parsed cloud fields
var totalRegion   = new CropRectangle(350, 580, 250, 50); // bottom-right total area
var merchantRegion = new CropRectangle(50, 30, 400, 60);  // top header area

using var merchantInput = new OcrInput();
merchantInput.LoadImage("receipt.jpg", merchantRegion);
var merchantName = new IronTesseract().Read(merchantInput).Text.Trim();

using var totalInput = new OcrInput();
totalInput.LoadImage("receipt.jpg", totalRegion);
var totalText = new IronTesseract().Read(totalInput).Text.Trim();
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

' Target specific layout regions instead of relying on pre-parsed cloud fields
Dim totalRegion As New CropRectangle(350, 580, 250, 50) ' bottom-right total area
Dim merchantRegion As New CropRectangle(50, 30, 400, 60) ' top header area

Using merchantInput As New OcrInput()
    merchantInput.LoadImage("receipt.jpg", merchantRegion)
    Dim merchantName As String = New IronTesseract().Read(merchantInput).Text.Trim()
End Using

Using totalInput As New OcrInput()
    totalInput.LoadImage("receipt.jpg", totalRegion)
    Dim totalText As String = New IronTesseract().Read(totalInput).Text.Trim()
End Using
$vbLabelText   $csharpLabel

La guía de OCR basada en regiones cubre el uso de CropRectangle en detalle. Para obtener patrones de extracción completos en todos los formatos de recibos y facturas, el tutorial de escaneo de recibos proporciona un código completamente funcional.

Problema 4: Documentos que llegan como flujos desde servicios ascendentes

Klippa: Klippa recibe documentos como cargas de formularios multipart (bytes de archivo envueltos en el contenido de un formulario HTTP). Si su aplicación recibe documentos como flujos desde S3, Azure Blob Storage o API internas, estaba leyendo el flujo en bytes y luego subiendo esos bytes a Klippa.

Solución:IronOCR acepta objetos Stream directamente. El paso de conversión de bytes desaparece:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
    using var input = new OcrInput();
    input.LoadImage(documentStream); // accepts Stream directly

    var ocr = new IronTesseract();
    var result = ocr.Read(input);
    return result.Text;
}
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

// Stream from S3, Azure Blob, or any upstream source
public async Task<string> ProcessDocumentStreamAsync(Stream documentStream)
{
    using var input = new OcrInput();
    input.LoadImage(documentStream); // accepts Stream directly

    var ocr = new IronTesseract();
    var result = ocr.Read(input);
    return result.Text;
}
Imports System.IO
Imports System.Threading.Tasks
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Public Async Function ProcessDocumentStreamAsync(documentStream As Stream) As Task(Of String)
    Using input As New OcrInput()
        input.LoadImage(documentStream) ' accepts Stream directly

        Dim ocr As New IronTesseract()
        Dim result = ocr.Read(input)
        Return result.Text
    End Using
End Function
$vbLabelText   $csharpLabel

No ReadAllBytes, no construcción MultipartFormDataContent, no HTTP POST. El flujo va directamente a OcrInput. La guía de entrada de arroyos abarca los tipos de arroyos y los patrones de eliminación.

Problema 5: Pruebas de integración que dependen de la simulación HTTP

Klippa: Las pruebas de integración para el código de Klippa simulan HttpClient o usan interceptores HTTP (por ejemplo, WireMock, MockHttp) para simular respuestas de API. Esas pruebas simulan la capa HTTP, no la lógica del OCR.

Solución: Las pruebas de IronOCR utilizan documentos reales con un resultado esperado conocido. No se necesita infraestructura de burla. Las pruebas se ejecutan sin conexión:

[Fact]
public void ReadDocument_ReturnsExpectedText()
{
    IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
    var ocr = new IronTesseract();

    // Use a real test fixture — no HTTP mocking, runs fully offline
    var result = ocr.Read("test-fixtures/sample-invoice.jpg");

    Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
    Assert.True(result.Confidence > 70);
}
[Fact]
public void ReadDocument_ReturnsExpectedText()
{
    IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";
    var ocr = new IronTesseract();

    // Use a real test fixture — no HTTP mocking, runs fully offline
    var result = ocr.Read("test-fixtures/sample-invoice.jpg");

    Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase);
    Assert.True(result.Confidence > 70);
}
<Fact>
Public Sub ReadDocument_ReturnsExpectedText()
    IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"
    Dim ocr = New IronTesseract()

    ' Use a real test fixture — no HTTP mocking, runs fully offline
    Dim result = ocr.Read("test-fixtures/sample-invoice.jpg")

    Assert.Contains("Invoice", result.Text, StringComparison.OrdinalIgnoreCase)
    Assert.True(result.Confidence > 70)
End Sub
$vbLabelText   $csharpLabel

Las pruebas que antes requerían una conexión Klippa en tiempo real o una configuración simulada HTTP compleja ahora se ejecutan en CI sin acceso a la red.

Problema 6: Documentos de baja calidad que Klippa mejoró en el servidor.

Klippa: El procesamiento en la nube aplica mejoras a la imagen antes del reconocimiento. Los desarrolladores nunca configuran esto; sucede automáticamente en los servidores de Klippa. Al migrar, los documentos que Klippa procesó de forma silenciosa pueden presentar una menor precisión sin un preprocesamiento explícito en IronOCR.

Solución: Aplicar explícitamente los filtros de preprocesamiento de IronOCR. El conjunto de filtros refleja lo que aplican los servicios en la nube en el servidor:

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();    // fix rotation from camera or scanner
input.DeNoise();   // remove compression noise
input.Contrast();  // boost faded ink
input.Binarize();  // clean background for clearer character edges

var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY";

using var input = new OcrInput();
input.LoadImage("low-quality-scan.jpg");
input.Deskew();    // fix rotation from camera or scanner
input.DeNoise();   // remove compression noise
input.Contrast();  // boost faded ink
input.Binarize();  // clean background for clearer character edges

var result = new IronTesseract().Read(input);
Console.WriteLine($"Confidence: {result.Confidence}%");
Imports IronOcr

IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY"

Using input As New OcrInput()
    input.LoadImage("low-quality-scan.jpg")
    input.Deskew()    ' fix rotation from camera or scanner
    input.DeNoise()   ' remove compression noise
    input.Contrast()  ' boost faded ink
    input.Binarize()  ' clean background for clearer character edges

    Dim result = New IronTesseract().Read(input)
    Console.WriteLine($"Confidence: {result.Confidence}%")
End Using
$vbLabelText   $csharpLabel

La guía de corrección de la calidad de imagen abarca todos los filtros de preprocesamiento y el orden en que deben aplicarse para los diferentes tipos de degradación del documento.

Lista de verificación de migración de Klippa OCR

Pre-Migración

Antes de eliminar cualquier código, revise su base de código para localizar todo el código específico de Klippa:

# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .

# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .

# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .

# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .

# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
# Find all files containing Klippa HTTP integration code
grep -r "X-Auth-Key" --include="*.cs" .
grep -r "klippa.com" --include="*.cs" .
grep -r "KlippaService\|KlippaResponse\|KlippaResult\|KlippaData" --include="*.cs" .

# Find all files with MultipartFormDataContent (likely Klippa upload code)
grep -r "MultipartFormDataContent" --include="*.cs" .

# Find all JSON deserialization models that map to Klippa response fields
grep -r "parsed_document\|vat_amount\|merchant\|X-Auth-Key" --include="*.cs" .

# Find all async methods that wrap Klippa calls
grep -r "ParseDocumentAsync\|ProcessReceiptAsync\|UploadAndParseAsync" --include="*.cs" .

# Find test files with HTTP mocks for Klippa
grep -r "MockHttp\|WireMock\|klippa" --include="*.cs" .
SHELL

Notas del inventario:

  • Registra cada clase que envoltorio HttpClient para llamadas a Klippa
  • Lista todos los modelos de clase de deserialización JSON (KlippaResponse, KlippaParsedDocument, etc.)
  • Documentar todas las asignaciones de campos que consumen las propiedades JSON preanalizadas de Klippa.
  • Tenga en cuenta cualquier política de reintento de Polly o bucles de reintento personalizados creados para Klippa.

Migración de código

  1. Instala el paquete NuGet IronOcr (dotnet add package IronOcr)
  2. Añade IronOcr.License.LicenseKey = "YOUR-LICENSE-KEY" al inicio de la aplicación
  3. Elimina System.Net.Http, System.Text.Json, Newtonsoft.Json importaciones de archivos de servicio de Klippa
  4. Elimina la clase KlippaService (o reemplaza su cuerpo con llamadas IronTesseract, manteniendo la interfaz)
  5. Registra IronTesseract como un singleton en el contenedor DI
  6. Reemplaza los bloques de carga MultipartFormDataContent con OcrInput.LoadImage() o OcrInput.LoadPdf()
  7. Elimina las clases de modelo de respuesta JSON (KlippaResponse, KlippaData, KlippaParsedDocument)
  8. Reemplaza las cadenas de navegación JSON anulables (.Data?.ParsedDocument?.Text) con result.Text
  9. Elimina los bucles de reintento y los tiempos de espera CancellationTokenSource de los sitios de llamada a Klippa
  10. Eliminar el manejo de límites de velocidad (bloqueos de captura HTTP 429)
  11. Reemplaza await ProcessDocumentAsync(...) con await ocr.ReadAsync(...) o ocr.Read(...) síncrono
  12. Añade filtros de preprocesamiento OcrInput (Deskew, DeNoise, Contrast) para entradas de documentos de baja calidad
  13. Reemplazar la infraestructura de pruebas simuladas HTTP con pruebas de documentación reales.
  14. Elimine las políticas de reintento de Polly o el middleware de reintento personalizado con ámbito limitado a las llamadas de Klippa.

Posmigración

  • Verificar que el resultado de la extracción de texto coincida con el contenido esperado de los documentos de prueba conocidos.
  • Confirmar que las puntuaciones de confianza superan el umbral aceptable (normalmente 70 % o más) para los tipos de documentos de producción.
  • Prueba de entrada PDF: carga PDFs de varias páginas nativamente y verifica el acceso a texto por página a través de result.Pages
  • Prueba de entrada de flujo: pasa MemoryStream y verifica que OcrInput.LoadImage(stream) produzca el resultado correcto
  • Verificar que los filtros de preprocesamiento mejoren la precisión en escaneos de baja calidad en comparación con la línea base sin procesar.
  • Confirma que el singleton IronTesseract inyectado por DI maneja solicitudes concurrentes sin contención
  • Ejecutar las pruebas de integración sin conexión (sin conexión a la red): todas las pruebas deberían pasar sin acceso a la nube.
  • Verifica la salida de PDF buscable con result.SaveAsSearchablePdf("output.pdf") para flujos de documentos escaneados
  • Prueba ReadAsync en contexto de controlador ASP.NET Core con propagación CancellationToken
  • Confirma que el patrón de eliminación using var input = new OcrInput() no filtra memoria bajo carga sostenida

Principales ventajas de migrar a IronOCR

Soberanía de datos desde el primer día. Tras la migración, los documentos financieros confidenciales, los escaneos de identidad y las facturas confidenciales nunca salen de su infraestructura. En el alcance de la auditoría no hay ningún procesador externo, ninguna política de retención de datos que revisar y ningún acuerdo de transferencia de datos que mantener. Las restricciones de HIPAA, ITAR, CMMC y FedRAMP que anteriormente hacían que Klippa fuera problemático se cumplen por defecto. El despliegue en Docker , AWS o Azure mantiene todo dentro de los límites de su propia infraestructura.

Se eliminó la complejidad de la infraestructura. La clase de servicio, el cliente HTTP, el código de carga de formularios, los modelos JSON, la política de reintentos, la configuración de tiempo de espera: todo existía para encapsular una llamada de red. Si se elimina la llamada de red, todo lo demás desaparece. El código resultante es más pequeño, más fácil de leer y tiene menos posibilidades de fallo. Una sola instancia de IronTesseract inyectada a través de DI reemplaza toda la capa de integración HTTP.

Costo predecible sin importar el volumen. Una licencia perpetua de IronOCR en $999 (Lite), $1,499 (Professional), o $2,999 (Enterprise) cubre procesamiento ilimitado de documentos. Procesar 500 documentos al mes o 500.000 al mes cuesta lo mismo. El sistema de facturación por documento que encarecía Klippa a gran escala está estructuralmente ausente. La página de licencias de IronOCR detalla todos los niveles y lo que incluye cada uno.

Alcance del documento sin límites.IronOCR procesa cualquier documento que contenga texto. Contratos escaneados, dibujos técnicos, formularios médicos, órdenes de compra, notas manuscritas, capturas de pantalla, archivos TIFF — todo manejado por la misma llamada Read() con la misma API. La restricción de ámbito especializado que requería un segundo sistema para los documentos que quedaban fuera de las categorías para las que Klippa estaba capacitado ha desaparecido. Una biblioteca, un punto de integración, cualquier tipo de documento.

Ahora se admiten entornos de red sin conexión y con acceso restringido. Las aplicaciones implementadas en redes bancarias, sistemas gubernamentales, entornos periféricos o cualquier infraestructura con acceso de salida restringido funcionan exactamente igual que en entornos abiertos. No se realiza ninguna comprobación de conectividad, ni ninguna prueba de estado a un punto final en la nube, ni se activa un modo degradado cuando no hay conexión a Internet. Las implementaciones aisladas de la red funcionan sin modificaciones. La guía de implementación de Linux y la guía de implementación de Docker cubren las rutas de implementación en contenedores y del lado del servidor para estos entornos.

Control total sobre la mejora de la imagen. El preprocesamiento en la nube era una caja negra: Klippa lo aplicaba, usted observaba los resultados y no tenía parámetros que ajustar. El pipeline de preprocesamiento de IronOCR es explícito y componible: Deskew(), DeNoise(), Contrast(), Binarize(), Sharpen(), Scale(), Dilate(), DeepCleanBackgroundNoise(). Cada filtro es opcional y debe pedirse por separado. Las mejoras en la precisión son medibles, reproducibles y están bajo su control. La guía de corrección de la calidad de imagen y la página de funciones de preprocesamiento cubren el catálogo completo de filtros, con indicaciones sobre cuándo aplicar cada uno.

Por favor notaKlippa y Tesseract son marcas registradas de sus respectivos propietarios. Este sitio no está afiliado, respaldado ni patrocinado por Google o Klippa. Todos los nombres de producto, 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 Klippa 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 Klippa OCR API a IronOCR?

Sustituya las secuencias de inicialización de Klippa 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 ofrece la misma precisión de OCR que la API de OCR de Klippa 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 gestiona IronOCR los datos de idioma que la API OCR de Klippa 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.

¿Requiere la migración de Klippa OCR API a IronOCR cambios en la infraestructura de despliegue?

IronOCR requiere menos cambios de infraestructura que Klippa 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 Klippa?

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 la API OCR de Klippa 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 Klippa 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