Manual Completo para Captura de Documentos en Aplicaciones iOS
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ón | Qué harás | Tiempo |
|---|---|---|
| Paso 1 | Configurar proyecto y dependencias | 10 min |
| Paso 2 | Implementación básica del SDK | 20 min |
| Paso 3 | Configurar permisos de cámara | 10 min |
| Paso 4 | Manejo de respuestas y imágenes | 15 min |
| Paso 5 | Probar captura de documentos | 10 min |
PASO 1: Configurar Proyecto y Dependencias
Objetivo
Configurar el entorno de desarrollo y añadir las dependencias necesarias del SDK.
Requisitos Técnicos
| Requisito | Versión | ¿Obligatorio? |
|---|---|---|
| Xcode | 12.0+ | Sí |
| iOS | 13.0+ | Sí |
| Swift | 5.5+ | Sí |
| CocoaPods | Latest | Sí |
| RAM | 2GB+ | 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?
- En Xcode, selecciona tu proyecto (no el target)
- Ve a Build Settings
- Busca "User Script Sandboxing" (puedes usar el campo de búsqueda)
- 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
| Elemento | Estado | Descripción |
|---|---|---|
| Permisos de cámara | ☐ | App solicita y obtiene permisos |
| Detección automática | ☐ | IA detecta documentos automáticamente |
| Captura de imágenes | ☐ | Genera original y crop de frente/reverso |
| Conversión base64 | ☐ | Todas las imágenes se convierten |
| Manejo de errores | ☐ | Errores se manejan correctamente |
Proceso de Prueba
Paso A: Probar Permisos
- Instalar aplicación en dispositivo físico
- Abrir aplicación por primera vez
- Verificar solicitud de permisos automática
- Conceder permisos de cámara
Paso B: Probar Captura Simple
- Presionar botón de captura
- Posicionar documento dentro del marco
- Esperar detección automática (marco verde)
- Verificar captura automática
- Revisar logs para confirmar procesamiento
Paso C: Probar Documento Doble Cara
- Capturar frente del documento
- Seguir instrucciones para voltear
- Capturar reverso automáticamente
- Verificar 4 imágenes en logs
- 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
| Problema | Solució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étodo | Descripción | Ejemplo |
|---|---|---|
| JaakStampsViewController(config:) | Constructor del controlador | JaakStampsViewController(config: config) |
| startCapture() | Inicia la captura | controller.startCapture() |
| stopCapture() | Detiene la captura | controller.stopCapture() |
| preloadModel(completion:) | Precarga modelo ML | controller.preloadModel { success in } |
Configuraciones Disponibles
| Configuración | Descripción | Cuándo usar |
|---|---|---|
| defaultConfig() | Configuración estándar | Uso general |
| highAccuracyConfig() | Alta precisión | Documentos complejos |
| highSpeedConfig() | Alta velocidad | Captura rápida |
Estructura de Respuesta
| Imagen | Descripción | Cuándo se genera |
|---|---|---|
| front.fullFrame | Imagen completa del frente | Siempre |
| front.cropped | Frente recortado por IA | Cuando detecta documento |
| back.fullFrame | Imagen completa del reverso | Solo si documento requiere reverso |
| back.cropped | Reverso recortado por IA | Cuando detecta reverso |
Utilidades
| Método | Descripción | Ejemplo |
|---|---|---|
| JAAKStampsSDK.isCompatible() | Verifica compatibilidad | JAAKStampsSDK.isCompatible() |
| JAAKStampsSDK.isCoreMLAvailable() | Verifica Core ML | JAAKStampsSDK.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: soporte@jaak.ai Incluir siempre: Logs iOS, configuración, versión SDK
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.