JAAK Stamps SDK
Stamps(@jaak.ai/stamps) es la versión 2 de document-detector, un componente web que permite la captura de identificaciones.
1. Objetivo, alcance y usuarios
El objetivo de este documento es proporcionar una guía completa para la integración y uso del WebComponent Jaak Stamps, un componente especializado en la captura automatizada de documentos de identidad mediante tecnología de visión por computadora.
Este documento abarca la implementación técnica del componente, configuración de propiedades, métodos disponibles, manejo de eventos, y las mejores prácticas para su integración en aplicaciones web modernas.
Dirigido a: Desarrolladores frontend con experiencia en integraciones de WebComponents, HTML5, JavaScript/TypeScript y APIs web.
Nivel requerido: Conocimientos intermedios en desarrollo web, manejo de WebComponents, acceso a cámaras mediante MediaStream API, y arquitecturas basadas en componentes.
Demo en Vivo
Antes de empezar con la implementación, puedes probar el componente en funcionamiento:
- Demo Live: https://stamps.jaak.solutions/
- Repositorio de ejemplo: https://github.com/jaak-ai/stamps-js-example
Funcionalidades Clave
- Detección automática en tiempo real: Identifica documentos de identificación a través de la cámara del dispositivo.
- Guía visual para posicionamiento: Ayuda al usuario a alinear el documento para una captura óptima.
- Captura adaptativa: Se ajusta automáticamente si el documento tiene o no reverso.
- Clasificación inteligente de documentos: Determina automáticamente si se requiere captura del reverso.
- Salida de imágenes base64: Proporciona tanto la imagen completa del cuadro de video como un recorte preciso del documento para cada lado capturado.
- Control de cámara avanzado: Selección de cámara, enfoque automático y control de linterna.
- Responsivo: Compatible con dispositivos móviles y de escritorio.
- Optimización automática: Mejora del rendimiento y gestión inteligente de recursos.
- Telemetría integrada: Soporte para OpenTelemetry con trazas distribuidas y métricas.
2. Desarrollo
2.1 Prerrequisitos técnicos
a) Requisitos técnicos
| Requisito | Versión/Especificación | Obligatorio | Notas |
|---|---|---|---|
| Navegador Web | Chrome 67+, Firefox 63+, Safari 12+, Edge 79+ | Sí | Soporte para WebComponents y MediaStream API |
| Protocol | HTTPS | Sí | Requerido para acceso a cámara web |
| JavaScript | ES2017+ | Sí | Soporte para async/await y módulos ES6 |
| Memoria RAM | 4GB+ recomendado | No | Para mejor rendimiento con modelos de IA |
| Cámara web | Cualquier cámara USB/integrada | Sí | Con resolución mínima 640x480 |
b) Credenciales y configuración de accesos
Requisitos de acceso:
- Protocolo HTTPS: Obligatorio para acceso a MediaStream API
- Permisos de cámara: El usuario debe otorgar permisos de acceso a la cámara
- Conexión a internet: Necesaria para descarga de modelos de detección desde CDN
- Almacenamiento local: Para persistencia de preferencias de cámara (opcional)
- Licencia del SDK: Obligatoria para el funcionamiento del componente
c) Obtención de Licencia
Existen dos formas de obtener la licencia del SDK:
Opción A: Solicitud Directa
- Formato: String alfanumérico único
- Ejemplo:
"ABC-123-XYZ-789" - Solicitar a: [email protected]
Opción B: Generación mediante API
Si no obtiene la licencia directamente del equipo JAAK, puede generarla mediante el siguiente proceso:
Paso 1: Obtener el Trace ID
Llamar al endpoint de inicio de flujo KYC:
POST /v1/kyc/sessionDe la respuesta, obtener los headers traceparent y x-trace-id:
traceparent: 00-cf143715d7a2d4ffc3ef122f62384844-6f7048446dffdb89-00
x-trace-id: 32922b9bf22570da5e9895fa592c6852
Paso 2: Validar la Licencia
Construir la licencia agregando el prefijo L al x-trace-id:
Lcf143715d7a2d4ffc3ef122f62384844
El campo obtenido se utilizará para autenticar los SDKs y el campo traceparent debe ser enviado en los headers de todos los llamados por API que se realicen.
2.2 Configuración del entorno
Paso 1. Instalación
a) Método principal de instalación
npm install @jaak.ai/[email protected]O mediante CDN:
<script type="module" src="https://unpkg.com/@jaak.ai/[email protected]/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>b) Configuración inicial
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Jaak Stamps Demo</title>
</head>
<body>
<jaak-stamps
id="documentCapture"
license="your-license-key-here"
license-environment="prod"
app-id="my-custom-app"
debug="false"
mask-size="90"
alignment-tolerance="15"
crop-margin="20"
preferred-camera="auto"
capture-delay="1500"
enable-back-document-timer="false"
back-document-timer-duration="20">
</jaak-stamps>
<script type="module" src="https://unpkg.com/@jaak.ai/[email protected]/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
<script type="module">
const jaakStamps = document.getElementById('documentCapture');
// Escuchar cuando el componente esté listo
jaakStamps.addEventListener('isReady', (event) => {
console.log('Componente listo:', event.detail);
});
// Escuchar cuando se complete la captura
jaakStamps.addEventListener('captureCompleted', (event) => {
console.log('Captura completada:', event.detail);
});
</script>
</body>
</html>Paso 2. Configuración Avanzada
// Configuración avanzada con manejo de errores
const jaakStamps = document.getElementById('documentCapture');
// Precargar modelos antes de iniciar captura
jaakStamps.preloadModel().then(result => {
if (result.success) {
console.log('Modelos precargados exitosamente');
} else {
console.error('Error al precargar modelos:', result.error);
}
});
// Configurar eventos
jaakStamps.addEventListener('isReady', handleReady);
jaakStamps.addEventListener('captureCompleted', handleCaptureCompleted);
function handleReady(event) {
console.log('Componente inicializado:', event.detail);
// Opcional: mostrar botón para iniciar captura
}
function handleCaptureCompleted(event) {
const images = event.detail;
console.log('Imágenes capturadas:', {
front: images.front,
back: images.back,
metadata: images.metadata
});
}2.3 Guía de implementación
2.3.1 Implementación en Vanilla JavaScript
a) Ejemplo mínimo funcional:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Captura de Documentos</title>
<style>
jaak-stamps {
width: 100%;
max-width: 600px;
height: 400px;
display: block;
margin: 20px auto;
}
.controls {
text-align: center;
margin: 20px;
}
button {
padding: 10px 20px;
margin: 5px;
font-size: 16px;
cursor: pointer;
}
.results {
max-width: 600px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="controls">
<button id="startBtn" onclick="startCapture()">Iniciar Captura</button>
<button id="resetBtn" onclick="resetCapture()">Reiniciar</button>
<button id="statusBtn" onclick="checkStatus()">Estado</button>
</div>
<jaak-stamps
id="documentCapture"
mask-size="85"
alignment-tolerance="15"
crop-margin="20"
preferred-camera="auto"
capture-delay="1500">
</jaak-stamps>
<div id="results" class="results" style="display: none;">
<h3>Resultados de la Captura</h3>
<div id="imageResults"></div>
</div>
<script type="module" src="https://unpkg.com/@jaak.ai/[email protected]/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
<script type="module">
const jaakStamps = document.getElementById('documentCapture');
const resultsDiv = document.getElementById('results');
const imageResults = document.getElementById('imageResults');
// Funciones globales para los botones
window.startCapture = async () => {
try {
await jaakStamps.startCapture();
console.log('Captura iniciada');
} catch (error) {
console.error('Error al iniciar captura:', error);
}
};
window.resetCapture = async () => {
await jaakStamps.resetCapture();
resultsDiv.style.display = 'none';
};
window.checkStatus = async () => {
const status = await jaakStamps.getStatus();
console.log('Estado actual:', status);
};
// Event listeners
jaakStamps.addEventListener('captureCompleted', (event) => {
const images = event.detail;
displayResults(images);
});
function displayResults(images) {
imageResults.innerHTML = `
<p><strong>Proceso completado:</strong> ${images.metadata.processCompleted}</p>
<p><strong>Total de imágenes:</strong> ${images.metadata.totalImages}</p>
<p><strong>Reverso omitido:</strong> ${images.metadata.backCaptureSkipped || 'No'}</p>
<p><strong>Timestamp:</strong> ${images.timestamp}</p>
`;
resultsDiv.style.display = 'block';
}
</script>
</body>
</html>b) Manejo de respuesta/resultado:
// Manejo completo de eventos y métodos
const jaakStamps = document.getElementById('documentCapture');
// 1. Verificar cuando el componente esté listo
jaakStamps.addEventListener('isReady', async (event) => {
console.log('Componente listo:', event.detail);
// Obtener información de cámaras disponibles
const cameraInfo = await jaakStamps.getCameraInfo();
console.log('Cámaras disponibles:', cameraInfo);
});
// 2. Manejar la finalización de captura
jaakStamps.addEventListener('captureCompleted', (event) => {
const capturedData = event.detail;
// Procesar imágenes capturadas
if (capturedData.front.fullFrame) {
console.log('Imagen frontal capturada');
// Mostrar o procesar imagen frontal
displayImage(capturedData.front.fullFrame, 'front-display');
}
if (capturedData.back.fullFrame) {
console.log('Imagen trasera capturada');
// Mostrar o procesar imagen trasera
displayImage(capturedData.back.fullFrame, 'back-display');
}
// Enviar imágenes al servidor directamente desde el evento
sendToServer(capturedData);
});
function displayImage(base64Data, elementId) {
const imgElement = document.getElementById(elementId);
if (imgElement) {
imgElement.src = base64Data;
}
}
function sendToServer(images) {
fetch('/api/process-documents', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(images)
})
.then(response => response.json())
.then(data => console.log('Respuesta del servidor:', data))
.catch(error => console.error('Error:', error));
}
// 3. Ejemplo alternativo: usar getCapturedImages() por separado
// (útil cuando necesitas obtener las imágenes en otro momento)
async function getImagesLater() {
try {
// Verificar que el proceso esté completado
const isCompleted = await jaakStamps.isProcessCompleted();
if (isCompleted) {
const images = await jaakStamps.getCapturedImages();
console.log('Imágenes obtenidas posteriormente:', images);
return images;
} else {
console.log('El proceso de captura aún no está completado');
}
} catch (error) {
console.error('Error al obtener imágenes:', error);
}
}2.3.2 Implementación en Angular
a) Instalación y configuración:
# Instalar el componente
npm install @jaak.ai/[email protected]
# Instalar tipos para TypeScript (opcional)
npm install --save-dev @types/nodeb) Configuración del módulo:
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
// Importar el componente
import { defineCustomElements } from '@jaak.ai/stamps/loader';
defineCustomElements();
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA] // Permitir elementos personalizados
})
export class AppModule { }c) Componente TypeScript:
// document-capture.component.ts
import { Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-document-capture',
templateUrl: './document-capture.component.html',
styleUrls: ['./document-capture.component.css']
})
export class DocumentCaptureComponent {
@ViewChild('jaakStamps', { static: false }) jaakStamps!: ElementRef;
isReady = false;
isCapturing = false;
capturedImages: any = null;
onComponentReady(event: any) {
this.isReady = event.detail;
console.log('Componente listo:', this.isReady);
}
onCaptureCompleted(event: any) {
this.capturedImages = event.detail;
this.isCapturing = false;
console.log('Captura completada:', this.capturedImages);
}
async startCapture() {
if (!this.isReady) return;
try {
this.isCapturing = true;
await this.jaakStamps.nativeElement.startCapture();
} catch (error) {
console.error('Error al iniciar captura:', error);
this.isCapturing = false;
}
}
async resetCapture() {
try {
await this.jaakStamps.nativeElement.resetCapture();
this.capturedImages = null;
this.isCapturing = false;
} catch (error) {
console.error('Error al reiniciar captura:', error);
}
}
}d) Template HTML:
<!-- document-capture.component.html -->
<div class="capture-container">
<h2>Captura de Documento</h2>
<div class="controls">
<button
(click)="startCapture()"
[disabled]="!isReady || isCapturing"
class="btn btn-primary">
{{ isCapturing ? 'Capturando...' : 'Iniciar Captura' }}
</button>
<button
(click)="resetCapture()"
[disabled]="!isReady"
class="btn btn-secondary">
Reiniciar
</button>
</div>
<jaak-stamps
#jaakStamps
mask-size="90"
alignment-tolerance="15"
crop-margin="20"
preferred-camera="auto"
capture-delay="1500"
(isReady)="onComponentReady($event)"
(captureCompleted)="onCaptureCompleted($event)"
class="stamps-component">
</jaak-stamps>
<div *ngIf="capturedImages" class="results">
<h3>Resultados de Captura</h3>
<div class="metadata">
<p><strong>Total de imágenes:</strong> {{ capturedImages.metadata.totalImages }}</p>
<p><strong>Proceso completado:</strong> {{ capturedImages.metadata.processCompleted ? 'Sí' : 'No' }}</p>
</div>
<div class="images" *ngIf="capturedImages.front.fullFrame">
<h4>Imagen Frontal:</h4>
<img [src]="capturedImages.front.fullFrame" alt="Documento frente" class="captured-image">
</div>
<div class="images" *ngIf="capturedImages.back.fullFrame">
<h4>Imagen Trasera:</h4>
<img [src]="capturedImages.back.fullFrame" alt="Documento reverso" class="captured-image">
</div>
</div>
</div>e) Estilos CSS:
/* document-capture.component.css */
.capture-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.controls {
text-align: center;
margin: 20px 0;
}
.btn {
padding: 10px 20px;
margin: 0 10px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.stamps-component {
width: 100%;
height: 400px;
display: block;
margin: 20px 0;
border: 1px solid #ddd;
border-radius: 8px;
}
.results {
margin-top: 20px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #f8f9fa;
}
.metadata p {
margin: 5px 0;
}
.images {
margin: 15px 0;
}
.captured-image {
max-width: 100%;
height: auto;
border: 1px solid #ccc;
border-radius: 4px;
}2.3.3 Implementación en React
a) Instalación y configuración:
# Instalar el componente
npm install @jaak.ai/[email protected]
# Para TypeScript, instalar tipos
npm install --save-dev @types/react @types/react-domb) Configuración inicial:
// App.js - Importación y configuración básica
import React, { useRef, useEffect, useState } from 'react';
// Importar el componente
import { defineCustomElements } from '@jaak.ai/stamps/loader';
defineCustomElements();
function DocumentCapture() {
const jaakStampsRef = useRef(null);
const [isReady, setIsReady] = useState(false);
const [isCapturing, setIsCapturing] = useState(false);
const [capturedImages, setCapturedImages] = useState(null);
useEffect(() => {
const jaakStamps = jaakStampsRef.current;
if (!jaakStamps) return;
// Configurar eventos
const handleReady = (event) => {
setIsReady(event.detail);
console.log('Componente listo:', event.detail);
};
const handleCaptureCompleted = (event) => {
setCapturedImages(event.detail);
setIsCapturing(false);
console.log('Captura completada:', event.detail);
};
jaakStamps.addEventListener('isReady', handleReady);
jaakStamps.addEventListener('captureCompleted', handleCaptureCompleted);
// Cleanup
return () => {
jaakStamps.removeEventListener('isReady', handleReady);
jaakStamps.removeEventListener('captureCompleted', handleCaptureCompleted);
};
}, []);
const startCapture = async () => {
if (!jaakStampsRef.current || !isReady) return;
try {
setIsCapturing(true);
await jaakStampsRef.current.startCapture();
} catch (error) {
console.error('Error al iniciar captura:', error);
setIsCapturing(false);
}
};
const resetCapture = async () => {
if (!jaakStampsRef.current) return;
try {
await jaakStampsRef.current.resetCapture();
setCapturedImages(null);
setIsCapturing(false);
} catch (error) {
console.error('Error al reiniciar:', error);
}
};
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<h2>Captura de Documento</h2>
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
<button
onClick={startCapture}
disabled={!isReady || isCapturing}
style={{
padding: '10px 20px',
margin: '0 10px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: isReady && !isCapturing ? 'pointer' : 'not-allowed',
opacity: isReady && !isCapturing ? 1 : 0.6
}}
>
{isCapturing ? 'Capturando...' : 'Iniciar Captura'}
</button>
<button
onClick={resetCapture}
disabled={!isReady}
style={{
padding: '10px 20px',
margin: '0 10px',
backgroundColor: '#6c757d',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: isReady ? 'pointer' : 'not-allowed',
opacity: isReady ? 1 : 0.6
}}
>
Reiniciar
</button>
</div>
<jaak-stamps
ref={jaakStampsRef}
mask-size="90"
alignment-tolerance="15"
crop-margin="20"
preferred-camera="auto"
capture-delay="1500"
style={{
width: '100%',
height: '400px',
display: 'block',
border: '1px solid #ddd',
borderRadius: '8px'
}}
/>
{capturedImages && (
<div style={{
marginTop: '20px',
padding: '20px',
border: '1px solid #ddd',
borderRadius: '8px',
backgroundColor: '#f8f9fa'
}}>
<h3>Resultados de Captura</h3>
<p><strong>Total de imágenes:</strong> {capturedImages.metadata.totalImages}</p>
<p><strong>Proceso completado:</strong> {capturedImages.metadata.processCompleted ? 'Sí' : 'No'}</p>
{capturedImages.front.fullFrame && (
<div style={{ margin: '15px 0' }}>
<h4>Imagen Frontal:</h4>
<img
src={capturedImages.front.fullFrame}
alt="Documento frente"
style={{
maxWidth: '100%',
height: 'auto',
border: '1px solid #ccc',
borderRadius: '4px'
}}
/>
</div>
)}
{capturedImages.back.fullFrame && (
<div style={{ margin: '15px 0' }}>
<h4>Imagen Trasera:</h4>
<img
src={capturedImages.back.fullFrame}
alt="Documento reverso"
style={{
maxWidth: '100%',
height: 'auto',
border: '1px solid #ccc',
borderRadius: '4px'
}}
/>
</div>
)}
</div>
)}
</div>
);
}
export default DocumentCapture;c) Ejemplo con CSS separado:
// DocumentCapture.jsx
import React, { useRef, useEffect, useState } from 'react';
import { defineCustomElements } from '@jaak.ai/stamps/loader';
import './DocumentCapture.css';
defineCustomElements();
function DocumentCapture() {
const jaakStampsRef = useRef(null);
const [isReady, setIsReady] = useState(false);
const [isCapturing, setIsCapturing] = useState(false);
const [capturedImages, setCapturedImages] = useState(null);
useEffect(() => {
const jaakStamps = jaakStampsRef.current;
if (!jaakStamps) return;
const handleReady = (event) => {
setIsReady(event.detail);
console.log('Componente listo:', event.detail);
};
const handleCaptureCompleted = (event) => {
setCapturedImages(event.detail);
setIsCapturing(false);
console.log('Captura completada:', event.detail);
};
jaakStamps.addEventListener('isReady', handleReady);
jaakStamps.addEventListener('captureCompleted', handleCaptureCompleted);
return () => {
jaakStamps.removeEventListener('isReady', handleReady);
jaakStamps.removeEventListener('captureCompleted', handleCaptureCompleted);
};
}, []);
const startCapture = async () => {
if (!jaakStampsRef.current || !isReady) return;
try {
setIsCapturing(true);
await jaakStampsRef.current.startCapture();
} catch (error) {
console.error('Error al iniciar captura:', error);
setIsCapturing(false);
}
};
const resetCapture = async () => {
if (!jaakStampsRef.current) return;
try {
await jaakStampsRef.current.resetCapture();
setCapturedImages(null);
setIsCapturing(false);
} catch (error) {
console.error('Error al reiniciar:', error);
}
};
return (
<div className="document-capture">
<h2>Captura de Documento</h2>
<div className="controls">
<button
onClick={startCapture}
disabled={!isReady || isCapturing}
className="btn btn-primary"
>
{isCapturing ? 'Capturando...' : 'Iniciar Captura'}
</button>
<button
onClick={resetCapture}
disabled={!isReady}
className="btn btn-secondary"
>
Reiniciar
</button>
</div>
<jaak-stamps
ref={jaakStampsRef}
mask-size="90"
alignment-tolerance="15"
crop-margin="20"
preferred-camera="auto"
capture-delay="1500"
className="stamps-component"
/>
{capturedImages && (
<div className="results">
<h3>Resultados de Captura</h3>
<div className="metadata">
<p><strong>Total de imágenes:</strong> {capturedImages.metadata.totalImages}</p>
<p><strong>Proceso completado:</strong> {capturedImages.metadata.processCompleted ? 'Sí' : 'No'}</p>
</div>
{capturedImages.front.fullFrame && (
<div className="image-section">
<h4>Imagen Frontal:</h4>
<img
src={capturedImages.front.fullFrame}
alt="Documento frente"
className="captured-image"
/>
</div>
)}
{capturedImages.back.fullFrame && (
<div className="image-section">
<h4>Imagen Trasera:</h4>
<img
src={capturedImages.back.fullFrame}
alt="Documento reverso"
className="captured-image"
/>
</div>
)}
</div>
)}
</div>
);
}
export default DocumentCapture;/* DocumentCapture.css */
.document-capture {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.controls {
text-align: center;
margin-bottom: 20px;
}
.btn {
padding: 10px 20px;
margin: 0 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.stamps-component {
width: 100%;
height: 400px;
display: block;
border: 1px solid #ddd;
border-radius: 8px;
}
.results {
margin-top: 20px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #f8f9fa;
}
.metadata p {
margin: 5px 0;
}
.image-section {
margin: 15px 0;
}
.captured-image {
max-width: 100%;
height: auto;
border: 1px solid #ccc;
border-radius: 4px;
}2.3.4 Implementación avanzada
a) Ejemplo completo funcional:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Implementación Avanzada - Jaak Stamps</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin-bottom: 30px;
}
.btn {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background-color: #545b62;
}
.btn-warning {
background-color: #ffc107;
color: #212529;
}
.btn-warning:hover:not(:disabled) {
background-color: #e0a800;
}
.stamps-container {
width: 100%;
height: 500px;
border: 2px solid #ddd;
border-radius: 10px;
overflow: hidden;
margin-bottom: 30px;
}
jaak-stamps {
width: 100%;
height: 100%;
display: block;
}
.status-panel {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.status-item {
background-color: white;
padding: 15px;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.status-label {
font-weight: bold;
color: #6c757d;
font-size: 14px;
margin-bottom: 5px;
}
.status-value {
font-size: 16px;
color: #333;
}
.status-ready {
color: #28a745;
}
.status-error {
color: #dc3545;
}
.results-section {
background-color: #e8f5e8;
border: 1px solid #c3e6cb;
border-radius: 8px;
padding: 20px;
margin-top: 20px;
}
.results-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.image-preview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.image-card {
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
}
.image-card img {
max-width: 100%;
height: auto;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.error-message {
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 6px;
padding: 15px;
margin: 20px 0;
color: #721c24;
}
.camera-selector {
margin-bottom: 20px;
}
.camera-selector select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Implementación Avanzada - Jaak Stamps</h1>
<p>Ejemplo completo con control total del flujo y manejo de errores</p>
</div>
<div class="camera-selector" id="cameraSelector" style="display: none;">
<label for="cameraSelect">Seleccionar cámara:</label>
<select id="cameraSelect"></select>
</div>
<div class="controls">
<button id="preloadBtn" class="btn btn-secondary">Precargar Modelos</button>
<button id="startBtn" class="btn btn-primary" disabled>Iniciar Captura</button>
<button id="resetBtn" class="btn btn-warning" disabled>Reiniciar</button>
<button id="statusBtn" class="btn btn-secondary">Estado</button>
</div>
<div class="stamps-container">
<jaak-stamps
id="documentCapture"
mask-size="90"
alignment-tolerance="15"
crop-margin="20"
preferred-camera="auto"
use-document-classification="true"
capture-delay="1500"
debug="false">
</jaak-stamps>
</div>
<div class="status-panel">
<h3>Estado del Componente</h3>
<div class="status-grid">
<div class="status-item">
<div class="status-label">Estado General</div>
<div class="status-value" id="generalStatus">Inicializando...</div>
</div>
<div class="status-item">
<div class="status-label">Modelos Cargados</div>
<div class="status-value" id="modelStatus">No</div>
</div>
<div class="status-item">
<div class="status-label">Cámara Activa</div>
<div class="status-value" id="cameraStatus">No</div>
</div>
<div class="status-item">
<div class="status-label">Paso de Captura</div>
<div class="status-value" id="captureStep">-</div>
</div>
</div>
</div>
<div id="errorContainer"></div>
<div id="resultsContainer"></div>
</div>
<script type="module" src="https://unpkg.com/@jaak.ai/[email protected]/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
<script type="module">
// Implementación avanzada con control total del flujo
class DocumentCaptureManager {
constructor(elementId) {
this.jaakStamps = document.getElementById(elementId);
this.setupEventListeners();
this.setupUI();
this.isReady = false;
this.currentError = null;
}
setupUI() {
// Referencias a elementos del DOM
this.preloadBtn = document.getElementById('preloadBtn');
this.startBtn = document.getElementById('startBtn');
this.resetBtn = document.getElementById('resetBtn');
this.statusBtn = document.getElementById('statusBtn');
this.cameraSelect = document.getElementById('cameraSelect');
this.cameraSelector = document.getElementById('cameraSelector');
this.errorContainer = document.getElementById('errorContainer');
this.resultsContainer = document.getElementById('resultsContainer');
// Event listeners para botones
this.preloadBtn.addEventListener('click', () => this.preloadModels());
this.startBtn.addEventListener('click', () => this.startCaptureProcess());
this.resetBtn.addEventListener('click', () => this.resetCapture());
this.statusBtn.addEventListener('click', () => this.showStatus());
this.cameraSelect.addEventListener('change', (e) => this.switchCamera(e.target.value));
}
async preloadModels() {
try {
this.updateStatus('Precargando modelos...');
this.preloadBtn.disabled = true;
const result = await this.jaakStamps.preloadModel();
if (result.success) {
console.log('Modelos precargados exitosamente');
this.updateStatus('Modelos cargados', 'ready');
document.getElementById('modelStatus').textContent = 'Sí';
this.onModelsReady();
} else {
console.error('Error al precargar modelos:', result.error);
this.handleError('MODEL_LOAD_ERROR', result.error);
}
} catch (error) {
console.error('Error en precarga de modelos:', error);
this.handleError('MODEL_LOAD_EXCEPTION', error);
} finally {
this.preloadBtn.disabled = false;
}
}
setupEventListeners() {
this.jaakStamps.addEventListener('isReady', (event) => {
this.onComponentReady(event.detail);
});
this.jaakStamps.addEventListener('captureCompleted', (event) => {
this.onCaptureCompleted(event.detail);
});
}
async onComponentReady(isReady) {
this.isReady = isReady;
if (isReady) {
this.updateStatus('Componente listo', 'ready');
// Configurar cámara preferida si es necesario
await this.configureCameraSettings();
// Habilitar interfaz de usuario
this.enableUserInterface();
} else {
this.updateStatus('Error en inicialización', 'error');
}
}
async configureCameraSettings() {
try {
const cameraInfo = await this.jaakStamps.getCameraInfo();
if (cameraInfo.isMultipleCamerasAvailable) {
this.showCameraSelector(cameraInfo.availableCameras);
}
// Configurar cámara preferida basada en tipo de dispositivo
if (cameraInfo.deviceType === 'mobile') {
await this.jaakStamps.setPreferredCamera('back');
}
document.getElementById('cameraStatus').textContent =
cameraInfo.selectedCameraId ? 'Sí' : 'No';
} catch (error) {
console.error('Error al configurar cámara:', error);
this.handleError('CAMERA_CONFIG_ERROR', error);
}
}
async startCaptureProcess() {
try {
this.clearError();
const status = await this.jaakStamps.getStatus();
if (!status.isModelPreloaded) {
throw new Error('Los modelos no están cargados. Precargar primero.');
}
this.updateStatus('Iniciando captura...');
this.startBtn.disabled = true;
await this.jaakStamps.startCapture();
this.onCaptureStarted();
} catch (error) {
console.error('Error al iniciar captura:', error);
this.handleError('CAPTURE_START_ERROR', error);
this.startBtn.disabled = false;
}
}
async onCaptureCompleted(capturedData) {
try {
this.updateStatus('Procesando imágenes...');
// Validar imágenes capturadas
if (!this.validateCapturedImages(capturedData)) {
throw new Error('Imágenes capturadas no son válidas');
}
// Procesar imágenes
const processedData = await this.processImages(capturedData);
// Mostrar resultados
this.displayResults(processedData);
// Opcional: Enviar al servidor
// const serverResponse = await this.uploadToServer(processedData);
this.updateStatus('Captura completada', 'ready');
this.onProcessingComplete(processedData);
} catch (error) {
console.error('Error en procesamiento:', error);
this.handleError('PROCESSING_ERROR', error);
}
}
validateCapturedImages(data) {
return data.metadata.processCompleted &&
data.front.fullFrame &&
(data.back.fullFrame || data.metadata.backCaptureSkipped);
}
async processImages(data) {
// Aplicar procesamiento adicional si es necesario
return {
...data,
processedAt: new Date().toISOString(),
clientId: this.getClientId(),
sessionId: this.getSessionId()
};
}
async uploadToServer(data) {
const response = await fetch('/api/document-verification', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.getAuthToken()}`
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`Server error: ${response.status}`);
}
return response.json();
}
async resetCapture() {
try {
await this.jaakStamps.resetCapture();
this.clearResults();
this.clearError();
this.updateStatus('Reiniciado', 'ready');
this.startBtn.disabled = false;
document.getElementById('captureStep').textContent = '-';
} catch (error) {
console.error('Error al reiniciar:', error);
this.handleError('RESET_ERROR', error);
}
}
async showStatus() {
try {
const status = await this.jaakStamps.getStatus();
const cameraInfo = await this.jaakStamps.getCameraInfo();
document.getElementById('generalStatus').textContent =
this.isReady ? 'Listo' : 'No listo';
document.getElementById('modelStatus').textContent =
status.isModelPreloaded ? 'Sí' : 'No';
document.getElementById('cameraStatus').textContent =
status.isVideoActive ? 'Sí' : 'No';
document.getElementById('captureStep').textContent =
status.captureStep || '-';
console.log('Estado completo:', { status, cameraInfo });
} catch (error) {
console.error('Error al obtener estado:', error);
}
}
async switchCamera(cameraId) {
try {
if (cameraId) {
await this.jaakStamps.setPreferredCamera(cameraId);
this.updateStatus('Cámara cambiada');
}
} catch (error) {
console.error('Error al cambiar cámara:', error);
this.handleError('CAMERA_SWITCH_ERROR', error);
}
}
showCameraSelector(cameras) {
this.cameraSelect.innerHTML = '';
cameras.forEach(camera => {
const option = document.createElement('option');
option.value = camera.id;
option.textContent = camera.label || `Cámara ${camera.id}`;
option.selected = camera.selected;
this.cameraSelect.appendChild(option);
});
this.cameraSelector.style.display = 'block';
}
displayResults(data) {
this.resultsContainer.innerHTML = `
<div class="results-section">
<div class="results-header">
<h3>Resultados de Captura</h3>
<small>Completado: ${data.processedAt}</small>
</div>
<div class="status-grid">
<div class="status-item">
<div class="status-label">Total de Imágenes</div>
<div class="status-value">${data.metadata.totalImages}</div>
</div>
<div class="status-item">
<div class="status-label">Proceso Completado</div>
<div class="status-value">${data.metadata.processCompleted ? 'Sí' : 'No'}</div>
</div>
<div class="status-item">
<div class="status-label">Reverso Omitido</div>
<div class="status-value">${data.metadata.backCaptureSkipped ? 'Sí' : 'No'}</div>
</div>
<div class="status-item">
<div class="status-label">ID de Sesión</div>
<div class="status-value">${data.sessionId}</div>
</div>
</div>
<div class="image-preview">
${data.front.fullFrame ? `
<div class="image-card">
<h4>Imagen Frontal</h4>
<img src="${data.front.fullFrame}" alt="Documento frente">
</div>
` : ''}
${data.back.fullFrame ? `
<div class="image-card">
<h4>Imagen Trasera</h4>
<img src="${data.back.fullFrame}" alt="Documento reverso">
</div>
` : ''}
</div>
</div>
`;
}
handleError(errorType, error) {
this.currentError = { type: errorType, message: error.message || error };
console.error(`${errorType}:`, error);
this.showErrorMessage(errorType, error.message || error);
this.updateStatus('Error', 'error');
// Reportar error a servicio de monitoreo
this.reportError(errorType, error);
}
showErrorMessage(type, message) {
this.errorContainer.innerHTML = `
<div class="error-message">
<strong>Error (${type}):</strong> ${message}
<button onclick="document.getElementById('errorContainer').innerHTML = ''"
style="float: right; background: none; border: none; font-size: 18px; cursor: pointer;">×</button>
</div>
`;
}
clearError() {
this.errorContainer.innerHTML = '';
this.currentError = null;
}
clearResults() {
this.resultsContainer.innerHTML = '';
}
updateStatus(message, type = 'info') {
const statusElement = document.getElementById('generalStatus');
statusElement.textContent = message;
statusElement.className = type === 'ready' ? 'status-ready' :
type === 'error' ? 'status-error' : '';
}
// Métodos auxiliares
onModelsReady() {
console.log('Modelos listos para usar');
}
enableUserInterface() {
this.startBtn.disabled = false;
this.resetBtn.disabled = false;
console.log('Interfaz de usuario habilitada');
}
onCaptureStarted() {
console.log('Captura iniciada exitosamente');
this.updateStatus('Capturando documento...');
}
onProcessingComplete(response) {
console.log('Procesamiento completado:', response);
this.startBtn.disabled = false;
}
reportError(type, error) {
// Implementar envío a servicio de monitoreo
console.log('Reportando error:', { type, error, timestamp: new Date().toISOString() });
}
getClientId() {
return localStorage.getItem('clientId') || 'client-' + Date.now();
}
getSessionId() {
return 'session-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}
getAuthToken() {
return localStorage.getItem('authToken') || 'demo-token';
}
}
// Inicializar el manager cuando el DOM esté listo
document.addEventListener('DOMContentLoaded', () => {
window.captureManager = new DocumentCaptureManager('documentCapture');
});
</script>
</body>
</html>b) Funcionalidades incluidas en el ejemplo avanzado:
- Interfaz completa: Botones para precargar modelos, iniciar captura, reiniciar y consultar estado
- Selector de cámara: Cambio dinámico entre cámaras disponibles
- Panel de estado: Monitoreo en tiempo real del estado del componente
- Manejo de errores: Captura y visualización de errores con tipos específicos
- Visualización de resultados: Muestra las imágenes capturadas y metadata
- Estilos completos: CSS moderno y responsive
- Validación de datos: Verificación de imágenes capturadas antes del procesamiento
- Gestión de sesiones: IDs únicos para cliente y sesión
- Integración con servidor: Estructura preparada para envío a APIs backend
2.4 Referencias/Métodos
2.4.1 Especificación principal
WebComponent - jaak-stamps
Descripción: El componente jaak-stamps es un WebComponent especializado en la captura automatizada de documentos de identidad. Utiliza tecnología de visión por computadora con modelos de inteligencia artificial para detectar, alinear y capturar documentos de forma precisa. Está optimizado para funcionar en dispositivos móviles y de escritorio, con detección automática de capacidades del dispositivo.
2.4.2 Propiedades de entrada
Propiedades de Licencia (Requeridas)
| Propiedad | Tipo | Requerido | Descripción | Ejemplo | Valor por defecto |
|---|---|---|---|---|---|
license | string | Sí | Clave de licencia para autenticación del componente | license="your-key" | undefined |
license-environment | string | No | Ambiente para validación de licencia: 'dev', 'qa', 'sandbox', 'prod' | license-environment="prod" | 'prod' |
app-id | string | No | Identificador de aplicación para validación y análisis | app-id="my-app" | 'jaak-stamps-web' |
trace-id | string | No | ID de traza opcional para tracking distribuido (se auto-genera si no se proporciona) | trace-id="abc123" | undefined |
Propiedades de Configuración Base
| Propiedad | Tipo | Requerido | Descripción | Ejemplo | Valor por defecto |
|---|---|---|---|---|---|
debug | boolean | No | Activa el modo debug con métricas de rendimiento y cajas de detección visibles | debug="true" | false |
alignment-tolerance | number | No | Tolerancia en píxeles para la detección de alineación del documento | alignment-tolerance="15" | 15 |
mask-size | number | No | Tamaño de la máscara de captura como porcentaje (50-100) | mask-size="85" | 90 |
crop-margin | number | No | Margen adicional para el recorte de imagen en píxeles (0-100) | crop-margin="5" | 20 |
use-document-classification | boolean | No | Habilita la clasificación automática de tipos de documento | use-document-classification="true" | false |
use-document-detector | boolean | No | Activa/desactiva el modelo de detección automática de documentos | use-document-detector="true" | true |
preferred-camera | string | No | Preferencia de cámara a usar: 'auto', 'front', 'back' | preferred-camera="back" | 'auto' |
capture-delay | number | No | Tiempo de espera en milisegundos antes de capturar después de detectar alineación (0-10000) | capture-delay="2000" | 1500 |
enable-back-document-timer | boolean | No | Habilita el temporizador automático para saltar la captura del reverso | enable-back-document-timer="true" | false |
back-document-timer-duration | number | No | Duración en segundos del temporizador para saltar reverso automáticamente | back-document-timer-duration="30" | 20 |
Nota: El modelo implementado en la clasificación de documentos (
use-document-classification) aún se encuentra en fase Beta por lo que podría presentar fallas en la exactitud de la detección.
Propiedades de Telemetría y OpenTelemetry
| Propiedad | Tipo | Requerido | Descripción | Ejemplo | Valor por defecto |
|---|---|---|---|---|---|
telemetry-collector-url | string | No | URL del recolector OTLP para trazas distribuidas | telemetry-collector-url="https://..." | 'https://collector.jaak.ai/v1/traces' |
metrics-collector-url | string | No | URL del recolector OTLP para métricas | metrics-collector-url="https://..." | 'https://collector.jaak.ai/v1/metrics' |
enable-telemetry | boolean | No | Habilita envío de trazas distribuidas a OpenTelemetry | enable-telemetry="true" | true |
enable-metrics | boolean | No | Habilita envío de métricas a OpenTelemetry | enable-metrics="true" | true |
metrics-export-interval-millis | number | No | Intervalo de exportación de métricas en milisegundos | metrics-export-interval-millis="60000" | 60000 |
propagate-trace-header-cors-urls | string | No | URLs (separadas por coma) para propagar headers W3C Trace Context | propagate-trace-header-cors-urls="https://api.example.com" | undefined |
Propiedades de Contexto
| Propiedad | Tipo | Requerido | Descripción | Ejemplo | Valor por defecto |
|---|---|---|---|---|---|
customer-id | string | No | ID del cliente para telemetría y análisis | customer-id="customer123" | undefined |
tenant-id | string | No | ID del tenant para multi-tenancy | tenant-id="tenant456" | undefined |
environment | string | No | Ambiente de ejecución: 'development', 'staging', 'production' | environment="production" | 'production' |
2.4.3 Métodos públicos
| Método | Parámetros | Retorno | Descripción |
|---|---|---|---|
startCapture() | - | Promise<void> | Inicia el proceso de captura de documentos |
stopCapture() | - | Promise<void> | Detiene el proceso de captura actual y finaliza la sesión |
resetCapture() | - | Promise<void> | Reinicia el proceso de captura al estado inicial |
skipBackCapture() | - | Promise<void> | Omite la captura del reverso del documento |
preloadModel() | - | Promise<{success: boolean, message?: string, error?: string}> | Precarga los modelos de detección y clasificación |
getCapturedImages() | - | Promise<CapturedImages> | Obtiene las imágenes capturadas (solo disponible después de completar) |
getStatus() | - | Promise<ComponentStatus> | Obtiene el estado actual del componente |
getCameraInfo() | - | Promise<CameraInfo> | Obtiene información sobre las cámaras disponibles |
setPreferredCamera(camera) | camera: 'auto' | 'front' | 'back' | Promise<{success: boolean, selectedCamera: string | null, availableCameras: number}> | Configura la cámara preferida |
isProcessCompleted() | - | Promise<boolean> | Verifica si el proceso de captura ha sido completado |
setCaptureDelay(delay) | delay: number | Promise<{success: boolean, captureDelay: number}> | Configura el tiempo de espera antes de capturar (0-10000ms) |
getCaptureDelay() | - | Promise<number> | Obtiene el tiempo de espera configurado para la captura |
Tipos de datos de retorno:
interface CapturedImages {
front: {
fullFrame: string | null; // Imagen completa en base64
cropped: string | null; // Imagen recortada en base64
};
back: {
fullFrame: string | null;
cropped: string | null;
};
metadata: {
totalImages: number;
processCompleted: boolean;
backCaptureSkipped: boolean;
};
timestamp?: string;
}
interface ComponentStatus {
isVideoActive: boolean;
captureStep: 'front' | 'back' | 'completed';
hasImages: boolean;
isProcessCompleted: boolean;
isModelPreloaded: boolean;
componentStatus?: 'initializing' | 'loading' | 'ready' | 'error';
componentMessage?: string;
}
interface CameraInfo {
availableCameras: Array<{id: string, label: string, selected: boolean}>;
selectedCameraId: string | null;
deviceType: 'mobile' | 'desktop';
isMultipleCamerasAvailable: boolean;
preferredFacing: 'environment' | 'user' | null;
}2.4.4 Eventos
| Evento | Tipo de dato | Descripción |
|---|---|---|
isReady | CustomEvent<boolean> | Se dispara cuando el componente está completamente inicializado y listo para usar |
captureCompleted | CustomEvent<CapturedImages> | Se dispara cuando se completa el proceso de captura con las imágenes resultantes |
traceIdGenerated | CustomEvent<{traceId: string}> | Se dispara después de la validación de licencia con el ID de traza generado o proporcionado para tracking de peticiones |
Ejemplo de manejo de eventos:
const jaakStamps = document.getElementById('documentCapture');
jaakStamps.addEventListener('isReady', (event) => {
console.log('Componente listo:', event.detail); // boolean
if (event.detail) {
// El componente está listo para usar
enableCaptureButton();
}
});
jaakStamps.addEventListener('captureCompleted', (event) => {
const images = event.detail; // CapturedImages
console.log('Captura completada:', images);
// Procesar imágenes capturadas
processDocumentImages(images);
});
jaakStamps.addEventListener('traceIdGenerated', (event) => {
const traceId = event.detail.traceId;
console.log('Trace ID generado:', traceId);
// Usar para correlacionar peticiones al backend
});2.5 Telemetría y Observabilidad (OpenTelemetry)
El WebComponent Jaak Stamps incluye integración completa con OpenTelemetry para proporcionar observabilidad, trazabilidad y monitoreo de rendimiento en producción.
2.5.1 Características de Telemetría
Trazas Distribuidas (Distributed Tracing)
El componente implementa trazado distribuido siguiendo el estándar W3C Trace Context, permitiendo:
- Seguimiento completo del flujo de captura de documentos
- Correlación de peticiones entre frontend y backend
- Identificación de cuellos de botella y problemas de rendimiento
- Propagación de contexto mediante headers
traceparent
Spans registrados automáticamente:
component.initialize- Inicialización del componentecapture.start- Inicio del proceso de capturamodel.preload- Precarga de modelos IAmodel.detection.load- Carga del modelo de detecciónmodel.classification.load- Carga del modelo de clasificación
Métricas de Rendimiento
El componente exporta métricas detalladas a OpenTelemetry:
Contadores (Counters):
capture.counter- Número de capturas completadaserror.counter- Conteo de errores por tipomodel.load.counter- Conteo de cargas de modelouser.interaction.counter- Interacciones del usuario
Histogramas (Latencias):
capture.latency- Tiempo de captura completomodel.load.latency- Tiempo de carga de modelosdetection.latency- Tiempo de detección por frameimage.size- Tamaño de imágenes capturadas
Gauges (Estado Actual):
active.sessions- Sesiones activasmemory.usage- Uso de memoria
2.5.2 Configuración de Telemetría
<jaak-stamps
license="your-license-key"
enable-telemetry="true"
enable-metrics="true"
telemetry-collector-url="https://collector.jaak.ai/v1/traces"
metrics-collector-url="https://collector.jaak.ai/v1/metrics"
metrics-export-interval-millis="60000"
propagate-trace-header-cors-urls="https://api.example.com,https://backend.example.com"
customer-id="customer123"
tenant-id="tenant456"
environment="production"
trace-id="optional-custom-trace-id">
</jaak-stamps>2.5.3 Uso del Trace ID
El componente genera automáticamente un ID de traza único para cada sesión, o puede recibir uno personalizado:
const jaakStamps = document.getElementById('documentCapture');
// Escuchar el evento traceIdGenerated
jaakStamps.addEventListener('traceIdGenerated', (event) => {
const traceId = event.detail.traceId;
console.log('Trace ID para esta sesión:', traceId);
// Usar este traceId para correlacionar peticiones en tu backend
fetch('/api/process-document', {
method: 'POST',
headers: {
'X-Trace-Id': traceId,
'Content-Type': 'application/json'
},
body: JSON.stringify({ /* datos */ })
});
});2.5.4 Atributos Enriquecidos
Las trazas y métricas incluyen automáticamente:
- User Agent: Navegador, versión, sistema operativo
- Geolocalización: País, región, ciudad (cuando está disponible)
- Device Memory: Capacidad de memoria del dispositivo
- Customer Context:
customerId,tenantId,environment - Service Info: Nombre del servicio, versión del componente
2.5.5 Propagación de Contexto (CORS)
Para que las trazas se propaguen a tus servicios backend:
<jaak-stamps
propagate-trace-header-cors-urls="https://api.example.com,https://services.example.com">
</jaak-stamps>Esto configurará automáticamente los interceptores de fetch y XMLHttpRequest para incluir el header traceparent en las peticiones a esas URLs.
2.5.6 Visualización con Jaeger/Zipkin
Las trazas exportadas son compatibles con:
- Jaeger - Sistema de trazado distribuido
- Zipkin - Sistema alternativo de trazado
- Grafana Tempo - Backend de trazas de Grafana
- Cualquier backend compatible con OTLP (OpenTelemetry Protocol)
2.6 Validación de Licencia
El componente requiere una licencia válida para funcionar. La validación se realiza automáticamente al cargar el componente.
2.6.1 Configuración de Licencia
<jaak-stamps
license="your-license-key-here"
license-environment="prod"
app-id="my-application">
</jaak-stamps>2.6.2 Ambientes Disponibles
| Ambiente | URL de Validación | Uso |
|---|---|---|
dev | https://api.dev.jaak.ai | Desarrollo local |
qa | https://api.qa.jaak.ai | Pruebas de calidad |
sandbox | https://api.sandbox.jaak.ai | Pruebas de integración |
prod | https://services.api.jaak.ai | Producción |
2.6.3 Manejo de Errores de Licencia
Si la licencia no es válida, el componente mostrará un error y no funcionará:
const jaakStamps = document.getElementById('documentCapture');
jaakStamps.addEventListener('isReady', (event) => {
if (!event.detail) {
console.error('Error: Licencia inválida o componente no inicializado');
}
});Mensajes de error comunes:
"License key is required"- No se proporcionó licencia"Invalid license key"- Licencia inválida o expirada"License validation failed"- Error en la validación
2.7 Componentes adicionales
El WebComponent Jaak Stamps incluye los siguientes módulos complementarios:
Servicios de Core
- CameraService: Manejo de múltiples cámaras, detección de dispositivos, y optimización de resolución
- DetectionService: Carga de modelos ONNX, inferencia en tiempo real, y clasificación de documentos
- StateManagerService: Gestión del flujo de captura multi-etapa (frente → reverso → completado)
- EventBusService: Sistema de comunicación entre componentes con eventos tipados
- LoggerService: Sistema de logging con diferentes niveles (debug, info, warn, error)
- ServiceContainer: Contenedor de inyección de dependencias para gestión centralizada de servicios
Estrategias de Dispositivo
- HighPerformanceDeviceStrategy: Estrategia optimizada para dispositivos de alto rendimiento
- LowMemoryDeviceStrategy: Estrategia optimizada para dispositivos con recursos limitados
- DeviceStrategyFactory: Factory para seleccionar automáticamente la estrategia apropiada
Interfaces y Contratos
- ICameraService: Contrato para servicios de manejo de cámara
- IDetectionService: Contrato para servicios de detección de documentos
- IStateManager: Contrato para gestión de estado de captura
- IEventBus: Contrato para sistema de eventos
- ILogger: Contrato para servicios de logging
Utilidades de Interfaz
- Selector de cámaras: Interfaz desplegable para cambio de cámara en tiempo real
- Monitor de rendimiento: Métricas en tiempo real (FPS, memoria, tiempo de inferencia) - solo en modo debug
- Animaciones de estado: Feedback visual para captura, volteo de documento, y éxito
- Máscara de alineación: Guía visual para posicionamiento correcto del documento
2.8 Pruebas y validación
a) Casos de prueba
| Caso de Prueba | Entrada/Configuración | Resultado Esperado | Criterio de Éxito |
|---|---|---|---|
| Inicialización básica | <jaak-stamps></jaak-stamps> | Componente se inicializa con configuración por defecto | Evento isReady se dispara con true |
| Captura frontal exitosa | Documento ID visible en cámara por >1 segundo | Imagen frontal capturada y transición a captura trasera | Animación "voltea tu identificación" aparece |
| Captura completa | Captura exitosa de frente y reverso | Proceso completado con ambas imágenes | Evento captureCompleted con metadata correcta |
| Detección de pasaporte | use-document-classification="true" con pasaporte | Salta captura de reverso automáticamente | backCaptureSkipped: true en metadata |
| Cambio de cámara | Dispositivo con múltiples cámaras | Interfaz de selección disponible y funcional | Cambio exitoso sin interrumpir captura |
| Modo debug | debug="true" | Métricas de rendimiento y cajas de detección visibles | Monitor de performance y overlay de detección activos |
| Manejo de errores | Sin permisos de cámara | Error manejado graciosamente | Estado de error mostrado al usuario |
2.9 Solución de problemas
a) Problemas comunes
| Problema: | El componente no se inicializa correctamente |
|---|---|
| Descripción: | El evento isReady nunca se dispara o se dispara con false |
| Causas posibles: | 1. Falta de permisos de cámara 2. Navegador no compatible 3. Conexión no HTTPS 4. Error en descarga de modelos 5. Licencia inválida o no proporcionada |
| Solución: | 1. Verificar que la página esté servida sobre HTTPS 2. Confirmar permisos de cámara en el navegador 3. Verificar conectividad a internet 4. Revisar consola del navegador para errores 5. Validar que la licencia sea correcta |
| Problema: | La detección de documentos es muy lenta |
|---|---|
| Descripción: | El procesamiento de frames toma >200ms causando lag en la interfaz |
| Causas posibles: | 1. Dispositivo con poca memoria 2. Múltiples pestañas/aplicaciones abiertas 3. Resolución de cámara muy alta |
| Solución: | 1. Cerrar aplicaciones innecesarias 2. Usar debug="true" para monitorear rendimiento3. Considerar reducir maskSize para menos procesamiento |
| Problema: | Las imágenes capturadas están cortadas o mal alineadas |
|---|---|
| Descripción: | Las imágenes resultantes no incluyen todo el documento |
| Causas posibles: | 1. cropMargin muy pequeño2. alignmentTolerance muy estricto3. Movimiento durante captura |
| Solución: | 1. Incrementar cropMargin (ej: 10-20)2. Aumentar alignmentTolerance (ej: 15-20)3. Instruir al usuario a mantener el documento quieto |
b) Códigos de error específicos
| Código | Descripción | Causa | Solución |
|---|---|---|---|
Camera permission error | Permisos de cámara denegados | Usuario rechazó permisos o política del navegador | Mostrar instrucciones para habilitar permisos manualmente |
Camera with ID X not found | Cámara específica no encontrada | Dispositivo sin cámara o cámara desconectada | Verificar disponibilidad de cámara, usar cámara automática |
Error al enumerar cámaras | No se pudieron listar cámaras | Permisos insuficientes o navegador no compatible | Verificar permisos y soporte del navegador |
Error al cargar preferencia | Fallo al cargar configuración guardada | Datos corruptos en localStorage | Limpiar localStorage o usar configuración por defecto |
Error al guardar preferencia | Fallo al guardar configuración | Problemas de almacenamiento local | Verificar capacidad de almacenamiento del navegador |
License key is required | No se proporcionó licencia | Falta el atributo license | Agregar licencia válida al componente |
Invalid license key | Licencia inválida | Licencia incorrecta o expirada | Contactar a soporte para obtener licencia válida |
Contacta al equipo de soporte ([email protected]) cuando:
- Los pasos de troubleshooting no resuelven el problema
- Recibes errores no documentados
- Necesitas configuraciones especiales para tu caso de uso
- Experimentas problemas de rendimiento persistentes
Información a incluir: Logs del navegador, configuración del componente, pasos para reproducir el problema, tipo de dispositivo y navegador
2.10 Consideraciones importantes
a) Seguridad
- Privacidad de datos: Las imágenes se procesan localmente en el navegador, no se envían automáticamente a servidores externos
- Permisos de cámara: Siempre solicitar permisos explícitos del usuario antes de acceder a la cámara
- HTTPS obligatorio: El componente requiere HTTPS para acceso a MediaStream API
- Validación de entrada: Validar propiedades del componente para evitar configuraciones inseguras
- Manejo de errores: Implementar manejo robusto de errores para evitar exposición de información sensible
b) Rendimiento
- Optimización automática: El componente detecta automáticamente las capacidades del dispositivo y ajusta el rendimiento
- Precarga de modelos: Usar
preloadModel()para mejorar la experiencia del usuario - Gestión de memoria: Los modelos se descargan una sola vez y se reutilizan
- Frame skipping: Implementado automáticamente para mantener fluidez en dispositivos lentos
c) Compatibilidad
- Navegadores soportados: Chrome 67+, Firefox 63+, Safari 12+, Edge 79+
- Dispositivos móviles: Optimizado para iOS Safari y Android Chrome
- Resolución adaptativa: Se ajusta automáticamente a diferentes tamaños de pantalla
- WebComponents nativos: No requiere polyfills en navegadores modernos
d) Flujo de Trabajo
Flujo Estándar (useDocumentClassification = false)
- Inicialización: El componente se prepara para la detección
- Iniciar captura: El modelo de detección se carga automáticamente
- Captura frontal: El usuario posiciona el documento y se captura automáticamente
- Solicitud de reverso: Siempre solicita voltear el documento para captura del reverso
- Botón omitir reverso: Disponible para omitir manualmente la captura del reverso
- Completado: Emite evento
captureCompletedcon las imágenes correspondientes
Flujo de Clasificación Inteligente (useDocumentClassification = true)
- Inicialización: El componente se prepara para la detección
- Iniciar captura: Los modelos de detección y clasificación se cargan automáticamente
- Captura frontal: El usuario posiciona el documento y se captura automáticamente
- Clasificación automática: El sistema determina si el documento requiere captura del reverso
- Flujo adaptativo:
- Si es pasaporte: El proceso se completa automáticamente (sin reverso)
- Si es otro documento: Solicita voltear el documento para captura del reverso
- Completado: Emite evento
captureCompletedcon las imágenes correspondientes
Flujo de Precarga (Recomendado)
- Precarga de modelo: Llamar a
preloadModel()para cargar modelos en memoria - Inicio optimizado: Al iniciar captura, utiliza modelos ya cargados
- Captura frontal: Detección y clasificación más rápida con modelos precargados
- Flujo inteligente:
- Documento sin reverso: Proceso completado inmediatamente
- Documento con reverso: Solicita captura del reverso
- Completado: Emite evento
captureCompletedcon todas las imágenes
3. Anexos
Anexo A. Glosario de términos
| Término | Definición |
|---|---|
| ONNX | Open Neural Network Exchange - Formato estándar para modelos de machine learning |
| WebComponent | Estándar web que permite crear elementos HTML personalizados reutilizables |
| MediaStream API | API del navegador para acceso a cámaras y micrófonos |
| Shadow DOM | Tecnología que permite encapsulación de estilos y comportamiento en WebComponents |
| Inference | Proceso de ejecutar un modelo de IA para obtener predicciones |
| Detection Box | Rectángulo que define la ubicación de un documento detectado en la imagen |
| Alignment Tolerance | Tolerancia en píxeles para considerar un documento como correctamente alineado |
| Crop Margin | Margen adicional alrededor del documento detectado al recortar la imagen |
| OpenTelemetry | Framework de observabilidad para instrumentación, generación y exportación de datos de telemetría |
| OTLP | OpenTelemetry Protocol - Protocolo estándar para transmisión de datos de telemetría |
| Trace Context | Estándar W3C para propagación de contexto de trazas distribuidas |
Updated 10 days ago
