Volver al blog
Guia para desarrolladores

Cómo integrar el SDK de ChequeUI en su aplicación móvil

Guía paso a paso para integrar el SDK de ChequeUI en iOS y Android con captura de cheques, OCR, autenticación, validación y pruebas de producción.

Publicado22 min de lecturaChequedb Team

Paso a paso: cómo integrar el SDK de ChequeUI en su aplicación móvil

Una guía completa para desarrolladores de iOS y Android que implementan la captura de cheques y las capacidades OCR


1. Introducción

El SDK móvil de ChequeUI permite a los desarrolladores integrar potentes capacidades de procesamiento de cheques directamente en sus aplicaciones móviles. Ya sea que esté creando una aplicación bancaria, una solución de procesamiento de pagos o una herramienta de contabilidad, el SDK proporciona todo lo que necesita para capturar, validar y extraer datos de cheques con precisión de nivel empresarial.

Resumen de capacidades del SDK

El SDK de ChequeUI ofrece un conjunto completo de funciones de procesamiento de cheques:

  • Captura inteligente: Detección automática y captura de bordes del cheque con detección de bordes en tiempo real
  • Extracción de datos OCR: reconocimiento basado en IA de todos los campos del cheque, incluidos detalles bancarios, información del beneficiario, fechas y montos.
  • Verificación de montos: validación cruzada entre montos escritos y numéricos para detectar discrepancias
  • Detección de firma: verificación de presencia de firma basada en visión por computadora
  • Procesamiento por lotes: maneje múltiples cheques de manera eficiente para operaciones masivas
  • Soporte sin conexión: Procese cheques sin conectividad de red con sincronización automática
  • Diseño que prioriza la seguridad: cifrado de extremo a extremo y manejo seguro de tokens
  • UI personalizable: tema la interfaz de captura para que coincida con la identidad de su marca

¿Por qué elegir SDK de ChequeUI?

A diferencia de las soluciones genéricas OCR, ChequeUI está diseñada específicamente para documentos financieros:

CaracterísticaGenérico OCRSDK de ChequeUI
Precisión de campo70-80%95%+
Validación de importemanualesAutomático
Detección de bordesBásicoAvanzado con corrección de perspectiva
Reconocimiento BancarioNingunoBase de datos integrada de más de 10.000 bancos
CumplimientoAutogestionadoSOC 2, PCI DSS listo
Tiempo de integración2-4 semanas2-3 días

2. Requisitos previos

Antes de integrar SDK de ChequeUI, asegúrese de tener implementados los siguientes requisitos previos.

Requisitos del sistema

iOS

  • Versión mínima: iOS 13.0+
  • Xcode: Versión 14.0 o posterior
  • Swift: Versión 5.5 o posterior
  • Capacidades del dispositivo: Cámara con enfoque automático, se recomienda un mínimo de 8 MP

androide

  • Nivel mínimo de API: API 24 (Android 7.0)
  • Nivel API objetivo: se recomienda API 34 (Android 14)
  • Kotlin: Versión 1.8.0 o posterior
  • Android Studio: Hedgehog (2023.1.1) o posterior
  • Capacidades del dispositivo: compatibilidad con Camera2 API, se recomienda un mínimo de 8 MP

Credenciales de API

Para utilizar el SDK de ChequeUI, necesita credenciales de API válidas:

  1. Regístrese en https://chequedb.com/developer
  2. Crea un nuevo proyecto en el panel de desarrollador
  3. Genere claves API para sus plataformas de destino
  4. Configurar ID de paquetes permitidos (iOS) y nombres de paquetes (Android)

Sus credenciales incluirán:

CHEQUEUI_API_KEY=your_api_key_here
CHEQUEUI_SECRET_KEY=your_secret_key_here
CHEQUEUI_ENVIRONMENT=sandbox  

# or 'production'

Nota de seguridad: Nunca envíe su SECRET_KEY al control de versiones. Utilice variables de entorno o gestión secreta segura.

Configuración del entorno de desarrollo

Configuración de iOS

  1. Asegúrese de tener instalado CocoaPods o Swift Package Manager
  2. Verifique que su perfil de aprovisionamiento incluya permisos de cámara
  3. Agregue lo siguiente a su Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to capture cheques for processing.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app accesses photos to upload cheque images.</string>

Configuración de Android

  1. Asegúrese de que su AndroidManifest.xml incluya los permisos necesarios:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" />
  1. Para Android 6.0+, implemente solicitudes de permiso de tiempo de ejecución para el permiso CAMERA.

3. Instalación

Instalación de iOS

Usando CocoaPods

Agregue lo siguiente a su Podfile:

platform :ios, '13.0'
use_frameworks!

target 'YourApp' do
  pod 'ChequeUI', '~> 2.5.0'
end

Luego ejecuta:

pod install

Usando el Administrador de paquetes Swift

En Xcode, vaya a Archivo → Agregar dependencias del paquete e ingrese:

https://github.com/chequedb/chequeui-ios-sdk.git

Seleccione la versión 2.5.0 o especifique su regla de versión:

// Package.swift dependencies
dependencies: [
    .package(url: "https://github.com/chequedb/chequeui-ios-sdk.git", from: "2.5.0")
]

Instalación de Android

Usando Gradle (build.gradle.kts)

Agregue el repositorio y la dependencia ChequeUI:

// settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://chequedb.jfrog.io/artifactory/chequeui-gradle") }
    }
}

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://chequedb.jfrog.io/artifactory/chequeui-gradle") }
    }
}
// app/build.gradle.kts
dependencies {
    implementation("com.chequedb:chequeui-sdk:2.5.0")
    implementation("com.chequedb:chequeui-camera:2.5.0")
    implementation("com.chequedb:chequeui-mlkit:2.5.0")
}

Usando Maven

<repositories>
    <repository>
        <id>chequeui</id>
        <url>https://chequedb.jfrog.io/artifactory/chequeui-maven</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.chequedb</groupId>
        <artifactId>chequeui-sdk</artifactId>
        <version>2.5.0</version>
    </dependency>
</dependencies>

Gestión de versiones

Recomendamos utilizar una definición de versión centralizada:

iOS (archivo Pod)

chequeui_version = '2.5.0'
pod 'ChequeUI', "~> #{chequeui_version}"

Android (gradle/libs.versions.toml)

[versions]
chequeui = "2.5.0"

[libraries]
chequeui-sdk = { module = "com.chequedb:chequeui-sdk", version.ref = "chequeui" }

4. Configuración de autenticación

Configuración de clave API

iOS (rápido)

import ChequeUI

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        
        let config = ChequeUIConfiguration(
            apiKey: "your_api_key_here",
            environment: .sandbox, // or .production
            region: .usEast
        )
        
        ChequeUI.initialize(with: config)
        return true
    }
}

Android (Kotlin)

import com.chequedb.chequeui.ChequeUI
import com.chequedb.chequeui.config.ChequeUIConfiguration

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        val config = ChequeUIConfiguration.Builder()
            .apiKey("your_api_key_here")
            .environment(Environment.SANDBOX) // or Environment.PRODUCTION
            .region(Region.US_EAST)
            .build()
        
        ChequeUI.initialize(this, config)
    }
}

Actualización de token

El SDK maneja automáticamente la actualización del token. Sin embargo, puede implementar un manejo de token personalizado para casos de uso avanzados:

iOS

ChequeUI.shared.setTokenProvider { completion in
    // Your custom token fetching logic
    fetchFreshToken { token, error in
        completion(token, error)
    }
}

androide

ChequeUI.setTokenProvider(object : TokenProvider {
    override suspend fun getToken(): String {
        // Your custom token fetching logic
        return fetchFreshToken()
    }
    
    override suspend fun refreshToken(): String {
        return refreshAccessToken()
    }
})

Mejores prácticas de seguridad

  1. Separación de entornos: nunca utilice credenciales de producción en desarrollo
  2. Almacenamiento de claves: use el llavero de iOS o el almacén de claves de Android para valores confidenciales
  3. Fijación de certificados: habilitar para entornos de producción
  4. Ofuscación: habilite ProGuard/R8 en Android para proteger los componentes internos de SDK
// iOS - Enable certificate pinning
let config = ChequeUIConfiguration(
    apiKey: apiKey,
    environment: .production,
    certificatePinning: true,
    pinnedCertificates: ["sha256/..."]
)
// Android - Enable certificate pinning
val config = ChequeUIConfiguration.Builder()
    .apiKey(apiKey)
    .environment(Environment.PRODUCTION)
    .certificatePinning(true)
    .addPinnedCertificate("sha256/...")
    .build()

5. Integración básica

SDK Inicialización

Una inicialización adecuada garantiza que el SDK esté listo antes de cualquier operación de verificación:

iOS

import ChequeUI

class ChequeService {
    static let shared = ChequeService()
    
    func initializeSDK() {
        guard ChequeUI.isInitialized else {
            print("SDK not initialized")
            return
        }
        
        // Configure logging
        ChequeUI.shared.logLevel = .debug
        
        // Set default options
        ChequeUI.shared.defaultCaptureOptions = CaptureOptions(
            autoCapture: true,
            guidanceMode: .enhanced,
            compressionQuality: 0.9
        )
    }
}

androide

import com.chequedb.chequeui.ChequeUI
import com.chequedb.chequeui.config.CaptureOptions

class ChequeService(private val context: Context) {
    
    fun initializeSDK() {
        if (!ChequeUI.isInitialized()) {
            throw IllegalStateException("SDK not initialized")
        }
        
        // Configure logging
        ChequeUI.setLogLevel(LogLevel.DEBUG)
        
        // Set default options
        val defaultOptions = CaptureOptions.Builder()
            .autoCapture(true)
            .guidanceMode(GuidanceMode.ENHANCED)
            .compressionQuality(0.9f)
            .build()
        
        ChequeUI.setDefaultCaptureOptions(defaultOptions)
    }
}

Opciones de configuración

OpciónTipoPredeterminadoDescripción
autoCaptureBooleanotrueCaptura automáticamente cuando se detectan bordes
guidanceModeEnumeración.standardComplejidad de superposición de guía visual
compressionQualityFlotador0.9Compresión JPEG (0,0-1,0)
maxImageDimensioninternacional2048Ancho/alto máximo en píxeles
enableFlashbooleanotruePermitir alternancia de flash en la interfaz de usuario
captureSoundbooleanotrueReproducir sonido del obturador al capturar

Configuración de manejo de errores

Configure el manejo de errores globales para detectar problemas de nivel SDK:

iOS

ChequeUI.shared.errorHandler = { error in
    switch error {
    case .unauthorized:
        // Handle authentication failure
        self.refreshCredentials()
    case .networkError(let underlying):
        // Handle connectivity issues
        self.showOfflineMode()
    case .rateLimitExceeded:
        // Implement exponential backoff
        self.scheduleRetry()
    default:
        print("ChequeUI Error: \(error.localizedDescription)")
    }
}

androide

ChequeUI.setErrorHandler { error ->
    when (error) {
        is ChequeUIError.Unauthorized -> refreshCredentials()
        is ChequeUIError.NetworkError -> showOfflineMode()
        is ChequeUIError.RateLimitExceeded -> scheduleRetry()
        else -> Log.e("ChequeUI", "Error: ${error.message}")
    }
}

6. Verificar captura

Integración de cámara

El SDK proporciona un controlador de vista/actividad de cámara listo para usar con funciones avanzadas.

iOS

import ChequeUI
import UIKit

class CaptureViewController: UIViewController {
    
    func presentChequeCapture() {
        let captureVC = ChequeCaptureViewController()
        captureVC.delegate = self
        captureVC.captureOptions = CaptureOptions(
            autoCapture: true,
            guidanceMode: .enhanced,
            showFlashToggle: true
        )
        
        present(captureVC, animated: true)
    }
}

extension CaptureViewController: ChequeCaptureDelegate {
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didCapture image: UIImage,
        withMetadata metadata: CaptureMetadata
    ) {
        // Handle captured image
        print("Capture quality score: \(metadata.qualityScore)")
        print("Edge detection confidence: \(metadata.edgeConfidence)")
        
        // Dismiss and process
        controller.dismiss(animated: true) {
            self.processCapturedCheque(image)
        }
    }
    
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didFailWithError error: ChequeUIError
    ) {
        controller.dismiss(animated: true)
        showError(error)
    }
}

androide

import com.chequedb.chequeui.capture.ChequeCaptureActivity
import com.chequedb.chequeui.capture.CaptureContract

class MainActivity : AppCompatActivity() {
    
    private val captureLauncher = registerForActivityResult(
        CaptureContract()
    ) { result ->
        when (result) {
            is CaptureResult.Success -> {
                val image = result.image
                val metadata = result.metadata
                
                Log.d("ChequeUI", "Quality score: ${metadata.qualityScore}")
                Log.d("ChequeUI", "Edge confidence: ${metadata.edgeConfidence}")
                
                processCapturedCheque(image)
            }
            is CaptureResult.Error -> {
                showError(result.error)
            }
            is CaptureResult.Cancelled -> {
                Log.d("ChequeUI", "User cancelled capture")
            }
        }
    }
    
    fun launchCapture() {
        val options = CaptureOptions.Builder()
            .autoCapture(true)
            .guidanceMode(GuidanceMode.ENHANCED)
            .showFlashToggle(true)
            .build()
        
        captureLauncher.launch(options)
    }
}

Captura automática frente a manual

El SDK admite modos de captura automática y manual:

// Auto-capture: Triggered when edges are stable and quality is good
let autoOptions = CaptureOptions(autoCapture: true, stabilityThreshold: 0.95)

// Manual capture: User taps shutter button
let manualOptions = CaptureOptions(autoCapture: false)
```Requisitos de captura automática:
- Confianza en la detección de bordes ≥ 85%
- Estabilidad de la imagen durante 1,5 segundos
- Se alcanzó el umbral mínimo de brillo
- No se detecta desenfoque de movimiento



## Validación de la calidad de la imagen

El SDK realiza controles de calidad en tiempo real:

```swift
// Quality thresholds
let options = CaptureOptions(
    minQualityScore: 0.75,        // Overall image quality
    minEdgeConfidence: 0.80,      // Edge detection confidence
    minBrightness: 0.3,           // Relative brightness (0-1)
    maxBlurScore: 0.4,            // Blur detection (lower is sharper)
    requireAllCorners: true       // All four corners must be visible
)

Detección de bordes

Detección avanzada de bordes con corrección de perspectiva:

// Configure edge detection sensitivity
val edgeOptions = EdgeDetectionOptions.Builder()
    .sensitivity(Sensitivity.HIGH)
    .perspectiveCorrection(true)
    .cornerRefinement(true)
    .build()

val captureOptions = CaptureOptions.Builder()
    .edgeDetection(edgeOptions)
    .build()

7. Extracción de datos

Procesamiento síncrono versus asíncrono

Elija el modo de procesamiento según su caso de uso:

Sincrónico (en tiempo real)

// iOS - Synchronous extraction
do {
    let result = try await ChequeUI.shared.extract(
        from: image,
        options: ExtractionOptions(syncMode: true)
    )
    updateUI(with: result)
} catch {
    handleExtractionError(error)
}
// Android - Synchronous extraction
lifecycleScope.launch {
    try {
        val result = ChequeUI.extract(
            image = image,
            options = ExtractionOptions(syncMode = true)
        )
        updateUI(result)
    } catch (e: ExtractionException) {
        handleExtractionError(e)
    }
}

Asíncrono (por lotes/en segundo plano)

// Submit for background processing
let jobId = try await ChequeUI.shared.submitExtraction(
    image: image,
    webhookUrl: "https://your-api.com/webhooks/cheque"
)

// Check status later
let status = try await ChequeUI.shared.checkExtractionStatus(jobId: jobId)

Mapeo de campos

Asigne campos extraídos a sus modelos de datos:

struct ChequeData: Codable {
    let bankName: String
    let branch: String
    let date: Date
    let payee: String
    let amountInWords: String
    let amountInNumerals: Decimal
    let accountNumber: String?
    let chequeNumber: String?
}

// Field mapping configuration
let mapping = FieldMapping()
    .map(.bankName, to: \.bankName)
    .map(.branch, to: \.branch)
    .map(.date, to: \.date, formatter: dateFormatter)
    .map(.amountInWords, to: \.amountInWords)
    .map(.amountInNumerals, to: \.amountInNumerals)

let result = try await ChequeUI.shared.extract(
    from: image,
    mapping: mapping
)

Umbrales de confianza

Establezca niveles mínimos de confianza para la extracción de campo:

let extractionOptions = ExtractionOptions(
    confidenceThresholds: [
        .bankName: 0.85,
        .amountInNumerals: 0.95,
        .amountInWords: 0.90,
        .date: 0.80,
        .payee: 0.75
    ],
    rejectOnLowConfidence: true
)

8. Manejo de errores

Errores de red

extension ChequeViewController {
    func handleNetworkError(_ error: ChequeUIError) {
        switch error {
        case .networkError(let urlError):
            switch urlError.code {
            case .notConnectedToInternet:
                enableOfflineMode()
            case .timedOut:
                showRetryDialog(message: "Connection timed out")
            default:
                showError("Network error: \(urlError.localizedDescription)")
            }
        default:
            break
        }
    }
}

Mala calidad de imagen

func handleQualityError(_ error: ChequeUIError) {
    switch error {
    case .poorImageQuality(let issues):
        var messages: [String] = []
        
        if issues.contains(.blurry) {
            messages.append("Image is blurry. Hold steady and retake.")
        }
        if issues.contains(.poorLighting) {
            messages.append("Insufficient lighting. Try using flash.")
        }
        if issues.contains(.edgesNotDetected) {
            messages.append("Cannot detect cheque edges. Place on dark background.")
        }
        if issues.contains(.glareDetected) {
            messages.append("Glare detected. Adjust angle to avoid reflections.")
        }
        
        showGuidance(messages: messages)
        
    default:
        break
    }
}

Cheques no válidos

func validateCheque(_ result: ExtractionResult) -> ValidationStatus {
    // Check amount matching
    guard result.amountsMatch else {
        return .invalid(reason: "Written and numeric amounts do not match")
    }
    
    // Check date validity
    guard result.date <= Date() else {
        return .invalid(reason: "Cheque date is in the future")
    }
    
    // Check expiry (typically 6 months)
    let sixMonthsAgo = Calendar.current.date(byAdding: .month, value: -6, to: Date())!
    guard result.date >= sixMonthsAgo else {
        return .invalid(reason: "Cheque may be stale-dated")
    }
    
    return .valid
}

Reintentar lógica

Implementar un retroceso exponencial para fallas transitorias:

class ChequeProcessor {
    private val maxRetries = 3
    private val baseDelay = 1000L // 1 second
    
    suspend fun processWithRetry(image: Bitmap): ExtractionResult {
        var lastError: Exception? = null
        
        repeat(maxRetries) { attempt ->
            try {
                return ChequeUI.extract(image)
            } catch (e: NetworkException) {
                lastError = e
                if (attempt < maxRetries - 1) {
                    val delay = baseDelay * (2.0.pow(attempt)).toLong()
                    delay(delay)
                }
            }
        }
        
        throw lastError ?: Exception("Extraction failed")
    }
}

9. Funciones avanzadas

Procesamiento por lotes

Procese múltiples cheques de manera eficiente:

// iOS Batch Processing
let batchRequest = BatchExtractionRequest(
    images: chequeImages,
    options: BatchOptions(
        parallelProcessing: true,
        maxConcurrent: 3,
        continueOnError: true
    )
)

let batchResult = try await ChequeUI.shared.extractBatch(request: batchRequest)

for (index, result) in batchResult.results.enumerated() {
    switch result {
    case .success(let extraction):
        print("Cheque \(index): Extracted \(extraction.payee)")
    case .failure(let error):
        print("Cheque \(index): Failed - \(error)")
    }
}
// Android Batch Processing
val batchRequest = BatchExtractionRequest(
    images = chequeImages,
    options = BatchOptions(
        parallelProcessing = true,
        maxConcurrent = 3,
        continueOnError = true
    )
)

ChequeUI.extractBatch(batchRequest)
    .onEach { progress ->
        updateProgress(progress.completed, progress.total)
    }
    .collect { result ->
        when (result) {
            is BatchResult.Success -> processExtraction(result.extraction)
            is BatchResult.Failure -> logFailedCheque(result.error)
        }
    }

Modo sin conexión

Habilite el procesamiento sin conexión con sincronización automática:

// Configure offline support
let config = ChequeUIConfiguration(
    apiKey: apiKey,
    environment: .production,
    offlineMode: .enabled(
        maxStorageMB: 100,
        autoSync: true,
        syncOnWifiOnly: false
    )
)

// Queue for offline processing
ChequeUI.shared.queueForOffline(image: image, metadata: metadata)

// Listen for sync events
ChequeUI.shared.offlineSyncDelegate = self

extension MyClass: OfflineSyncDelegate {
    func offlineSync(didCompleteSync results: [OfflineResult]) {
        for result in results {
            print("Synced cheque: \(result.chequeId)")
        }
    }
}

Temas de interfaz de usuario personalizados

Personalice la interfaz de captura para que coincida con su marca:

// iOS Theming
var theme = ChequeUITheme()
theme.primaryColor = UIColor.systemIndigo
theme.accentColor = UIColor.systemOrange
theme.backgroundColor = UIColor.systemBackground
theme.textColor = UIColor.label
theme.cornerRadius = 12.0
theme.font = UIFont.systemFont(ofSize: 16, weight: .medium)

// Overlay customization
theme.overlayStyle = .roundedRectangle(cornerRadius: 20)
theme.guideColor = UIColor.green.withAlphaComponent(0.7)
theme.guideWidth = 3.0

ChequeUI.shared.applyTheme(theme)
// Android Theming
val theme = ChequeUITheme.Builder()
    .primaryColor(ContextCompat.getColor(context, R.color.brand_primary))
    .accentColor(ContextCompat.getColor(context, R.color.brand_accent))
    .backgroundColor(ContextCompat.getColor(context, R.color.background))
    .textColor(ContextCompat.getColor(context, R.color.text_primary))
    .cornerRadius(12f)
    .fontFamily(R.font.brand_font)
    .overlayStyle(OverlayStyle.ROUNDED_RECTANGLE)
    .guideColor(ContextCompat.getColor(context, R.color.guide_green))
    .build()

ChequeUI.applyTheme(theme)

10. Pruebas

Pruebas unitarias

Pruebe su lógica de integración:

// iOS Unit Tests
import XCTest
@testable import ChequeUI

class ChequeServiceTests: XCTestCase {
    
    var chequeService: ChequeService!
    var mockChequeUI: MockChequeUI!
    
    override func setUp() {
        super.setUp()
        mockChequeUI = MockChequeUI()
        chequeService = ChequeService(chequeUI: mockChequeUI)
    }
    
    func testAmountValidation() async {
        // Given
        let extraction = ExtractionResult(
            amountInWords: "One Thousand Dollars",
            amountInNumerals: 1000.00,
            amountsMatch: true
        )
        mockChequeUI.mockResult = extraction
        
        // When
        let result = try? await chequeService.processCheque(image: testImage)
        
        // Then
        XCTAssertNotNil(result)
        XCTAssertEqual(result?.amount, 1000.00)
        XCTAssertTrue(result?.isValid ?? false)
    }
    
    func testAmountMismatchDetection() async {
        // Given
        let extraction = ExtractionResult(
            amountInWords: "One Thousand Dollars",
            amountInNumerals: 100.00,
            amountsMatch: false
        )
        mockChequeUI.mockResult = extraction
        
        // When
        let result = try? await chequeService.processCheque(image: testImage)
        
        // Then
        XCTAssertNotNil(result)
        XCTAssertFalse(result?.isValid ?? true)
    }
}
// Android Unit Tests
@RunWith(MockitoJUnitRunner::class)
class ChequeServiceTest {
    
    @Mock
    lateinit var mockChequeUI: ChequeUI
    
    lateinit var chequeService: ChequeService
    
    @Before
    fun setup() {
        chequeService = ChequeService(mockChequeUI)
    }
    
    @Test
    fun `amount validation succeeds when amounts match`() = runTest {
        // Given
        val extraction = ExtractionResult(
            amountInWords = "One Thousand Dollars",
            amountInNumerals = BigDecimal("1000.00"),
            amountsMatch = true
        )
        whenever(mockChequeUI.extract(any(), any())).thenReturn(extraction)
        
        // When
        val result = chequeService.processCheque(testImage)
        
        // Then
        assertTrue(result.isValid)
        assertEquals(BigDecimal("1000.00"), result.amount)
    }
}

Pruebas de integración

Pruebe los flujos de un extremo a otro:

// iOS UI Tests
class ChequeCaptureUITests: XCTestCase {
    
    var app: XCUIApplication!
    
    override func setUp() {
        super.setUp()
        app = XCUIApplication()
        app.launchArguments = ["--uitesting", "--mock-camera"]
        app.launch()
    }
    
    func testCompleteCaptureFlow() {
        // Tap capture button
        app.buttons["Capture Cheque"].tap()
        
        // Wait for camera
        XCTAssertTrue(app.otherElements["ChequeCameraView"].waitForExistence(timeout: 5))
        
        // Trigger mock capture
        app.buttons["MockCapture"].tap()
        
        // Verify results screen
        XCTAssertTrue(app.staticTexts["Extraction Results"].waitForExistence(timeout: 10))
        XCTAssertTrue(app.staticTexts["Bank Name"].exists)
    }
}

Respuestas simuladas

// Mock ChequeUI for testing
class MockChequeUI: ChequeUIProtocol {
    var mockResult: ExtractionResult?
    var mockError: Error?
    
    func extract(from image: UIImage, options: ExtractionOptions?) async throws -> ExtractionResult {
        if let error = mockError {
            throw error
        }
        return mockResult ?? ExtractionResult()
    }
}

11. Despliegue de producción

Ajuste de rendimiento

Optimice las cargas de trabajo de producción:

// iOS Performance Configuration
let perfConfig = PerformanceConfig(
    imageCompression: 0.85,      // Balance quality vs size
    maxImageDimension: 1920,     // Reduce processing time
    cacheSizeMB: 50,             // Enable response caching
    enableImagePreprocessing: true // Auto-enhance before OCR
)

ChequeUI.shared.configurePerformance(perfConfig)
// Android Performance Configuration
val perfConfig = PerformanceConfig.Builder()
    .imageCompression(0.85f)
    .maxImageDimension(1920)
    .cacheSizeMB(50)
    .enableImagePreprocessing(true)
    .bitmapPoolEnabled(true)     // Reuse bitmap allocations
    .build()

ChequeUI.configurePerformance(perfConfig)

Monitoreo

Integre con su plataforma de análisis:

// iOS Analytics Integration
ChequeUI.shared.analyticsDelegate = self

extension MyApp: ChequeUIAnalyticsDelegate {
    func chequeUI(didCaptureCheque event: CaptureEvent) {
        Analytics.track("cheque_captured", properties: [
            "quality_score": event.qualityScore,
            "processing_time_ms": event.processingTime,
            "auto_capture": event.usedAutoCapture
        ])
    }
    
    func chequeUI(didExtractData event: ExtractionEvent) {
        Analytics.track("cheque_extracted", properties: [
            "confidence_avg": event.averageConfidence,
            "fields_extracted": event.fieldCount,
            "amounts_matched": event.amountsMatch
        ])
    }
}

Métricas del panel de análisis

Realice un seguimiento de estas métricas clave en producción:

MétricaObjetivoDescripción
Tasa de éxito de captura>95%Porcentaje de capturas exitosas
Precisión de extracción>92%Precisión OCR a nivel de campo
Latencia de extremo a extremo<3sCaptura a extracción completa
Tasa de igualación de importe>98%Acuerdo escrito vs numérico
SDK Tasa de accidentes<0,1%Métrica de estabilidad

12. Ejemplos de código completos

iOS (Swift): integración completa

import UIKit
import ChequeUI

class ChequeScannerViewController: UIViewController {
    
    // MARK: - Properties
    private let chequeService = ChequeService()
    private var currentImage: UIImage?
    
    // MARK: - UI Components
    private lazy var captureButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("Capture Cheque", for: .normal)
        button.backgroundColor = .systemIndigo
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 12
        button.addTarget(self, action: #selector(captureTapped), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    private lazy var resultView: ChequeResultView = {
        let view = ChequeResultView()
        view.isHidden = true
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        initializeSDK()
    }
    
    // MARK: - Setup
    private func setupUI() {
        view.backgroundColor = .systemBackground
        title = "Cheque Scanner"
        
        view.addSubview(captureButton)
        view.addSubview(resultView)
        
        NSLayoutConstraint.activate([
            captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            captureButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            captureButton.widthAnchor.constraint(equalToConstant: 200),
            captureButton.heightAnchor.constraint(equalToConstant: 50),
            
            resultView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            resultView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            resultView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            resultView.bottomAnchor.constraint(equalTo: captureButton.topAnchor, constant: -20)
        ])
    }
    
    private func initializeSDK() {
        guard ChequeUI.isInitialized else {
            showAlert(message: "SDK not initialized")
            return
        }
        
        ChequeUI.shared.logLevel = .info
        ChequeUI.shared.errorHandler = { [weak self] error in
            self?.handleSDKError(error)
        }
    }
    
    // MARK: - Actions
    @objc private func captureTapped() {
        presentChequeCapture()
    }
    
    private func presentChequeCapture() {
        let captureVC = ChequeCaptureViewController()
        captureVC.delegate = self
        captureVC.captureOptions = CaptureOptions(
            autoCapture: true,
            guidanceMode: .enhanced,
            minQualityScore: 0.75,
            minEdgeConfidence: 0.80
        )
        present(captureVC, animated: true)
    }
    
    // MARK: - Processing
    private func processCheque(_ image: UIImage) {
        showLoading(true)
        
        Task {
            do {
                let extraction = try await ChequeUI.shared.extract(
                    from: image,
                    options: ExtractionOptions(
                        confidenceThresholds: [
                            .amountInNumerals: 0.95,
                            .amountInWords: 0.90
                        ]
                    )
                )
                
                await MainActor.run {
                    self.showLoading(false)
                    self.displayResults(extraction)
                }
                
            } catch {
                await MainActor.run {
                    self.showLoading(false)
                    self.handleExtractionError(error)
                }
            }
        }
    }
    
    private func displayResults(_ result: ExtractionResult) {
        resultView.configure(with: result)
        resultView.isHidden = false
        
        // Log analytics
        Analytics.track("cheque_processed", properties: [
            "bank": result.bankName,
            "amount": result.amountInNumerals,
            "confidence": result.averageConfidence
        ])
    }
    
    // MARK: - Error Handling
    private func handleSDKError(_ error: ChequeUIError) {
        switch error {
        case .unauthorized:
            showAlert(message: "Session expired. Please log in again.")
        case .networkError:
            showAlert(message: "Network error. Please check your connection.")
        case .rateLimitExceeded:
            showAlert(message: "Too many requests. Please try again later.")
        default:
            showAlert(message: error.localizedDescription)
        }
    }
    
    private func handleExtractionError(_ error: Error) {
        if let chequeError = error as? ChequeUIError {
            switch chequeError {
            case .poorImageQuality(let issues):
                let message = issues.map { $0.description }.joined(separator: "\n")
                showAlert(message: "Image quality issues:\n\(message)")
            default:
                showAlert(message: chequeError.localizedDescription)
            }
        } else {
            showAlert(message: "An unexpected error occurred")
        }
    }
    
    private func showLoading(_ loading: Bool) {
        captureButton.isEnabled = !loading
        captureButton.setTitle(loading ? "Processing..." : "Capture Cheque", for: .normal)
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "Notice", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - ChequeCaptureDelegate
extension ChequeScannerViewController: ChequeCaptureDelegate {
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didCapture image: UIImage,
        withMetadata metadata: CaptureMetadata
    ) {
        controller.dismiss(animated: true) { [weak self] in
            self?.currentImage = image
            self?.processCheque(image)
        }
    }
    
    func chequeCapture(
        _ controller: ChequeCaptureViewController,
        didFailWithError error: ChequeUIError
    ) {
        controller.dismiss(animated: true) { [weak self] in
            self?.handleSDKError(error)
        }
    }
}

Android (Kotlin): integración completa

package com.example.chequeapp

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.chequedb.chequeui.ChequeUI
import com.chequedb.chequeui.capture.CaptureContract
import com.chequedb.chequeui.capture.CaptureOptions
import com.chequedb.chequeui.capture.CaptureResult
import com.chequedb.chequeui.config.ChequeUIConfiguration
import com.chequedb.chequeui.config.Environment
import com.chequedb.chequeui.config.ExtractionOptions
import com.chequedb.chequeui.config.GuidanceMode
import com.chequedb.chequeui.error.ChequeUIError
import com.chequedb.chequeui.model.ExtractionResult
import com.example.chequeapp.databinding.ActivityChequeScannerBinding
import kotlinx.coroutines.launch

class ChequeScannerActivity : AppCompatActivity() {

    private lateinit var binding: ActivityChequeScannerBinding
    
    private val captureLauncher = registerForActivityResult(CaptureContract()) { result ->
        when (result) {
            is CaptureResult.Success -> {
                processCheque(result.image)
            }
            is CaptureResult.Error -> {
                handleCaptureError(result.error)
            }
            is CaptureResult.Cancelled -> {
                Toast.makeText(this, "Capture cancelled", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityChequeScannerBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        initializeSDK()
        setupUI()
    }

    private fun initializeSDK() {
        if (!ChequeUI.isInitialized()) {
            val config = ChequeUIConfiguration.Builder()
                .apiKey(BuildConfig.CHEQUEUI_API_KEY)
                .environment(if (BuildConfig.DEBUG) Environment.SANDBOX else Environment.PRODUCTION)
                .build()
            ChequeUI.initialize(applicationContext, config)
        }
        
        ChequeUI.setLogLevel(LogLevel.INFO)
        ChequeUI.setErrorHandler { error ->
            handleSDKError(error)
        }
    }

    private fun setupUI() {
        binding.captureButton.setOnClickListener {
            launchCapture()
        }
        
        binding.retryButton.setOnClickListener {
            launchCapture()
        }
        
        binding.resultView.isVisible = false
    }

    private fun launchCapture() {
        val options = CaptureOptions.Builder()
            .autoCapture(true)
            .guidanceMode(GuidanceMode.ENHANCED)
            .minQualityScore(0.75f)
            .minEdgeConfidence(0.80f)
            .showFlashToggle(true)
            .build()
        
        captureLauncher.launch(options)
    }

    private fun processCheque(image: Bitmap) {
        showLoading(true)
        
        lifecycleScope.launch {
            try {
                val extraction = ChequeUI.extract(
                    image = image,
                    options = ExtractionOptions(
                        confidenceThresholds = mapOf(
                            ExtractionField.AMOUNT_IN_NUMERALS to 0.95f,
                            ExtractionField.AMOUNT_IN_WORDS to 0.90f,
                            ExtractionField.BANK_NAME to 0.85f
                        )
                    )
                )
                
                showLoading(false)
                displayResults(extraction)
                
            } catch (e: Exception) {
                showLoading(false)
                handleExtractionError(e)
            }
        }
    }

    private fun displayResults(result: ExtractionResult) {
        binding.resultView.apply {
            isVisible = true
            setBankName(result.bankName)
            setBranch(result.branch)
            setPayee(result.payee)
            setAmount("${result.amountInWords} / $${result.amountInNumerals}")
            setDate(result.date)
            setConfidence(result.averageConfidence)
            setAmountsMatch(result.amountsMatch)
        }
        
        // Log analytics
        Analytics.logEvent("cheque_processed") {
            param("bank", result.bankName)
            param("amount", result.amountInNumerals.toDouble())
            param("confidence", result.averageConfidence.toDouble())
        }
    }

    private fun handleSDKError(error: ChequeUIError) {
        val message = when (error) {
            is ChequeUIError.Unauthorized -> "Session expired. Please log in again."
            is ChequeUIError.NetworkError -> "Network error. Please check your connection."
            is ChequeUIError.RateLimitExceeded -> "Too many requests. Please try again later."
            else -> error.message ?: "An error occurred"
        }
        
        runOnUiThread {
            Toast.makeText(this, message, Toast.LENGTH_LONG).show()
        }
    }

    private fun handleCaptureError(error: ChequeUIError) {
        when (error) {
            is ChequeUIError.CameraPermissionDenied -> {
                Toast.makeText(this, "Camera permission required", Toast.LENGTH_LONG).show()
            }
            else -> handleSDKError(error)
        }
    }

    private fun handleExtractionError(error: Exception) {
        val message = when (error) {
            is ChequeUIError.PoorImageQuality -> {
                val issues = error.issues.joinToString("\n") { "• ${it.description}" }
                "Image quality issues:\n$issues"
            }
            is ChequeUIError.LowConfidence -> {
                "Could not read cheque clearly. Please retake."
            }
            else -> "Failed to process cheque. Please try again."
        }
        
        Toast.makeText(this, message, Toast.LENGTH_LONG).show()
        binding.errorView.isVisible = true
    }

    private fun showLoading(loading: Boolean) {
        binding.captureButton.isEnabled = !loading
        binding.progressBar.visibility = if (loading) View.VISIBLE else View.GONE
        binding.captureButton.text = if (loading) "Processing..." else "Capture Cheque"
    }
}

13. Guía de solución de problemas

Problemas comunes y soluciones

ProblemaPosible causaSolución
SDK Error de inicializaciónClave API no válidaVerificar las credenciales en el panel de desarrollador
La cámara no se abrePermiso denegadoConsulte Info.plist / AndroidManifest.xml
Precisión deficiente de OCRProblemas de calidad de imagenUtilice captura automática y validación de calidad
Procesamiento lentoTamaño de imagen grandeReducir maxImageDimension en configuración
Vencimiento del tokenDesviación del relojSincronizar la hora del dispositivo con NTP
Errores de compilaciónConflictos de versiónActualización a la última versión SDK

Consejos de depuración

  1. Habilitar registro detallado:

    ChequeUI.shared.logLevel = .verbose
    
  2. Verifique la versión SDK:

    print("SDK de ChequeUI Version: \(ChequeUI.version)")
    
  3. Validar configuración:

    let validation = ChequeUI.shared.validateConfiguration()
    print(validation.diagnostics)
    

Recursos de soporte


14. Conclusión

El SDK móvil de ChequeUI proporciona una solución integral lista para producción para integrar capacidades de procesamiento de cheques en sus aplicaciones iOS y Android. Con sus funciones de captura inteligente, OCR de alta precisión y amplias opciones de personalización, puede ofrecer a sus usuarios una experiencia de procesamiento de cheques perfecta.

Conclusiones clave

  1. Integración rápida: comience en horas, no en semanas, con nuestro intuitivo SDK
  2. Listo para producción: manejo de errores integrado, soporte fuera de línea y funciones de seguridad
  3. Alta precisión: modelos de aprendizaje automático especialmente diseñados y optimizados para documentos financieros
  4. Arquitectura flexible: compatibilidad con flujos de trabajo sincrónicos y asincrónicos
  5. Soporte integral: documentación extensa, ejemplos y soporte dedicado

Próximos pasos

  1. Regístrese para obtener una cuenta de desarrollador en chequedb.com/developer
  2. Descargue las aplicaciones de muestra de nuestros repositorios de GitHub
  3. Experimente con el entorno sandbox
  4. Integre el SDK en su aplicación
  5. Implemente en producción con confianza

Recursos adicionales


Compartir este articulo

Ayuda a otros equipos a descubrir este contenido

Lleva estos flujos a produccion

Descubre como Chequedb automatiza procesamiento, revision y control de fraude en operaciones de cheques.