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. No necesitas conocimientos técnicos avanzados - solo sigue los pasos.


📋 Antes de Empezar - Lista de Verificación

Asegúrate de tener estos elementos listos:

  • Xcode 12.0+ instalado
  • Dispositivo iOS 13.0+ (físico, no simulador)
  • CocoaPods configurado
  • Conocimientos básicos de Swift/UIKit
  • Acceso a cámara funcional
  • Documento de identidad para pruebas

🗂️ Índice de Contenidos

SecciónQué harásTiempo
Paso 1Configurar proyecto y dependencias10 min
Paso 2Implementación básica del SDK20 min
Paso 3Configurar permisos de cámara10 min
Paso 4Manejo de respuestas y imágenes15 min
Paso 5Probar captura de documentos10 min

PASO 1: Configurar Proyecto y Dependencias

🎯 Objetivo

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

✅ Requisitos Técnicos

RequisitoVersión¿Obligatorio?
Xcode12.0+
iOS13.0+
Swift5.5+
CocoaPodsLatest
RAM2GB+Recomendado

1.1 Configuración Podfile

platform :ios, '13.0'
use_frameworks!

target 'YourApp' do
  # SDK JAAKStamps - Versión Beta
  pod 'jaak-stamps', '>= 1.0.0-beta.1', '< 1.0.0-dev'
end

1.2 Instalación de Dependencias

# Instalar dependencias
pod install

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

1.3 Configuración de Build Settings

⚠️ IMPORTANTE: Para el correcto funcionamiento de la biblioteca, es necesario configurar el siguiente build setting:

User Script Sandboxing = NO

¿Cómo configurar?

  1. En Xcode, selecciona tu proyecto (no el target)
  2. Ve a Build Settings
  3. Busca "User Script Sandboxing" (puedes usar el campo de búsqueda)
  4. Cambia el valor a "No"

¿Por qué es necesario?

La biblioteca JAAKStamps utiliza scripts durante el proceso de build para:

  • Copiar recursos necesarios (modelos ONNX, archivos de configuración)
  • Configurar frameworks nativos
  • Validar dependencias

El sandboxing de scripts puede interferir con estos procesos, causando errores de build o runtime. Deshabilitar esta opción permite que la biblioteca funcione correctamente.

Nota de Seguridad: Esta configuración es específica para el proceso de build y no afecta la seguridad de la aplicación final.

1.4 Configuración de Frameworks Necesarios

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

  • UIKit - Interfaz de usuario
  • AVFoundation - Acceso a cámara
  • CoreML - Procesamiento de IA
  • Vision - Análisis de imágenes
  • Foundation - Funcionalidades base

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

import UIKit
import JAAKStamps

class DocumentCaptureViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupDocumentCapture()
    }
    
    private func setupDocumentCapture() {
        let config = JaakStampsConfig.defaultConfig()
        
        let captureController = JaakStampsViewController(config: config)
        captureController.delegate = self
        
        captureController.modalPresentationStyle = .fullScreen
        present(captureController, animated: true) {
            captureController.startCapture()
        }
    }
}

extension DocumentCaptureViewController: JaakStampsDelegate {
    
    func jaakStampsDidBecomeReady(_ controller: JaakStampsViewController) {
        print("SDK listo para captura")
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didCompleteCapture images: CapturedImages) {
        print("Captura completada")
        
        if let frontImage = images.front.fullFrame {
            processImage(frontImage, type: "front_full")
        }
        
        if let frontCrop = images.front.cropped {
            processImage(frontCrop, type: "front_crop")
        }
        
        if let backImage = images.back.fullFrame {
            processImage(backImage, type: "back_full")
        }
        
        if let backCrop = images.back.cropped {
            processImage(backCrop, type: "back_crop")
        }
        
        controller.dismiss(animated: true)
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didFailWithError error: JaakStampsError) {
        print("Error: \(error.localizedDescription)")
        controller.dismiss(animated: true)
    }
    
    func jaakStamps(_ controller: JaakStampsViewController, didUpdateStatus status: ComponentStatus) {
        // Método requerido - implementación básica
        print("Status actualizado: \(status)")
    }
    
    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()
    }
}

2.2 Configuración SwiftUI (Opcional)

import SwiftUI
import JAAKStamps

struct DocumentCaptureView: UIViewControllerRepresentable {
    @Binding var isPresented: Bool
    @Binding var capturedImages: CapturedImages?
    
    func makeUIViewController(context: Context) -> JaakStampsViewController {
        let config = JaakStampsConfig.defaultConfig()
        let controller = JaakStampsViewController(config: config)
        controller.delegate = context.coordinator
        return controller
    }
    
    func updateUIViewController(_ uiViewController: JaakStampsViewController, context: Context) {
        if isPresented && !uiViewController.isProcessCompleted() {
            uiViewController.startCapture()
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    class Coordinator: NSObject, JaakStampsDelegate {
        let parent: DocumentCaptureView
        
        init(_ parent: DocumentCaptureView) {
            self.parent = parent
        }
        
        func jaakStampsDidBecomeReady(_ controller: JaakStampsViewController) {
            print("SDK listo")
        }
        
        func jaakStamps(_ controller: JaakStampsViewController, didCompleteCapture images: CapturedImages) {
            DispatchQueue.main.async {
                self.parent.capturedImages = images
                self.parent.isPresented = false
            }
        }
        
        func jaakStamps(_ controller: JaakStampsViewController, didFailWithError error: JaakStampsError) {
            DispatchQueue.main.async {
                self.parent.isPresented = false
            }
        }
        
        func jaakStamps(_ controller: JaakStampsViewController, didUpdateStatus status: ComponentStatus) {
            // Método requerido - implementación básica
            print("Status actualizado: \(status)")
        }
    }
}

2.3 Uso en SwiftUI

struct ContentView: View {
    @State private var showCapture = false
    @State private var capturedImages: CapturedImages?
    
    var body: some View {
        VStack {
            Button("Capturar Documento") {
                showCapture = true
            }
            .padding()
            
            if capturedImages != nil {
                Text("✅ Documento capturado")
                    .foregroundColor(.green)
            }
        }
        .fullScreenCover(isPresented: $showCapture) {
            DocumentCaptureView(
                isPresented: $showCapture,
                capturedImages: $capturedImages
            )
        }
    }
}

PASO 3: Configurar Permisos de Cámara

🎯 Objetivo

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

3.1 Info.plist

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

3.2 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.3 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() {
    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 y Imágenes

🎯 Objetivo

Implementar el manejo completo de las imágenes capturadas y conversión necesaria.

4.1 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)")
    
    // 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,
            "platform": "ios"
        ]
    ]
}

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

4.2 Manejo Avanzado 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)
    default:
        handleGenericError(error.localizedDescription)
    }
    
    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. Reinicia la aplicación e intenta nuevamente."
    )
}

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

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

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: Probar Captura de Documentos

🎯 Objetivo

Verificar que todas las funcionalidades del SDK funcionan correctamente.

✅ Lista de Verificación

ElementoDescripción
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

🔍 Proceso de Prueba

Paso A: 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

Paso B: 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 C: 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
  5. Confirmar procesamiento exitoso

🛠️ Debugging y Troubleshooting

Verificar Compatibilidad del Dispositivo

private func checkDeviceCompatibility() {
    print("=== VERIFICANDO COMPATIBILIDAD ===")
    
    let isCompatible = JAAKStampsSDK.isCompatible()
    print("SDK compatible: \(isCompatible)")
    
    let coreMLAvailable = JAAKStampsSDK.isCoreMLAvailable()
    print("Core ML disponible: \(coreMLAvailable)")
    
    let cameraStatus = PermissionsManager.checkCameraPermission()
    print("Estado cámara: \(cameraStatus)")
    
    let systemReqs = JAAKStampsSDK.getSystemRequirements()
    print("Requisitos del sistema: \(systemReqs)")
}

private func enableDebugMode() {
    var config = JaakStampsConfig.defaultConfig()
    config.debug = true
    
    // Usar configuración con debug habilitado
    let captureController = JaakStampsViewController(config: config)
}

Problemas Comunes

ProblemaSolución
"SDK no compatible"✅ Verificar iOS 13.0+ y Core ML disponible
"Cámara no inicia"✅ Verificar permisos y que no esté ocupada
"Modelo no carga"✅ Reiniciar app y verificar memoria disponible
"Captura no funciona"✅ Probar en dispositivo físico, no simulador
"User Script Sandboxing"✅ Cambiar a "No" en Build Settings del proyecto

📚 Referencia Completa

🔧 Métodos del SDK

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 }

📡 Configuraciones Disponibles

ConfiguraciónDescripciónCuándo usar
defaultConfig()Configuración estándarUso general
highAccuracyConfig()Alta precisiónDocumentos complejos
highSpeedConfig()Alta velocidadCaptura rápida

📊 Estructura de Respuesta

ImagenDescripciónCuándo se genera
front.fullFrameImagen completa del frenteSiempre
front.croppedFrente recortado por IACuando detecta documento
back.fullFrameImagen completa del reversoSolo si documento requiere reverso
back.croppedReverso recortado por IACuando detecta reverso

⚙️ Utilidades

MétodoDescripciónEjemplo
JAAKStampsSDK.isCompatible()Verifica compatibilidadJAAKStampsSDK.isCompatible()
JAAKStampsSDK.isCoreMLAvailable()Verifica Core MLJAAKStampsSDK.isCoreMLAvailable()

🚨 Solución de Problemas

Problema: "Error de modelo ML"

// ✅ Verificar antes de usar
if !JAAKStampsSDK.isCoreMLAvailable() {
    showAlert(title: "Dispositivo Incompatible", 
              message: "Este dispositivo no soporta Core ML")
    return
}

// Precargar modelo explícitamente
captureController.preloadModel { success in
    if success {
        captureController.startCapture()
    } else {
        self.showAlert(title: "Error", message: "No se pudo cargar el modelo")
    }
}

Problema: "Cámara no funciona en simulador"

// ✅ Solo funciona en dispositivo físico
#if targetEnvironment(simulator)
    showAlert(title: "Simulador", 
              message: "La captura de documentos requiere un dispositivo físico")
    return
#endif

Problema: "Permisos constantemente denegados"

// ✅ Verificar configuración del sistema
private func checkPermissionStatus() {
    let status = AVCaptureDevice.authorizationStatus(for: .video)
    
    if status == .denied {
        showAlert(
            title: "Permisos Requeridos",
            message: "Ve a Configuración > Privacidad > Cámara y habilita el acceso para esta app"
        ) {
            PermissionsManager.openSettings()
        }
    }
}

Problema: "User Script Sandboxing Error"

// ✅ Verificar configuración del proyecto
private func checkBuildSettings() {
    #if DEBUG
    print("⚠️ Verificar Build Settings:")
    print("1. Seleccionar PROYECTO (no target)")
    print("2. Build Settings > User Script Sandboxing = NO")
    print("3. Clean Build Folder y recompilar")
    #endif
}

📞 ¿Necesitas Ayuda?

🆘 Información para soporte

  • Descripción del problema: Qué intentas hacer vs qué sucede
  • Logs de Xcode: Screenshots de errores en consola
  • Información del dispositivo: Modelo iOS, versión, memoria
  • Configuración Podfile: Versiones de dependencias
  • Tipo de documento: INE, pasaporte, licencia, etc.

📋 Contacto de Soporte

📧 Email: [email protected]
📱 Incluir siempre: Logs iOS, configuración, versión SDK


¡Listo! 🎉

Has implementado exitosamente el JAAKStamps SDK en tu aplicación iOS. Tu app ahora puede capturar documentos automáticamente usando inteligencia artificial, generando imágenes optimizadas para procesos KYC.