JAAK Stamps SDK iOS

@jaak.ai/stamps sdk ios - Componente para la captura de identificaciones

⏱️ Tiempo estimado: 45-60 minutos para configuración completa


🎯 ¿Qué aprenderás en este manual?

Este manual te enseñará a implementar y usar el JAAKStamps SDK para captura automatizada de documentos de identidad en aplicaciones iOS nativas utilizando inteligencia artificial y visión por computadora. Requiere conocimientos avanzados en desarrollo iOS, frameworks nativos, manejo de cámara y arquitectura de aplicaciones móviles.


📋 Antes de Empezar - Lista de Verificación

Asegúrate de tener estos elementos listos:

  • Xcode 12.0+ instalado
  • Dispositivo iOS 12.0+ (físico, no simulador)
  • CocoaPods configurado
  • Conocimientos avanzados de Swift/UIKit
  • Experiencia con AVFoundation y Core ML
  • Acceso a cámara funcional
  • Documento de identidad para pruebas

🗂️ Índice de Contenidos

SecciónQué harásTiempo
Paso 1Configurar proyecto y dependencias15 min
Paso 2Implementación básica del SDK25 min
Paso 3Configurar permisos de cámara10 min
Paso 4Manejo de respuestas y imágenes20 min
Paso 5Implementación avanzada (opcional)30 min
Paso 6Probar captura de documentos15 min

PASO 1: Configurar Proyecto y Dependencias

🎯 Objetivo

Configurar el entorno de desarrollo y añadir las dependencias necesarias del SDK.

✅ Requisitos Técnicos

RequisitoVersión¿Obligatorio?Notas
iOS12.0+Compatible desde iOS 12.0
Xcode12.0+IDE de desarrollo
Swift5.5+Lenguaje de programación
CocoaPodsLatestGestor de dependencias
DispositivoiPhone/iPad con cámaraCaptura requiere cámara
RAM2GB+RecomendadoPara procesamiento ML

1.1 Configuración Podfile

platform :ios, '12.0'
use_frameworks!

target 'YourApp' do
  pod 'JAAKStamps', '~> 1.0.0'
end

1.2 Instalación de Dependencias

# Instalar dependencias
pod install

# Abrir workspace (importante: no el .xcodeproj)
open YourApp.xcworkspace

1.3 Frameworks Requeridos

En tu proyecto Xcode, verifica que estos frameworks estén linkeados:

  • UIKit - Interfaz de usuario
  • AVFoundation - Acceso a cámara y captura
  • Foundation - Funcionalidades base

1.4 Dependencias Externas

El SDK incluye automáticamente:

  • onnxruntime-objc ~> 1.16.0 (procesamiento de modelos ONNX)

PASO 2: Implementación Básica del SDK

🎯 Objetivo

Crear la implementación base del JAAKStamps SDK con captura automatizada.

2.1 ViewController Básico - UIKit (Recomendado)

import UIKit
import JAAKStamps

class DocumentCaptureViewController: UIViewController {
    
    private var jaakStampsController: JaakStampsViewController?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupDocumentCapture()
    }
    
    private func setupDocumentCapture() {
        // Verificar compatibilidad del dispositivo
        guard JAAKStampsSDK.isCompatible() else {
            showCompatibilityError()
            return
        }
        
        // Configuración básica con valores por defecto
        let config = JaakStampsConfig.defaultConfig()
        
        // Crear controlador embebido
        jaakStampsController = JaakStampsViewController(config: config)
        jaakStampsController?.delegate = self
        
        // Embeber como child view controller
        guard let captureController = jaakStampsController else { return }
        
        addChild(captureController)
        view.addSubview(captureController.view)
        captureController.view.frame = view.bounds
        captureController.didMove(toParent: self)
        
        // Iniciar captura automáticamente
        captureController.startCapture()
    }
    
    private func showCompatibilityError() {
        let alert = UIAlertController(
            title: "Dispositivo No Compatible",
            message: "Este dispositivo no es compatible con el SDK JAAKStamps",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "Entendido", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - JaakStampsDelegate
extension DocumentCaptureViewController: JaakStampsDelegate {
    
    func jaakStampsDidBecomeReady(_ controller: JaakStampsViewController) {
        print("SDK listo para captura")
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didCompleteCapture images: CapturedImages) {
        print("Captura completada exitosamente")
        
        // Procesar imágenes frontales
        if let frontImage = images.front.fullFrame {
            processImage(frontImage, type: "front_full")
        }
        
        if let frontCropped = images.front.cropped {
            processImage(frontCropped, type: "front_cropped")
        }
        
        // Procesar imágenes traseras (si existen)
        if let backImage = images.back.fullFrame {
            processImage(backImage, type: "back_full")
        }
        
        if let backCropped = images.back.cropped {
            processImage(backCropped, type: "back_cropped")
        }
        
        // Mostrar metadatos
        let metadata = images.metadata
        print("Total imágenes: \(metadata.totalImages)")
        print("Reverso omitido: \(metadata.backCaptureSkipped)")
        print("Tiempo de procesamiento: \(metadata.processingTime)s")
        
        // Cerrar captura
        controller.dismiss(animated: true)
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didFailWithError error: JaakStampsError) {
        print("Error en captura: \(error.localizedDescription)")
        handleCaptureError(error)
        controller.dismiss(animated: true)
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didUpdateStatus status: ComponentStatus) {
        // Opcional: manejar actualizaciones de estado
        print("Estado actual: \(status.captureStep), Alineado: \(status.isAligned)")
    }
    
    private func processImage(_ image: UIImage, type: String) {
        print("Procesando imagen: \(type)")
        // Convertir a base64 si es necesario
        // let base64 = image.jpegData(compressionQuality: 0.8)?.base64EncodedString()
    }
    
    private func handleCaptureError(_ error: JaakStampsError) {
        var title = "Error"
        var message = error.localizedDescription
        
        switch error {
        case .cameraPermissionDenied:
            title = "Permisos de Cámara"
            message = "Se requiere acceso a la cámara para capturar documentos"
        case .cameraNotAvailable:
            title = "Cámara No Disponible"
            message = "No se pudo acceder a la cámara del dispositivo"
        case .modelLoadingFailed(let details):
            title = "Error de Modelo ML"
            message = "Error cargando modelo: \(details)"
        case .captureSessionFailed(let details):
            title = "Error de Captura"
            message = "Error en sesión de captura: \(details)"
        default:
            break
        }
        
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Entendido", style: .default))
        present(alert, animated: true)
    }
}

2.2 Implementación SwiftUI

import SwiftUI
import JAAKStamps

// Manager para controlar el SDK
class DocumentCaptureManager: ObservableObject {
    @Published var capturedImages: CapturedImages?
    @Published var errorMessage: String?
    @Published var isReady = false
    
    private var jaakStampsController: JaakStampsViewController?
    
    var captureViewController: JaakStampsViewController? {
        return jaakStampsController
    }
    
    func initialize() {
        guard jaakStampsController == nil else { return }
        guard JAAKStampsSDK.isCompatible() else {
            errorMessage = "Dispositivo no compatible"
            return
        }
        
        let config = JaakStampsConfig.defaultConfig()
        jaakStampsController = JaakStampsViewController(config: config)
        jaakStampsController?.delegate = self
    }
}

// MARK: - JaakStampsDelegate
extension DocumentCaptureManager: JaakStampsDelegate {
    
    func jaakStampsDidBecomeReady(_ controller: JaakStampsViewController) {
        DispatchQueue.main.async {
            self.isReady = true
            print("SDK listo para captura")
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didCompleteCapture images: CapturedImages) {
        DispatchQueue.main.async {
            self.capturedImages = images
            print("Captura completada exitosamente")
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didFailWithError error: JaakStampsError) {
        DispatchQueue.main.async {
            self.errorMessage = error.localizedDescription
            print("Error en captura: \(error.localizedDescription)")
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didUpdateStatus status: ComponentStatus) {
        // Opcional: manejar actualizaciones de estado
    }
}

// Wrapper para integrar JAAKStamps en SwiftUI
struct JAAKStampsView: UIViewControllerRepresentable {
    @ObservedObject var captureManager: DocumentCaptureManager
    
    func makeUIViewController(context: Context) -> JaakStampsViewController {
        if captureManager.captureViewController == nil {
            captureManager.initialize()
        }
        
        guard let controller = captureManager.captureViewController else {
            fatalError("JAAKStamps controller should exist after initialization")
        }
        
        return controller
    }
    
    func updateUIViewController(_ uiViewController: JaakStampsViewController, context: Context) {
        // SwiftUI manejará las actualizaciones automáticamente
    }
}

// Vista básica de SwiftUI
struct ContentView: View {
    @ObservedObject private var captureManager = DocumentCaptureManager()
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Captura de Documentos")
                .font(.title)
                .fontWeight(.bold)
            
            if captureManager.isReady {
                // Vista de cámara embebida
                JAAKStampsView(captureManager: captureManager)
                    .frame(height: 400)
                    .cornerRadius(12)
                
                // Botón de captura
                Button("Iniciar Captura") {
                    captureManager.captureViewController?.startCapture()
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
            }
            
            // Mostrar resultado
            if let images = captureManager.capturedImages {
                Text("¡Documento capturado exitosamente!")
                    .foregroundColor(.green)
                    .padding()
            }
            
            if let error = captureManager.errorMessage {
                Text("Error: \(error)")
                    .foregroundColor(.red)
                    .padding()
            }
            
            Spacer()
        }
        .padding()
        .onAppear {
            captureManager.initialize()
        }
    }
}

2.3 Diferencias entre UIKit y SwiftUI

AspectoUIKitSwiftUI
IntegraciónDirecta y nativaRequiere UIViewControllerRepresentable
ComplejidadMás simple, menos códigoMás código de wrapper necesario
RendimientoÓptimo, sin overheadLigero overhead por el wrapper
Manejo de estadoDelegados tradicionalesBinding y coordinadores
PresentaciónModal o embedded directoFullScreenCover o sheet
DebuggingMás directoPuede ser más complejo
RecomendaciónPreferida para este SDKUsar solo si la app es 100% SwiftUI

💡 Recomendación: Usa la implementación UIKit cuando sea posible, ya que el SDK está optimizado para este framework.


PASO 3: Configurar Permisos de Cámara

🎯 Objetivo

Configurar correctamente los permisos de cámara requeridos por iOS.

3.1 Configuración Info.plist (Método Tradicional)

<key>NSCameraUsageDescription</key>
<string>Esta aplicación necesita acceso a la cámara para capturar documentos</string>

3.2 Configuración en Xcode (Versiones Recientes)

  1. En Xcode, selecciona tu target
  2. Ve a la pestaña Info
  3. Expande Custom iOS Target Properties
  4. Haz clic en el botón + para agregar una nueva key
  5. Selecciona Privacy - Camera Usage Description
  6. Agrega el valor: Esta aplicación necesita acceso a la cámara para capturar documentos

3.3 Privacy Manifest (iOS 17+)

<!-- PrivacyInfo.xcprivacy -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSPrivacyAccessedAPITypes</key>
    <array>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryCamera</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>CA92.1</string>
            </array>
        </dict>
    </array>
    <key>NSPrivacyCollectedDataTypes</key>
    <array/>
    <key>NSPrivacyTracking</key>
    <false/>
    <key>NSPrivacyTrackingDomains</key>
    <array/>
</dict>
</plist>

3.4 Manejo de Permisos en Código

import AVFoundation

class PermissionsManager {
    
    static func checkCameraPermission() -> AVAuthorizationStatus {
        return AVCaptureDevice.authorizationStatus(for: .video)
    }
    
    static func requestCameraPermission(completion: @escaping (Bool) -> Void) {
        AVCaptureDevice.requestAccess(for: .video) { granted in
            DispatchQueue.main.async {
                completion(granted)
            }
        }
    }
    
    static func openSettings() {
        guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
            return
        }
        
        if UIApplication.shared.canOpenURL(settingsUrl) {
            UIApplication.shared.open(settingsUrl)
        }
    }
}

3.5 Verificación Antes de Captura

private func setupDocumentCapture() {
    let cameraStatus = PermissionsManager.checkCameraPermission()
    
    switch cameraStatus {
    case .authorized:
        startDocumentCapture()
    case .notDetermined:
        PermissionsManager.requestCameraPermission { granted in
            if granted {
                self.startDocumentCapture()
            } else {
                self.showPermissionAlert()
            }
        }
    case .denied, .restricted:
        showPermissionAlert()
    @unknown default:
        showPermissionAlert()
    }
}

private func startDocumentCapture() {
    guard JAAKStampsSDK.isCompatible() else {
        showCompatibilityError()
        return
    }
    
    let config = JaakStampsConfig.defaultConfig()
    let captureController = JaakStampsViewController(config: config)
    captureController.delegate = self
    
    captureController.modalPresentationStyle = .fullScreen
    present(captureController, animated: true) {
        captureController.startCapture()
    }
}

private func showPermissionAlert() {
    let alert = UIAlertController(
        title: "Permisos de Cámara",
        message: "Se requiere acceso a la cámara para capturar documentos",
        preferredStyle: .alert
    )
    
    alert.addAction(UIAlertAction(title: "Configuración", style: .default) { _ in
        PermissionsManager.openSettings()
    })
    
    alert.addAction(UIAlertAction(title: "Cancelar", style: .cancel))
    
    present(alert, animated: true)
}

PASO 4: Manejo de Respuestas e Imágenes

🎯 Objetivo

Implementar el manejo completo de las imágenes capturadas según la estructura oficial del SDK.

4.1 Estructura de Respuesta Completa

// Estructura oficial del SDK
public struct CapturedImages {
    public let front: ImagePair      // Imagen frontal
    public let back: ImagePair       // Imagen trasera
    public let metadata: Metadata    // Metadatos de captura
    
    public struct ImagePair {
        public let fullFrame: UIImage?    // Imagen completa
        public let cropped: UIImage?      // Imagen recortada
    }
    
    public struct Metadata {
        public let totalImages: Int           // Total de imágenes
        public let processCompleted: Bool     // Proceso completado
        public let backCaptureSkipped: Bool   // Reverso omitido
        public let timestamp: Date            // Marca de tiempo
        public let captureSteps: [CaptureStep] // Pasos completados
        public let documentType: DocumentType? // Tipo de documento
        public let processingTime: TimeInterval // Tiempo de procesamiento
    }
}

4.2 Procesamiento Completo de Imágenes

func jaakStamps(_ controller: JaakStampsViewController, didCompleteCapture images: CapturedImages) {
    let documentImages = DocumentImages()
    
    // Procesar frente completo
    if let frontFull = images.front.fullFrame {
        documentImages.frontOriginal = convertToBase64(frontFull)
        print("Front Original: \(documentImages.frontOriginal?.count ?? 0) chars")
    }
    
    // Procesar frente recortado
    if let frontCrop = images.front.cropped {
        documentImages.frontCrop = convertToBase64(frontCrop)
        print("Front Crop: \(documentImages.frontCrop?.count ?? 0) chars")
    }
    
    // Procesar reverso completo (si existe)
    if let backFull = images.back.fullFrame {
        documentImages.backOriginal = convertToBase64(backFull)
        print("Back Original: \(documentImages.backOriginal?.count ?? 0) chars")
    }
    
    // Procesar reverso recortado (si existe)
    if let backCrop = images.back.cropped {
        documentImages.backCrop = convertToBase64(backCrop)
        print("Back Crop: \(documentImages.backCrop?.count ?? 0) chars")
    }
    
    // Determinar tipo de documento
    let documentType = images.metadata.backCaptureSkipped ? "single_sided" : "double_sided"
    
    // Procesar documento completo
    processCompleteDocument(documentImages, type: documentType, metadata: images.metadata)
    
    controller.dismiss(animated: true)
}

struct DocumentImages {
    var frontOriginal: String?
    var frontCrop: String?
    var backOriginal: String?
    var backCrop: String?
}

private func convertToBase64(_ image: UIImage) -> String? {
    guard let imageData = image.jpegData(compressionQuality: 0.8) else { return nil }
    return imageData.base64EncodedString()
}

private func processCompleteDocument(_ images: DocumentImages, type: String, metadata: CapturedImages.Metadata) {
    print("=== PROCESANDO DOCUMENTO ===")
    print("Tipo: \(type)")
    print("Imágenes totales: \(metadata.totalImages)")
    print("Proceso completado: \(metadata.processCompleted)")
    print("Tiempo de procesamiento: \(metadata.processingTime)s")
    
    // Validar que tenemos al menos la imagen frontal
    guard images.frontOriginal != nil || images.frontCrop != nil else {
        print("Error: No se encontraron imágenes frontales válidas")
        return
    }
    
    // Crear payload para backend
    let payload = createDocumentPayload(images, type: type, metadata: metadata)
    
    // Enviar a backend o procesar localmente
    sendToBackend(payload)
    
    print("Documento procesado correctamente")
}

private func createDocumentPayload(_ images: DocumentImages, type: String, metadata: CapturedImages.Metadata) -> [String: Any] {
    return [
        "documentType": type,
        "timestamp": Date().timeIntervalSince1970,
        "version": "1.0.0",
        "images": [
            "frontOriginal": images.frontOriginal ?? "",
            "frontCrop": images.frontCrop ?? "",
            "backOriginal": images.backOriginal ?? "",
            "backCrop": images.backCrop ?? ""
        ],
        "metadata": [
            "totalImages": metadata.totalImages,
            "backCaptureSkipped": metadata.backCaptureSkipped,
            "processingTime": metadata.processingTime,
            "documentType": metadata.documentType?.rawValue ?? "unknown",
            "platform": "ios"
        ]
    ]
}

private func sendToBackend(_ payload: [String: Any]) {
    print("Enviando a backend: \(payload.keys)")
    // Implementar llamada a tu API
}

4.3 Manejo Completo de Errores

func jaakStamps(_ controller: JaakStampsViewController, didFailWithError error: JaakStampsError) {
    print("Error en captura: \(error.localizedDescription)")
    
    switch error {
    case .cameraPermissionDenied:
        handleCameraPermissionError()
    case .cameraNotAvailable:
        handleCameraError()
    case .modelLoadingFailed(let message):
        handleMLError(message)
    case .captureSessionFailed(let message):
        handleCaptureError(message)
    case .processingFailed(let message):
        handleProcessingError(message)
    case .networkError(let message):
        handleNetworkError(message)
    case .invalidConfiguration(let message):
        handleConfigurationError(message)
    case .unknown(let message):
        handleGenericError(message)
    }
    
    controller.dismiss(animated: true)
}

private func handleCameraPermissionError() {
    showAlert(
        title: "Permisos de Cámara",
        message: "Se requiere acceso a la cámara para capturar documentos. Ve a Configuración para habilitarlo."
    ) {
        PermissionsManager.openSettings()
    }
}

private func handleCameraError() {
    showAlert(
        title: "Error de Cámara",
        message: "No se pudo acceder a la cámara. Verifica que no esté siendo usada por otra aplicación."
    )
}

private func handleMLError(_ message: String) {
    showAlert(
        title: "Error de Procesamiento",
        message: "No se pudo cargar el modelo de IA: \(message). Reinicia la aplicación e intenta nuevamente."
    )
}

private func handleCaptureError(_ message: String) {
    showAlert(
        title: "Error de Captura",
        message: "Error durante la captura: \(message). Intenta nuevamente."
    )
}

private func handleProcessingError(_ message: String) {
    showAlert(
        title: "Error de Procesamiento",
        message: "Error al procesar la imagen: \(message). Verifica que el documento esté bien iluminado."
    )
}

private func handleNetworkError(_ message: String) {
    showAlert(
        title: "Error de Red",
        message: "Error de conectividad: \(message). Verifica tu conexión a internet."
    )
}

private func handleConfigurationError(_ message: String) {
    showAlert(
        title: "Error de Configuración",
        message: "Configuración inválida: \(message). Contacta al desarrollador."
    )
}

private func handleGenericError(_ message: String) {
    showAlert(
        title: "Error",
        message: "Ocurrió un error inesperado: \(message)"
    )
}

private func showAlert(title: String, message: String, action: (() -> Void)? = nil) {
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
    
    if let action = action {
        alert.addAction(UIAlertAction(title: "Configuración", style: .default) { _ in
            action()
        })
        alert.addAction(UIAlertAction(title: "Cancelar", style: .cancel))
    } else {
        alert.addAction(UIAlertAction(title: "Entendido", style: .default))
    }
    
    present(alert, animated: true)
}

PASO 5: Implementación Avanzada (Opcional)

🎯 Objetivo

Implementar funcionalidades avanzadas del SDK con configuración personalizada y delegados adicionales.

5.1 Configuración Avanzada

private func initializeAdvancedSDK() {
    // Configuración avanzada personalizada
    var config = JaakStampsConfig()
    config.debug = true // Modo debug para desarrollo
    config.maskSize = 85.0 // Tamaño de máscara (50-100)
    config.alignmentTolerance = 20.0 // Tolerancia de alineación
    config.cropMargin = 0.0 // Margen de recorte (0-100)
    config.autoCapture = true // Captura automática
    config.autoCaptureDelay = 1.5 // Delay para captura automática
    config.useDocumentClassification = true // Clasificación de documentos
    config.preferredCamera = .back // Cámara preferida
    config.maxFramesPerSecond = 30 // FPS máximo
    config.minConfidenceThreshold = 0.7 // Umbral de confianza mínimo
    
    // Inicializar controlador
    jaakStampsController = JaakStampsViewController(config: config)
    jaakStampsController.delegate = self
    
    // Precargar modelo para mejor rendimiento
    jaakStampsController.preloadModel { [weak self] success in
        DispatchQueue.main.async {
            if success {
                self?.statusLabel.text = "Modelo precargado - Listo para capturar"
            } else {
                self?.statusLabel.text = "Error precargando modelo"
            }
        }
    }
}

5.2 Configuración Dinámica

// Métodos para actualizar configuración en tiempo real
private func updateMaskSize(_ size: Double) {
    jaakStampsController.setMaskSize(size)
    print("Tamaño de máscara actualizado a \(size)")
}

private func updateAlignmentTolerance(_ tolerance: Double) {
    jaakStampsController.setAlignmentTolerance(tolerance)
    print("Tolerancia de alineación actualizada a \(tolerance)")
}

private func updateCropMargin(_ margin: Double) {
    jaakStampsController.setCropMargin(margin)
    print("Margen de recorte actualizado a \(margin)")
}

private func toggleDebugMode(_ enabled: Bool) {
    jaakStampsController.setDebugMode(enabled)
    print("Modo debug \(enabled ? "activado" : "desactivado")")
}

private func changeCameraPreference(_ preference: CameraPreference) {
    jaakStampsController.setPreferredCamera(preference)
    print("Cámara cambiada a \(preference)")
}

// Reiniciar componente
private func resetComponent() {
    jaakStampsController.resetComponent()
    print("Componente reiniciado")
}

5.3 Delegados Avanzados

// MARK: - JaakStampsDelegate (Métodos Avanzados)
extension AdvancedDocumentCaptureViewController: JaakStampsDelegate {
    
    func jaakStampsDidBecomeReady(_ controller: JaakStampsViewController) {
        DispatchQueue.main.async {
            self.statusLabel.text = "Cámara lista - Posicione el documento"
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didCompleteCapture images: CapturedImages) {
        DispatchQueue.main.async {
            let totalImages = images.metadata.totalImages
            let frontAvailable = images.front.fullFrame != nil
            let backAvailable = images.back.fullFrame != nil
            let processingTime = images.metadata.processingTime
            
            self.statusLabel.text = """
            ✅ Captura completada exitosamente!
            Imágenes: \(totalImages) | Frontal: \(frontAvailable) | Trasera: \(backAvailable)
            Tiempo: \(String(format: "%.2f", processingTime))s
            """
            
            print("=== CAPTURA COMPLETADA ===")
            print("Total de imágenes: \(totalImages)")
            print("Imagen frontal completa: \(images.front.fullFrame != nil)")
            print("Imagen frontal recortada: \(images.front.cropped != nil)")
            print("Imagen trasera disponible: \(backAvailable)")
            print("Captura trasera omitida: \(images.metadata.backCaptureSkipped)")
            print("Tiempo de procesamiento: \(processingTime)s")
            print("Tipo de documento: \(images.metadata.documentType?.rawValue ?? "No detectado")")
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didFailWithError error: JaakStampsError) {
        DispatchQueue.main.async {
            self.statusLabel.text = "❌ Error: \(error.localizedDescription)"
            
            // Mostrar sugerencia de recuperación si está disponible
            if error.isRecoverable {
                self.statusLabel.text += "\n💡 \(error.recoveryAction)"
            }
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didUpdateStatus status: ComponentStatus) {
        DispatchQueue.main.async {
            // Actualizar progreso de alineación
            let alignmentScore = status.isAligned ? 1.0 : 0.3
            self.alignmentProgressView.progress = Float(alignmentScore)
            
            // Actualizar mensaje de estado basado en el paso actual
            switch status.captureStep {
            case .front:
                if status.isAligned {
                    self.statusLabel.text = "✅ Frontal alineado - Capturando..."
                } else {
                    self.statusLabel.text = "🔄 Posicione la parte frontal del documento"
                }
            case .transitioning:
                self.statusLabel.text = "🔄 Por favor voltee el documento"
            case .back:
                if status.isAligned {
                    self.statusLabel.text = "✅ Trasero alineado - Capturando..."
                } else {
                    self.statusLabel.text = "🔄 Posicione la parte trasera del documento"
                }
            case .completed:
                self.statusLabel.text = "✅ Captura completada"
            }
            
            // Mostrar información adicional de debug
            if self.debugMode {
                let confidence = String(format: "%.2f", status.detectionConfidence)
                self.statusLabel.text += "\n🔍 Confianza: \(confidence)"
            }
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didUpdateAlignment alignment: SideAlignment) {
        DispatchQueue.main.async {
            // Feedback visual para alineación
            let alignmentText = """
            Alineación: ↑\(alignment.top ? "✅" : "❌") ↓\(alignment.bottom ? "✅" : "❌") ←\(alignment.left ? "✅" : "❌") →\(alignment.right ? "✅" : "❌")
            """
            
            if self.debugMode {
                self.statusLabel.text += "\n\(alignmentText)"
            }
            
            // Actualizar progreso basado en alineación
            let alignedSides = [alignment.top, alignment.bottom, alignment.left, alignment.right].compactMap { $0 ? 1 : 0 }.reduce(0, +)
            self.alignmentProgressView.progress = Float(alignedSides) / 4.0
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didClassifyDocument classification: ClassificationResult) {
        DispatchQueue.main.async {
            let confidencePercent = Int(classification.confidence * 100)
            self.statusLabel.text = "📋 Documento clasificado: \(classification.documentType) (\(confidencePercent)%)"
        }
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didChangeCamera cameraInfo: CameraInfo) {
        DispatchQueue.main.async {
            let deviceCount = cameraInfo.availableCameras.count
            let selectedCamera = cameraInfo.selectedCameraId ?? "No seleccionada"
            
            self.statusLabel.text = """
            📷 Cámara cambiada
            Dispositivos disponibles: \(deviceCount) | Seleccionada: \(selectedCamera)
            """
        }
    }
}

### **5.4 Utilidades del SDK**

```swift
// Verificaciones del sistema
private func checkSystemCapabilities() {
    let isCompatible = JAAKStampsSDK.isCompatible()
    let systemReqs = JAAKStampsSDK.getSystemRequirements()
    let cameraStatus = JAAKStampsSDK.getCameraPermissionStatus()
    let availableCameras = JAAKStampsSDK.getAvailableCameraDevices()
    
    print("=== VERIFICACIÓN DEL SISTEMA ===")
    print("SDK compatible: \(isCompatible)")
    print("Requisitos del sistema: \(systemReqs)")
    print("Estado de cámara: \(cameraStatus)")
    print("Cámaras disponibles: \(availableCameras.count)")
}

// Solicitar permisos
private func requestCameraPermissionIfNeeded() {
    JAAKStampsSDK.requestCameraPermission { granted in
        DispatchQueue.main.async {
            if granted {
                self.startDocumentCapture()
            } else {
                self.showPermissionAlert()
            }
        }
    }
}

5.5 Extensiones de Conveniencia

extension JaakStampsError {
    var isRecoverable: Bool {
        switch self {
        case .cameraPermissionDenied, .cameraNotAvailable:
            return true
        case .modelLoadingFailed, .captureSessionFailed:
            return true
        case .processingFailed:
            return true
        default:
            return false
        }
    }
    
    var recoveryAction: String {
        switch self {
        case .cameraPermissionDenied:
            return "Ve a Configuración para habilitar el acceso a la cámara"
        case .cameraNotAvailable:
            return "Cierra otras apps que usen la cámara e intenta nuevamente"
        case .modelLoadingFailed:
            return "Reinicia la aplicación e intenta nuevamente"
        case .captureSessionFailed:
            return "Reinicia la captura"
        case .processingFailed:
            return "Mejora la iluminación e intenta nuevamente"
        default:
            return "Intenta nuevamente"
        }
    }
}

extension CapturedImages {
    var hasBothSides: Bool {
        return front.fullFrame != nil && back.fullFrame != nil
    }
    
    var allImages: [UIImage] {
        var images: [UIImage] = []
        
        if let frontFull = front.fullFrame { images.append(frontFull) }
        if let frontCrop = front.cropped { images.append(frontCrop) }
        if let backFull = back.fullFrame { images.append(backFull) }
        if let backCrop = back.cropped { images.append(backCrop) }
        
        return images
    }
}

PASO 6: Probar Captura de Documentos

🎯 Objetivo

Verificar que todas las funcionalidades del SDK funcionan correctamente.

✅ Lista de Verificación

ElementoDescripción
Compatibilidad del dispositivoSDK.isCompatible() retorna true
Permisos de cámaraApp solicita y obtiene permisos
Detección automáticaIA detecta documentos automáticamente
Captura de imágenesGenera original y crop de frente/reverso
Conversión base64Todas las imágenes se convierten
Manejo de erroresErrores se manejan correctamente
Configuración dinámicaMétodos de configuración funcionan
Delegados avanzadosCallbacks adicionales funcionan

🔍 Proceso de Prueba

Paso A: Verificar Compatibilidad

// Agregar a tu código de prueba
private func testCompatibility() {
    print("=== PRUEBA DE COMPATIBILIDAD ===")
    
    let isCompatible = JAAKStampsSDK.isCompatible()
    print("SDK compatible: \(isCompatible)")
    
    let systemReqs = JAAKStampsSDK.getSystemRequirements()
    print("Requisitos: \(systemReqs)")
    
    let cameraStatus = JAAKStampsSDK.getCameraPermissionStatus()
    print("Estado cámara: \(cameraStatus)")
    
    assert(isCompatible, "El dispositivo debe ser compatible")
}

Paso B: Probar Permisos

  1. Instalar aplicación en dispositivo físico
  2. Abrir aplicación por primera vez
  3. Verificar solicitud de permisos automática
  4. Conceder permisos de cámara
  5. Confirmar que la cámara inicia correctamente

Paso C: Probar Captura Simple

  1. Presionar botón de captura
  2. Posicionar documento dentro del marco
  3. Esperar detección automática (marco verde)
  4. Verificar captura automática
  5. Revisar logs para confirmar procesamiento

Paso D: Probar Documento Doble Cara

  1. Capturar frente del documento
  2. Seguir instrucciones para voltear
  3. Capturar reverso automáticamente
  4. Verificar 4 imágenes en logs (front/back full/cropped)
  5. Confirmar procesamiento exitoso

Paso E: Probar Configuración Avanzada

private func testAdvancedConfiguration() {
    // Cambiar tamaño de máscara
    jaakStampsController.setMaskSize(90.0)
    
    // Cambiar tolerancia de alineación
    jaakStampsController.setAlignmentTolerance(15.0)
    
    // Activar modo debug
    jaakStampsController.setDebugMode(true)
    
    // Cambiar cámara
    jaakStampsController.setPreferredCamera(.front)
    
    print("Configuración avanzada aplicada")
}

🛠️ Debugging y Troubleshooting

Verificar Estado del SDK

private func debugSDKState() {
    print("=== DEBUG DEL SDK ===")
    
    // Verificar compatibilidad
    let isCompatible = JAAKStampsSDK.isCompatible()
    print("Compatible: \(isCompatible)")
    
    // Verificar estado de la cámara
    let cameraStatus = JAAKStampsSDK.getCameraPermissionStatus()
    print("Permisos cámara: \(cameraStatus)")
    
    // Verificar dispositivos disponibles
    let cameras = JAAKStampsSDK.getAvailableCameraDevices()
    print("Cámaras disponibles: \(cameras.count)")
    
    // Verificar configuración actual
    if let controller = jaakStampsController {
        print("Controlador inicializado: Sí")
        print("Modo debug: \(controller.isDebugModeEnabled)")
    } else {
        print("Controlador inicializado: No")
    }
}

Habilitar Logging Detallado

private func enableDetailedLogging() {
    var config = JaakStampsConfig()
    config.debug = true
    config.logLevel = .verbose
    
    jaakStampsController = JaakStampsViewController(config: config)
    jaakStampsController.delegate = self
}

Problemas Comunes y Soluciones

ProblemaCausa ProbableSolución
"SDK no compatible"iOS < 12.0 o hardware insuficiente✅ Verificar JAAKStampsSDK.isCompatible()
"Cámara no inicia"Permisos denegados o cámara ocupada✅ Verificar permisos y cerrar otras apps
"Modelo no carga"Memoria insuficiente o archivos corruptos✅ Reiniciar app y verificar espacio
"Captura no funciona"Simulador o configuración incorrecta✅ Usar dispositivo físico y verificar config
"Detección lenta"Configuración de FPS alta✅ Reducir maxFramesPerSecond
"Imágenes borrosas"Iluminación pobre o movimiento✅ Mejorar iluminación y estabilizar dispositivo

Test de Integración Completa

func testCompleteIntegration() {
    // 1. Verificar compatibilidad
    XCTAssertTrue(JAAKStampsSDK.isCompatible())
    
    // 2. Crear configuración
    let config = JaakStampsConfig.defaultConfig()
    let captureVC = JaakStampsViewController(config: config)
    XCTAssertNotNil(captureVC)
    
    // 3. Verificar delegado
    captureVC.delegate = self
    XCTAssertNotNil(captureVC.delegate)
    
    // 4. Precargar modelo
    let expectation = XCTestExpectation(description: "Model preload")
    captureVC.preloadModel { success in
        XCTAssertTrue(success)
        expectation.fulfill()
    }
    
    wait(for: [expectation], timeout: 10.0)
    
    // 5. Verificar configuración dinámica
    captureVC.setMaskSize(90.0)
    captureVC.setDebugMode(true)
    
    print("✅ Test de integración completado")
}

📚 Referencia Completa del SDK

🔧 Métodos Principales

MétodoDescripciónEjemplo
JaakStampsViewController(config:)Constructor del controladorJaakStampsViewController(config: config)
startCapture()Inicia la capturacontroller.startCapture()
stopCapture()Detiene la capturacontroller.stopCapture()
preloadModel(completion:)Precarga modelo MLcontroller.preloadModel { success in }
resetComponent()Reinicia el componentecontroller.resetComponent()

⚙️ Métodos de Configuración Dinámica

MétodoDescripciónRango/Valores
setMaskSize(_:)Cambiar tamaño de máscara50.0 - 100.0
setAlignmentTolerance(_:)Cambiar tolerancia de alineación5.0 - 50.0
setCropMargin(_:)Cambiar margen de recorte0.0 - 100.0
setDebugMode(_:)Activar/desactivar debugtrue/false
setPreferredCamera(_:)Cambiar cámara preferida.auto/.front/.back

🔍 Métodos de Utilidad

MétodoDescripciónRetorno
JAAKStampsSDK.isCompatible()Verifica compatibilidadBool
JAAKStampsSDK.getSystemRequirements()Obtiene requisitos[String: Any]
JAAKStampsSDK.getCameraPermissionStatus()Estado de permisosAVAuthorizationStatus
JAAKStampsSDK.getAvailableCameraDevices()Dispositivos disponibles[AVCaptureDevice]
JAAKStampsSDK.requestCameraPermission(_:)Solicita permisosvoid

📊 Configuraciones Disponibles

ConfiguraciónDescripciónValor por DefectoCuándo usar
defaultConfig()Configuración estándarValores balanceadosUso general
debugModo debugfalseDesarrollo y debugging
maskSizeTamaño de máscara85.0Ajustar según tipo de documento
alignmentToleranceTolerancia de alineación20.0Ajustar precisión vs velocidad
autoCaptureCaptura automáticatrueExperiencia automatizada
autoCaptureDelayDelay de auto-captura1.5sDar tiempo al usuario
useDocumentClassificationClasificación de documentosfalseIdentificar tipo de documento
cropMarginMargen de recorte0.0Incluir más contexto
preferredCameraCámara preferida.backCalidad vs conveniencia

📡 Estructura de Delegados

Delegados Básicos

  • jaakStampsDidBecomeReady(_:) - SDK listo
  • jaakStamps(_:didCompleteCapture:) - Captura completada
  • jaakStamps(_:didFailWithError:) - Error en captura
  • jaakStamps(_:didUpdateStatus:) - Actualización de estado

Delegados Avanzados

  • jaakStamps(_:didUpdateAlignment:) - Actualización de alineación
  • jaakStamps(_:didClassifyDocument:) - Clasificación de documento
  • jaakStamps(_:didChangeCamera:) - Cambio de cámara

📊 Estructura de Respuesta Detallada

CapturedImages

struct CapturedImages {
    let front: ImagePair        // Imágenes frontales
    let back: ImagePair         // Imágenes traseras
    let metadata: Metadata      // Metadatos
}

ImagePair

struct ImagePair {
    let fullFrame: UIImage?     // Imagen completa del frame
    let cropped: UIImage?       // Imagen recortada por IA
}

Metadata

struct Metadata {
    let totalImages: Int                // Número total de imágenes
    let processCompleted: Bool          // Si el proceso se completó
    let backCaptureSkipped: Bool        // Si se omitió la captura trasera
    let timestamp: Date                 // Marca de tiempo
    let captureSteps: [CaptureStep]     // Pasos completados
    let documentType: DocumentType?     // Tipo de documento detectado
    let processingTime: TimeInterval    // Tiempo de procesamiento
}

⚠️ Tipos de Error

enum JaakStampsError: Error {
    case cameraPermissionDenied          // Permiso denegado
    case cameraNotAvailable              // Cámara no disponible
    case modelLoadingFailed(String)      // Error cargando modelo
    case captureSessionFailed(String)    // Error en sesión
    case processingFailed(String)        // Error en procesamiento
    case networkError(String)            // Error de red
    case invalidConfiguration(String)    // Configuración inválida
    case unknown(String)                 // Error desconocido
}

🚨 Solución de Problemas Avanzada

Problemas de Compatibilidad

Error: "SDK no compatible"

// ✅ Verificar antes de usar
if !JAAKStampsSDK.isCompatible() {
    let requirements = JAAKStampsSDK.getSystemRequirements()
    print("Requisitos no cumplidos: \(requirements)")
    showCompatibilityError()
    return
}

Error: "Modelo ML no se carga"

// ✅ Precargar explícitamente
captureController.preloadModel { success in
    if success {
        print("Modelo cargado exitosamente")
        captureController.startCapture()
    } else {
        print("Error cargando modelo - reinicia la app")
        self.showModelError()
    }
}

Problemas de Rendimiento

Captura lenta en dispositivos antiguos

// ✅ Configuración optimizada para dispositivos lentos
var config = JaakStampsConfig()
config.maxFramesPerSecond = 15  // Reducir FPS
config.minConfidenceThreshold = 0.6  // Reducir umbral
config.maskSize = 70.0  // Reducir área de detección

Consumo excesivo de memoria

// ✅ Optimización de memoria
override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    
    // Limpiar recursos del SDK
    jaakStampsController?.stopCapture()
    jaakStampsController = nil
    
    // Forzar liberación de memoria
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        // Permitir que el sistema libere memoria
    }
}

Problemas de Calidad de Imagen

Imágenes borrosas o de baja calidad

// ✅ Configuración para mejor calidad
var config = JaakStampsConfig()
config.autoCaptureDelay = 2.0  // Más tiempo para estabilizar
config.minConfidenceThreshold = 0.8  // Mayor confianza
config.cropMargin = 10.0  // Más margen de recorte

Detección inconsistente

// ✅ Ajustar parámetros de detección
captureController.setAlignmentTolerance(15.0)  // Más estricto
captureController.setMaskSize(80.0)  // Área más específica

📞 ¿Necesitas Ayuda?

🆘 Información para Soporte

Cuando contactes al soporte, incluye siempre:

Información del Dispositivo

// Agregar este código para obtener información de debug
private func generateDebugInfo() -> String {
    let device = UIDevice.current
    let isCompatible = JAAKStampsSDK.isCompatible()
    let systemReqs = JAAKStampsSDK.getSystemRequirements()
    let cameraStatus = JAAKStampsSDK.getCameraPermissionStatus()
    
    return """
    === INFORMACIÓN DE DEBUG ===
    Dispositivo: \(device.model)
    iOS: \(device.systemVersion)
    SDK Compatible: \(isCompatible)
    Requisitos: \(systemReqs)
    Permisos Cámara: \(cameraStatus)
    Memoria disponible: \(ProcessInfo.processInfo.physicalMemory / 1024 / 1024) MB
    """
}

Logs de Error

  • Screenshots de errores en consola Xcode
  • Logs específicos del SDK (con debug activado)
  • Pasos exactos para reproducir el problema

Configuración Utilizada

// Compartir la configuración usada
private func getConfigurationSummary() -> String {
    guard let controller = jaakStampsController else { return "No inicializado" }
    
    return """
    === CONFIGURACIÓN ACTUAL ===
    Modo Debug: \(controller.isDebugModeEnabled)
    Cámara Preferida: \(controller.getCurrentCameraPreference())
    Configuración: \(controller.getCurrentConfiguration())
    """
}

📋 Contacto de Soporte

📧 Email: [email protected]
📱 Incluir siempre:

  • Logs de iOS completos
  • Información de dispositivo
  • Configuración del SDK
  • Versión del SDK utilizada
  • Tipo de documento capturado

📖 Recursos Adicionales


¡Listo! 🎉

Has implementado exitosamente el JAAKStamps SDK con todas sus funcionalidades avanzadas. Tu aplicación iOS ahora puede:

Capturar documentos automáticamente usando inteligencia artificial
Detectar y alinear documentos en tiempo real
Generar imágenes optimizadas (originales y recortadas)
Clasificar tipos de documento automáticamente
Manejar errores de forma robusta
Configurar dinámicamente el comportamiento del SDK
Optimizar rendimiento para diferentes dispositivos

Tu aplicación está lista para procesos KYC de nivel empresarial.