@jaak.ai/stamps 2.0.0-beta.3 → 2.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +665 -514
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +1991 -1057
  4. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  5. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  6. package/dist/cjs/loader.cjs.js +1 -1
  7. package/dist/collection/components/my-component/my-component.css +483 -123
  8. package/dist/collection/components/my-component/my-component.js +915 -1120
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/CameraService.js +590 -0
  11. package/dist/collection/services/CameraService.js.map +1 -0
  12. package/dist/collection/services/DetectionService.js +387 -0
  13. package/dist/collection/services/DetectionService.js.map +1 -0
  14. package/dist/collection/services/EventBusService.js +42 -0
  15. package/dist/collection/services/EventBusService.js.map +1 -0
  16. package/dist/collection/services/LoggerService.js +40 -0
  17. package/dist/collection/services/LoggerService.js.map +1 -0
  18. package/dist/collection/services/ServiceContainer.js +66 -0
  19. package/dist/collection/services/ServiceContainer.js.map +1 -0
  20. package/dist/collection/services/StateManagerService.js +109 -0
  21. package/dist/collection/services/StateManagerService.js.map +1 -0
  22. package/dist/collection/services/factories/DeviceStrategyFactory.js +16 -0
  23. package/dist/collection/services/factories/DeviceStrategyFactory.js.map +1 -0
  24. package/dist/collection/services/interfaces/ICameraService.js +2 -0
  25. package/dist/collection/services/interfaces/ICameraService.js.map +1 -0
  26. package/dist/collection/services/interfaces/IDetectionService.js +2 -0
  27. package/dist/collection/services/interfaces/IDetectionService.js.map +1 -0
  28. package/dist/collection/services/interfaces/IEventBus.js +2 -0
  29. package/dist/collection/services/interfaces/IEventBus.js.map +1 -0
  30. package/dist/collection/services/interfaces/ILogger.js +2 -0
  31. package/dist/collection/services/interfaces/ILogger.js.map +1 -0
  32. package/dist/collection/services/interfaces/IStateManager.js +2 -0
  33. package/dist/collection/services/interfaces/IStateManager.js.map +1 -0
  34. package/dist/collection/services/strategies/HighPerformanceDeviceStrategy.js +30 -0
  35. package/dist/collection/services/strategies/HighPerformanceDeviceStrategy.js.map +1 -0
  36. package/dist/collection/services/strategies/IDeviceStrategy.js +2 -0
  37. package/dist/collection/services/strategies/IDeviceStrategy.js.map +1 -0
  38. package/dist/collection/services/strategies/LowMemoryDeviceStrategy.js +30 -0
  39. package/dist/collection/services/strategies/LowMemoryDeviceStrategy.js.map +1 -0
  40. package/dist/collection/types/component-types.js +2 -0
  41. package/dist/collection/types/component-types.js.map +1 -0
  42. package/dist/components/jaak-stamps.js +2011 -1082
  43. package/dist/components/jaak-stamps.js.map +1 -1
  44. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  45. package/dist/esm/jaak-stamps.entry.js +1991 -1057
  46. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  47. package/dist/esm/loader.js +1 -1
  48. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  49. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  50. package/dist/jaak-stamps-webcomponent/p-9ed19fa4.entry.js +2 -0
  51. package/dist/jaak-stamps-webcomponent/p-9ed19fa4.entry.js.map +1 -0
  52. package/dist/types/components/my-component/my-component.d.ts +89 -108
  53. package/dist/types/components.d.ts +39 -9
  54. package/dist/types/services/CameraService.d.ts +47 -0
  55. package/dist/types/services/DetectionService.d.ts +35 -0
  56. package/dist/types/services/EventBusService.d.ts +9 -0
  57. package/dist/types/services/LoggerService.d.ts +12 -0
  58. package/dist/types/services/ServiceContainer.d.ts +27 -0
  59. package/dist/types/services/StateManagerService.d.ts +15 -0
  60. package/dist/types/services/factories/DeviceStrategyFactory.d.ts +4 -0
  61. package/dist/types/services/interfaces/ICameraService.d.ts +34 -0
  62. package/dist/types/services/interfaces/IDetectionService.d.ts +31 -0
  63. package/dist/types/services/interfaces/IEventBus.d.ts +16 -0
  64. package/dist/types/services/interfaces/ILogger.d.ts +8 -0
  65. package/dist/types/services/interfaces/IStateManager.d.ts +35 -0
  66. package/dist/types/services/strategies/HighPerformanceDeviceStrategy.d.ts +6 -0
  67. package/dist/types/services/strategies/IDeviceStrategy.d.ts +21 -0
  68. package/dist/types/services/strategies/LowMemoryDeviceStrategy.d.ts +6 -0
  69. package/dist/types/types/component-types.d.ts +36 -0
  70. package/package.json +3 -3
  71. package/dist/jaak-stamps-webcomponent/p-14eb13d8.entry.js +0 -2
  72. package/dist/jaak-stamps-webcomponent/p-14eb13d8.entry.js.map +0 -1
package/README.md CHANGED
@@ -1,610 +1,761 @@
1
- # Jaak Stamps - Document Detector Web Component
1
+ # Jaak Stamps Web Component
2
2
 
3
- Un componente web para la detección y captura automática de documentos de identificación usando visión artificial y redes neuronales.
3
+ [![npm version](https://badge.fury.io/js/%40jaak.ai%2Fstamps.svg)](https://www.npmjs.com/package/@jaak.ai/stamps)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## Características
6
+ [📖 Versión en Español](./README_ES.md)
6
7
 
7
- - 📷 **Detección automática** de documentos de identificación en tiempo real
8
- - 🎯 **Guía visual** para posicionamiento óptimo del documento
9
- - 🤖 **Clasificación inteligente** de documentos para determinar si requieren reverso
10
- - 📋 **Captura adaptativa** - automáticamente detecta si el documento tiene reverso
11
- - 🖼 **Doble salida** para cada lado: imagen completa y recorte del documento
12
- - ✂️ **Recorte configurable** con margen personalizable alrededor del documento
13
- - 🔧 **API programática** para control total del proceso
14
- - 📱 **Responsive** y compatible con dispositivos móviles
15
- - ⚡ **Optimización automática** para mejor rendimiento
8
+ ## Description
16
9
 
17
- ## Instalación
10
+ Jaak Stamps is a powerful web component for document scanning and capture using camera input with AI-powered document detection. The component provides a complete solution for capturing high-quality document images with real-time detection feedback and automated cropping capabilities.
11
+
12
+ ### Key Features
13
+
14
+ - **AI-Powered Document Detection**: Automatically detects and frames documents in the camera view
15
+ - **Dual-Side Capture**: Supports both front and back document capture workflow
16
+ - **Real-time Preview**: Live camera feed with detection overlays and guidance
17
+ - **Multi-Camera Support**: Automatic camera selection with manual override options
18
+ - **Smart Cropping**: Automatic document boundary detection and cropping
19
+ - **Mobile Optimized**: Responsive design that works across all device sizes
20
+ - **Web Standards Compliant**: Built as a standard Web Component for maximum compatibility
21
+
22
+ ### Typical Use Cases
23
+
24
+ - **Identity Verification**: Capture ID cards, passports, and driver's licenses
25
+ - **Document Digitization**: Scan contracts, certificates, and legal documents
26
+ - **KYC/AML Processes**: Streamlined identity document capture for compliance
27
+ - **Form Processing**: Digitize paper forms and applications
28
+ - **Insurance Claims**: Capture damage reports and supporting documentation
29
+
30
+ ## Installation
31
+
32
+ ### NPM/Yarn
18
33
 
19
- ### NPM
20
34
  ```bash
35
+ # Using npm
21
36
  npm install @jaak.ai/stamps
22
- ```
23
37
 
24
- ### Yarn
25
- ```bash
38
+ # Using yarn
26
39
  yarn add @jaak.ai/stamps
27
40
  ```
28
41
 
29
42
  ### CDN
43
+
30
44
  ```html
45
+ <!-- ES Module -->
31
46
  <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
47
+
48
+ <!-- With Loader -->
49
+ <script type="module">
50
+ import { defineCustomElements } from 'https://unpkg.com/@jaak.ai/stamps/loader/index.js';
51
+ defineCustomElements();
52
+ </script>
53
+ ```
54
+
55
+ ## API Reference
56
+
57
+ ### Properties/Attributes
58
+
59
+ | Property | Type | Default | Description |
60
+ |----------|------|---------|-------------|
61
+ | `debug` | `boolean` | `false` | Enable debug mode with additional logging and overlays |
62
+ | `mask-size` | `number` | `80` | Size percentage of the document detection mask |
63
+ | `alignment-tolerance` | `number` | `15` | Tolerance level for document alignment detection |
64
+ | `capture-delay` | `number` | `1500` | Delay in milliseconds before automatic capture |
65
+ | `crop-margin` | `number` | `20` | Margin in pixels around detected document for cropping |
66
+ | `preferred-camera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Preferred camera selection |
67
+ | `use-document-classification` | `boolean` | `false` | Enable AI document classification |
68
+ | `enable-back-document-timer` | `boolean` | `false` | Enable timer for back document capture |
69
+ | `back-document-timer-duration` | `number` | `20` | Duration in seconds for back document capture timer |
70
+
71
+ ### Methods
72
+
73
+ #### Camera Control
74
+
75
+ ```typescript
76
+ // Get information about available cameras
77
+ getCameraInfo(): Promise<CameraInfoResponse>
78
+
79
+ // Set preferred camera
80
+ setPreferredCamera(camera: "auto" | "front" | "back"): Promise<{
81
+ success: boolean;
82
+ selectedCamera: string;
83
+ availableCameras: number;
84
+ }>
85
+
86
+ // Control device torch/flashlight
87
+ setTorchEnabled(enabled: boolean): Promise<{
88
+ success: boolean;
89
+ enabled: boolean;
90
+ }>
91
+
92
+ // Focus camera at specific point
93
+ focusAtPoint(x: number, y: number): Promise<{
94
+ success: boolean;
95
+ coordinates: { x: number; y: number };
96
+ }>
97
+ ```
98
+
99
+ #### Capture Control
100
+
101
+ ```typescript
102
+ // Start the capture process
103
+ startCapture(): Promise<void>
104
+
105
+ // Stop the capture process
106
+ stopCapture(): Promise<void>
107
+
108
+ // Reset capture state
109
+ resetCapture(): Promise<void>
110
+
111
+ // Skip back document capture
112
+ skipBackCapture(): Promise<void>
113
+ ```
114
+
115
+ #### Configuration
116
+
117
+ ```typescript
118
+ // Set capture delay
119
+ setCaptureDelay(delay: number): Promise<{
120
+ success: boolean;
121
+ captureDelay: number;
122
+ }>
123
+
124
+ // Get current capture delay
125
+ getCaptureDelay(): Promise<number>
126
+ ```
127
+
128
+ #### Status & Data
129
+
130
+ ```typescript
131
+ // Get current component status
132
+ getStatus(): Promise<StatusResponse>
133
+
134
+ // Get captured images
135
+ getCapturedImages(): Promise<CapturedImagesResponse>
136
+
137
+ // Check if process is completed
138
+ isProcessCompleted(): Promise<boolean>
139
+
140
+ // Preload AI model
141
+ preloadModel(): Promise<{
142
+ success: boolean;
143
+ message?: string;
144
+ error?: any;
145
+ }>
32
146
  ```
33
147
 
34
- ## Uso Básico
148
+ ### Events
149
+
150
+ | Event | Detail Type | Description |
151
+ |-------|-------------|-------------|
152
+ | `isReady` | `boolean` | Fired when component is fully initialized and ready |
153
+ | `captureCompleted` | `object` | Fired when document capture process is completed |
35
154
 
36
- ### HTML Vanilla
155
+ ### Type Definitions
156
+
157
+ ```typescript
158
+ interface CameraInfoResponse {
159
+ availableCameras: Array<{
160
+ id: string;
161
+ label: string;
162
+ selected: boolean;
163
+ }>;
164
+ selectedCameraId: string | null;
165
+ deviceType: string;
166
+ isMultipleCamerasAvailable: boolean;
167
+ preferredFacing: 'environment' | 'user' | null;
168
+ }
169
+
170
+ interface CapturedImagesResponse {
171
+ front: {
172
+ fullFrame: string | null;
173
+ cropped: string | null;
174
+ };
175
+ back: {
176
+ fullFrame: string | null;
177
+ cropped: string | null;
178
+ };
179
+ metadata: {
180
+ totalImages: number;
181
+ processCompleted: boolean;
182
+ backCaptureSkipped: boolean;
183
+ };
184
+ }
185
+
186
+ interface StatusResponse {
187
+ isVideoActive: boolean;
188
+ captureStep: 'front' | 'back' | 'completed';
189
+ hasImages: boolean;
190
+ isProcessCompleted: boolean;
191
+ isModelPreloaded: boolean;
192
+ }
193
+ ```
194
+
195
+ ## Implementation Examples
196
+
197
+ ### Vanilla JavaScript
37
198
 
38
199
  ```html
39
200
  <!DOCTYPE html>
40
201
  <html>
41
202
  <head>
42
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
43
- <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
203
+ <script type="module" src="https://unpkg.com/@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js"></script>
44
204
  </head>
45
205
  <body>
46
- <jaak-stamps id="detector" debug="false" mask-size="90" crop-margin="10" use-document-classification="false"></jaak-stamps>
206
+ <jaak-stamps
207
+ id="documentScanner"
208
+ preferred-camera="auto"
209
+ capture-delay="1500"
210
+ debug="false">
211
+ </jaak-stamps>
212
+
213
+ <script>
214
+ const scanner = document.getElementById('documentScanner');
47
215
 
48
- <button onclick="startCapture()">Iniciar Captura</button>
49
- <button onclick="stopCapture()">Detener</button>
50
- <button onclick="resetCapture()">Reiniciar</button>
51
-
52
- <script>
53
- const detector = document.getElementById('detector');
54
-
55
- // Escuchar evento de captura completada
56
- detector.addEventListener('captureCompleted', (event) => {
57
- console.log('Captura completada:', event.detail);
58
- // event.detail contiene las imágenes capturadas
59
- });
60
-
61
- // Escuchar evento de componente listo
62
- detector.addEventListener('isReady', (event) => {
63
- console.log('Componente listo:', event.detail);
64
- // event.detail indica si el componente está listo (true/false)
65
- });
66
-
67
- function startCapture() {
68
- detector.startCapture();
69
- }
70
-
71
- function stopCapture() {
72
- detector.stopCapture();
73
- }
74
-
75
- function resetCapture() {
76
- detector.resetCapture();
77
- }
78
-
79
- // Precargar el modelo (opcional - mejora el rendimiento)
80
- async function preloadModel() {
81
- try {
82
- const result = await detector.preloadModel();
83
- console.log('Modelo precargado:', result);
84
- } catch (error) {
85
- console.error('Error al precargar:', error);
86
- }
87
- }
88
-
89
- // Obtener imágenes después de la captura
90
- async function getImages() {
91
- try {
92
- const images = await detector.getCapturedImages();
93
- console.log('Imágenes capturadas:', images);
94
- } catch (error) {
95
- console.error('Error:', error.message);
96
- }
97
- }
98
- </script>
216
+ // Wait for component to be ready
217
+ scanner.addEventListener('isReady', () => {
218
+ console.log('Document scanner is ready');
219
+
220
+ // Start the capture process
221
+ scanner.startCapture();
222
+ });
223
+
224
+ // Handle capture completion
225
+ scanner.addEventListener('captureCompleted', async (event) => {
226
+ console.log('Capture completed:', event.detail);
227
+
228
+ // Get captured images
229
+ const images = await scanner.getCapturedImages();
230
+ console.log('Captured images:', images);
231
+
232
+ // Process the images as needed
233
+ if (images.front.cropped) {
234
+ // Display or upload the cropped front image
235
+ displayImage(images.front.cropped);
236
+ }
237
+ });
238
+
239
+ function displayImage(base64Image) {
240
+ const img = document.createElement('img');
241
+ img.src = `data:image/jpeg;base64,${base64Image}`;
242
+ document.body.appendChild(img);
243
+ }
244
+ </script>
99
245
  </body>
100
246
  </html>
101
247
  ```
102
248
 
103
- ### Angular
249
+ ### React
104
250
 
105
- #### 1. Instalar el componente
106
- ```bash
107
- npm install @jaak.ai/stamps
108
- ```
251
+ ```tsx
252
+ import React, { useEffect, useRef, useState } from 'react';
253
+ import { defineCustomElements } from '@jaak.ai/stamps/loader';
109
254
 
110
- #### 2. Configurar el módulo (app.module.ts)
111
- ```typescript
112
- import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
113
- import { BrowserModule } from '@angular/platform-browser';
114
- import { AppComponent } from './app.component';
255
+ // Define custom elements
256
+ defineCustomElements();
115
257
 
116
- // Importar el componente web
117
- import '@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js';
258
+ // Extend HTMLElement for TypeScript
259
+ declare global {
260
+ namespace JSX {
261
+ interface IntrinsicElements {
262
+ 'jaak-stamps': any;
263
+ }
264
+ }
265
+ }
118
266
 
119
- @NgModule({
120
- declarations: [AppComponent],
121
- imports: [BrowserModule],
122
- providers: [],
123
- bootstrap: [AppComponent],
124
- schemas: [CUSTOM_ELEMENTS_SCHEMA] // Permitir elementos personalizados
125
- })
126
- export class AppModule { }
267
+ interface CapturedImages {
268
+ front: { fullFrame: string | null; cropped: string | null };
269
+ back: { fullFrame: string | null; cropped: string | null };
270
+ metadata: {
271
+ totalImages: number;
272
+ processCompleted: boolean;
273
+ backCaptureSkipped: boolean;
274
+ };
275
+ }
276
+
277
+ const DocumentScanner: React.FC = () => {
278
+ const scannerRef = useRef<HTMLElement>(null);
279
+ const [isReady, setIsReady] = useState(false);
280
+ const [capturedImages, setCapturedImages] = useState<CapturedImages | null>(null);
281
+
282
+ useEffect(() => {
283
+ const scanner = scannerRef.current;
284
+ if (!scanner) return;
285
+
286
+ // Component ready handler
287
+ const handleReady = () => {
288
+ setIsReady(true);
289
+ };
290
+
291
+ // Capture completion handler
292
+ const handleCaptureCompleted = async () => {
293
+ const images = await (scanner as any).getCapturedImages();
294
+ setCapturedImages(images);
295
+ };
296
+
297
+ // Add event listeners
298
+ scanner.addEventListener('isReady', handleReady);
299
+ scanner.addEventListener('captureCompleted', handleCaptureCompleted);
300
+
301
+ return () => {
302
+ scanner.removeEventListener('isReady', handleReady);
303
+ scanner.removeEventListener('captureCompleted', handleCaptureCompleted);
304
+ };
305
+ }, []);
306
+
307
+ const startScan = async () => {
308
+ if (scannerRef.current) {
309
+ await (scannerRef.current as any).startCapture();
310
+ }
311
+ };
312
+
313
+ return (
314
+ <div>
315
+ <h2>Document Scanner</h2>
316
+
317
+ <jaak-stamps
318
+ ref={scannerRef}
319
+ preferred-camera="auto"
320
+ capture-delay={1500}
321
+ use-document-classification={true}
322
+ />
323
+
324
+ <div style={{ marginTop: '20px' }}>
325
+ <button onClick={startScan} disabled={!isReady}>
326
+ {isReady ? 'Start Scanning' : 'Loading...'}
327
+ </button>
328
+ </div>
329
+
330
+ {capturedImages && (
331
+ <div style={{ marginTop: '20px' }}>
332
+ <h3>Captured Images</h3>
333
+ {capturedImages.front.cropped && (
334
+ <div>
335
+ <h4>Front Document</h4>
336
+ <img
337
+ src={`data:image/jpeg;base64,${capturedImages.front.cropped}`}
338
+ alt="Front document"
339
+ style={{ maxWidth: '300px' }}
340
+ />
341
+ </div>
342
+ )}
343
+ {capturedImages.back.cropped && (
344
+ <div>
345
+ <h4>Back Document</h4>
346
+ <img
347
+ src={`data:image/jpeg;base64,${capturedImages.back.cropped}`}
348
+ alt="Back document"
349
+ style={{ maxWidth: '300px' }}
350
+ />
351
+ </div>
352
+ )}
353
+ </div>
354
+ )}
355
+ </div>
356
+ );
357
+ };
358
+
359
+ export default DocumentScanner;
127
360
  ```
128
361
 
129
- #### 3. Usar en el componente
362
+ ### Angular
363
+
130
364
  ```typescript
131
- // app.component.ts
132
- import { Component, ElementRef, ViewChild, OnInit } from '@angular/core';
365
+ // document-scanner.component.ts
366
+ import { Component, ElementRef, ViewChild, AfterViewInit, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
367
+ import { defineCustomElements } from '@jaak.ai/stamps/loader';
368
+
369
+ // Define custom elements
370
+ defineCustomElements();
371
+
372
+ interface CapturedImages {
373
+ front: { fullFrame: string | null; cropped: string | null };
374
+ back: { fullFrame: string | null; cropped: string | null };
375
+ metadata: {
376
+ totalImages: number;
377
+ processCompleted: boolean;
378
+ backCaptureSkipped: boolean;
379
+ };
380
+ }
133
381
 
134
382
  @Component({
135
- selector: 'app-root',
383
+ selector: 'app-document-scanner',
136
384
  template: `
137
- <div class="container">
138
- <h1>Detector de Documentos</h1>
385
+ <div class="scanner-container">
386
+ <h2>Document Scanner</h2>
139
387
 
140
- <jaak-stamps
141
- #detector
142
- [debug]="false"
143
- [maskSize]="90"
144
- [cropMargin]="10"
145
- [useDocumentClassification]="false"
146
- (captureCompleted)="onCaptureCompleted($event)"
147
- (isReady)="onComponentReady($event)">
388
+ <jaak-stamps
389
+ #scanner
390
+ preferred-camera="auto"
391
+ [capture-delay]="1500"
392
+ [use-document-classification]="true">
148
393
  </jaak-stamps>
149
394
 
150
395
  <div class="controls">
151
- <button (click)="preloadModel()">Precargar Modelo</button>
152
- <button (click)="startCapture()">Iniciar Captura</button>
153
- <button (click)="stopCapture()">Detener</button>
154
- <button (click)="resetCapture()">Reiniciar</button>
155
- <button (click)="getImages()" [disabled]="!isCompleted">Obtener Imágenes</button>
396
+ <button
397
+ (click)="startScan()"
398
+ [disabled]="!isReady"
399
+ class="scan-button">
400
+ {{ isReady ? 'Start Scanning' : 'Loading...' }}
401
+ </button>
402
+
403
+ <button
404
+ (click)="resetScan()"
405
+ [disabled]="!isReady"
406
+ class="reset-button">
407
+ Reset
408
+ </button>
156
409
  </div>
157
410
 
158
- <div *ngIf="capturedData" class="results">
159
- <h3>Imágenes Capturadas:</h3>
160
- <p>Total de imágenes: {{ capturedData.metadata.totalImages }}</p>
161
- <img *ngIf="capturedData.front.cropped" [src]="capturedData.front.cropped" alt="Frente">
162
- <img *ngIf="capturedData.back.cropped" [src]="capturedData.back.cropped" alt="Reverso">
411
+ <div *ngIf="capturedImages" class="results">
412
+ <h3>Captured Images</h3>
413
+
414
+ <div *ngIf="capturedImages.front.cropped" class="image-result">
415
+ <h4>Front Document</h4>
416
+ <img
417
+ [src]="'data:image/jpeg;base64,' + capturedImages.front.cropped"
418
+ alt="Front document"
419
+ class="captured-image">
420
+ </div>
421
+
422
+ <div *ngIf="capturedImages.back.cropped" class="image-result">
423
+ <h4>Back Document</h4>
424
+ <img
425
+ [src]="'data:image/jpeg;base64,' + capturedImages.back.cropped"
426
+ alt="Back document"
427
+ class="captured-image">
428
+ </div>
429
+
430
+ <div class="metadata">
431
+ <p>Total Images: {{ capturedImages.metadata.totalImages }}</p>
432
+ <p>Process Completed: {{ capturedImages.metadata.processCompleted }}</p>
433
+ </div>
163
434
  </div>
164
435
  </div>
165
436
  `,
166
437
  styles: [`
167
- .container { max-width: 800px; margin: 0 auto; padding: 20px; }
168
- .controls { margin: 20px 0; }
169
- .controls button { margin: 0 10px; padding: 10px 20px; }
170
- .results img { max-width: 300px; margin: 10px; border: 1px solid #ccc; }
171
- `]
172
- })
173
- export class AppComponent implements OnInit {
174
- @ViewChild('detector', { static: true }) detectorRef!: ElementRef;
175
-
176
- capturedData: any = null;
177
- isCompleted = false;
178
-
179
- ngOnInit() {
180
- // El elemento ya está disponible después de la vista
181
- }
182
-
183
- async preloadModel() {
184
- try {
185
- const result = await this.detectorRef.nativeElement.preloadModel();
186
- console.log('Modelo precargado:', result);
187
- } catch (error) {
188
- console.error('Error al precargar modelo:', error);
438
+ .scanner-container {
439
+ max-width: 600px;
440
+ margin: 0 auto;
441
+ padding: 20px;
189
442
  }
190
- }
191
-
192
- async startCapture() {
193
- try {
194
- await this.detectorRef.nativeElement.startCapture();
195
- } catch (error) {
196
- console.error('Error al iniciar captura:', error);
443
+
444
+ .controls {
445
+ margin: 20px 0;
446
+ display: flex;
447
+ gap: 10px;
197
448
  }
198
- }
449
+
450
+ .scan-button, .reset-button {
451
+ padding: 10px 20px;
452
+ font-size: 16px;
453
+ border: none;
454
+ border-radius: 4px;
455
+ cursor: pointer;
456
+ }
457
+
458
+ .scan-button {
459
+ background-color: #007bff;
460
+ color: white;
461
+ }
462
+
463
+ .reset-button {
464
+ background-color: #6c757d;
465
+ color: white;
466
+ }
467
+
468
+ .scan-button:disabled, .reset-button:disabled {
469
+ opacity: 0.6;
470
+ cursor: not-allowed;
471
+ }
472
+
473
+ .results {
474
+ margin-top: 20px;
475
+ }
476
+
477
+ .image-result {
478
+ margin: 20px 0;
479
+ }
480
+
481
+ .captured-image {
482
+ max-width: 100%;
483
+ height: auto;
484
+ border: 1px solid #ddd;
485
+ border-radius: 4px;
486
+ }
487
+
488
+ .metadata {
489
+ background-color: #f8f9fa;
490
+ padding: 10px;
491
+ border-radius: 4px;
492
+ margin-top: 20px;
493
+ }
494
+ `],
495
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
496
+ })
497
+ export class DocumentScannerComponent implements AfterViewInit {
498
+ @ViewChild('scanner', { static: false }) scannerRef!: ElementRef;
199
499
 
200
- async stopCapture() {
201
- await this.detectorRef.nativeElement.stopCapture();
202
- }
500
+ isReady = false;
501
+ capturedImages: CapturedImages | null = null;
203
502
 
204
- async resetCapture() {
205
- await this.detectorRef.nativeElement.resetCapture();
206
- this.capturedData = null;
207
- this.isCompleted = false;
503
+ ngAfterViewInit() {
504
+ const scanner = this.scannerRef.nativeElement;
505
+
506
+ // Component ready handler
507
+ scanner.addEventListener('isReady', () => {
508
+ this.isReady = true;
509
+ });
510
+
511
+ // Capture completion handler
512
+ scanner.addEventListener('captureCompleted', async () => {
513
+ this.capturedImages = await scanner.getCapturedImages();
514
+ });
208
515
  }
209
516
 
210
- async getImages() {
211
- try {
212
- const images = await this.detectorRef.nativeElement.getCapturedImages();
213
- console.log('Imágenes obtenidas:', images);
214
- } catch (error) {
215
- console.error('Error al obtener imágenes:', error);
517
+ async startScan() {
518
+ if (this.scannerRef?.nativeElement) {
519
+ await this.scannerRef.nativeElement.startCapture();
216
520
  }
217
521
  }
218
522
 
219
- onCaptureCompleted(event: any) {
220
- console.log('Captura completada en Angular:', event.detail);
221
- this.capturedData = event.detail;
222
- this.isCompleted = true;
223
- }
224
-
225
- onComponentReady(event: any) {
226
- console.log('Componente listo en Angular:', event.detail);
227
- // El componente está listo para iniciar detección
523
+ async resetScan() {
524
+ if (this.scannerRef?.nativeElement) {
525
+ await this.scannerRef.nativeElement.resetCapture();
526
+ this.capturedImages = null;
527
+ }
228
528
  }
229
529
  }
230
530
  ```
231
531
 
232
- ### React
532
+ ```typescript
533
+ // app.module.ts
534
+ import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
535
+ import { BrowserModule } from '@angular/platform-browser';
536
+ import { DocumentScannerComponent } from './document-scanner.component';
233
537
 
234
- #### 1. Instalar el componente
235
- ```bash
236
- npm install @jaak.ai/stamps
538
+ @NgModule({
539
+ declarations: [DocumentScannerComponent],
540
+ imports: [BrowserModule],
541
+ schemas: [CUSTOM_ELEMENTS_SCHEMA], // Required for custom elements
542
+ exports: [DocumentScannerComponent]
543
+ })
544
+ export class DocumentScannerModule { }
237
545
  ```
238
546
 
239
- #### 2. Crear componente wrapper (opcional pero recomendado)
240
- ```jsx
241
- // JaakStampsWrapper.jsx
242
- import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';
547
+ ## Style Guide
548
+
549
+ ### CSS Custom Properties
243
550
 
244
- // Importar el web component
245
- import '@jaak.ai/stamps/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js';
551
+ The component supports customization through CSS custom properties:
246
552
 
247
- const JaakStampsWrapper = forwardRef(({ debug = false, maskSize = 90, cropMargin = 0, useDocumentClassification = false, onCaptureCompleted, onIsReady }, ref) => {
248
- const elementRef = useRef(null);
553
+ ```css
554
+ jaak-stamps {
555
+ /* Camera view dimensions */
556
+ --camera-width: 100%;
557
+ --camera-height: 400px;
249
558
 
250
- useImperativeHandle(ref, () => ({
251
- preloadModel: () => elementRef.current?.preloadModel(),
252
- startCapture: () => elementRef.current?.startCapture(),
253
- stopCapture: () => elementRef.current?.stopCapture(),
254
- resetCapture: () => elementRef.current?.resetCapture(),
255
- getCapturedImages: () => elementRef.current?.getCapturedImages(),
256
- getStatus: () => elementRef.current?.getStatus(),
257
- skipBackCapture: () => elementRef.current?.skipBackCapture(),
258
- isProcessCompleted: () => elementRef.current?.isProcessCompleted()
259
- }));
559
+ /* Detection overlay colors */
560
+ --detection-overlay-color: rgba(0, 123, 255, 0.3);
561
+ --detection-border-color: #007bff;
562
+ --detection-border-width: 2px;
260
563
 
261
- useEffect(() => {
262
- const element = elementRef.current;
263
-
264
- const handleCaptureCompleted = (event) => {
265
- if (onCaptureCompleted) {
266
- onCaptureCompleted(event.detail);
267
- }
268
- };
269
-
270
- const handleIsReady = (event) => {
271
- if (onIsReady) {
272
- onIsReady(event.detail);
273
- }
274
- };
275
-
276
- if (element) {
277
- element.addEventListener('captureCompleted', handleCaptureCompleted);
278
- element.addEventListener('isReady', handleIsReady);
279
-
280
- return () => {
281
- element.removeEventListener('captureCompleted', handleCaptureCompleted);
282
- element.removeEventListener('isReady', handleIsReady);
283
- };
284
- }
285
- }, [onCaptureCompleted, onIsReady]);
564
+ /* UI element colors */
565
+ --button-background: #007bff;
566
+ --button-text-color: #ffffff;
567
+ --button-border-radius: 4px;
286
568
 
287
- return (
288
- <jaak-stamps
289
- ref={elementRef}
290
- debug={debug}
291
- mask-size={maskSize}
292
- crop-margin={cropMargin}
293
- use-document-classification={useDocumentClassification}
294
- />
295
- );
296
- });
297
-
298
- export default JaakStampsWrapper;
569
+ /* Status indicator colors */
570
+ --status-success-color: #28a745;
571
+ --status-warning-color: #ffc107;
572
+ --status-error-color: #dc3545;
573
+ }
299
574
  ```
300
575
 
301
- #### 3. Usar en tu aplicación React
302
- ```jsx
303
- // App.jsx
304
- import React, { useRef, useState } from 'react';
305
- import JaakStampsWrapper from './components/JaakStampsWrapper';
306
-
307
- function App() {
308
- const detectorRef = useRef(null);
309
- const [capturedData, setCapturedData] = useState(null);
310
- const [isCompleted, setIsCompleted] = useState(false);
311
- const [status, setStatus] = useState(null);
312
-
313
- const handleCaptureCompleted = (data) => {
314
- console.log('Captura completada:', data);
315
- setCapturedData(data);
316
- setIsCompleted(true);
317
- };
318
-
319
- const handleIsReady = (isReady) => {
320
- console.log('Componente listo:', isReady);
321
- // El componente está listo para iniciar detección
322
- };
323
-
324
- const preloadModel = async () => {
325
- try {
326
- const result = await detectorRef.current?.preloadModel();
327
- console.log('Modelo precargado:', result);
328
- } catch (error) {
329
- console.error('Error al precargar modelo:', error);
330
- }
331
- };
332
-
333
- const startCapture = async () => {
334
- try {
335
- await detectorRef.current?.startCapture();
336
- } catch (error) {
337
- console.error('Error al iniciar captura:', error);
338
- }
339
- };
340
-
341
- const stopCapture = async () => {
342
- await detectorRef.current?.stopCapture();
343
- };
344
-
345
- const resetCapture = async () => {
346
- await detectorRef.current?.resetCapture();
347
- setCapturedData(null);
348
- setIsCompleted(false);
349
- };
350
-
351
- const getImages = async () => {
352
- try {
353
- const images = await detectorRef.current?.getCapturedImages();
354
- console.log('Imágenes obtenidas:', images);
355
- } catch (error) {
356
- console.error('Error al obtener imágenes:', error);
357
- }
358
- };
359
-
360
- const checkStatus = async () => {
361
- const currentStatus = await detectorRef.current?.getStatus();
362
- setStatus(currentStatus);
363
- };
364
-
365
- return (
366
- <div className="App">
367
- <header className="App-header">
368
- <h1>Detector de Documentos - React</h1>
369
-
370
- <JaakStampsWrapper
371
- ref={detectorRef}
372
- debug={false}
373
- maskSize={90}
374
- cropMargin={10}
375
- useDocumentClassification={false}
376
- onCaptureCompleted={handleCaptureCompleted}
377
- onIsReady={handleIsReady}
378
- />
379
-
380
- <div className="controls">
381
- <button onClick={preloadModel}>Precargar Modelo</button>
382
- <button onClick={startCapture}>Iniciar Captura</button>
383
- <button onClick={stopCapture}>Detener</button>
384
- <button onClick={resetCapture}>Reiniciar</button>
385
- <button onClick={getImages} disabled={!isCompleted}>
386
- Obtener Imágenes
387
- </button>
388
- <button onClick={checkStatus}>Verificar Estado</button>
389
- </div>
390
-
391
- {status && (
392
- <div className="status">
393
- <h3>Estado Actual:</h3>
394
- <p>Video activo: {status.isVideoActive ? 'Sí' : 'No'}</p>
395
- <p>Paso: {status.captureStep}</p>
396
- <p>Mensaje: {status.statusMessage}</p>
397
- <p>Proceso completado: {status.isProcessCompleted ? 'Sí' : 'No'}</p>
398
- <p>Modelo precargado: {status.isModelPreloaded ? 'Sí' : 'No'}</p>
399
- </div>
400
- )}
401
-
402
- {capturedData && (
403
- <div className="results">
404
- <h3>Resultados de la Captura:</h3>
405
- <p>Total de imágenes: {capturedData.metadata.totalImages}</p>
406
- <p>Timestamp: {capturedData.timestamp}</p>
407
-
408
- {capturedData.front.cropped && (
409
- <div>
410
- <h4>Frente del Documento:</h4>
411
- <img
412
- src={capturedData.front.cropped}
413
- alt="Frente del documento"
414
- style={{ maxWidth: '300px', border: '1px solid #ccc', margin: '10px' }}
415
- />
416
- </div>
417
- )}
418
-
419
- {capturedData.back.cropped && (
420
- <div>
421
- <h4>Reverso del Documento:</h4>
422
- <img
423
- src={capturedData.back.cropped}
424
- alt="Reverso del documento"
425
- style={{ maxWidth: '300px', border: '1px solid #ccc', margin: '10px' }}
426
- />
427
- </div>
428
- )}
429
- </div>
430
- )}
431
- </header>
432
- </div>
433
- );
576
+ ### Custom CSS Classes
577
+
578
+ You can style the component container and add custom overlays:
579
+
580
+ ```css
581
+ /* Component container styling */
582
+ .document-scanner-container {
583
+ position: relative;
584
+ border: 1px solid #ddd;
585
+ border-radius: 8px;
586
+ overflow: hidden;
587
+ background: #000;
588
+ }
589
+
590
+ /* Custom loading overlay */
591
+ .scanner-loading {
592
+ position: absolute;
593
+ top: 50%;
594
+ left: 50%;
595
+ transform: translate(-50%, -50%);
596
+ background: rgba(0, 0, 0, 0.8);
597
+ color: white;
598
+ padding: 20px;
599
+ border-radius: 8px;
600
+ z-index: 1000;
434
601
  }
435
602
 
436
- export default App;
603
+ /* Responsive design */
604
+ @media (max-width: 768px) {
605
+ jaak-stamps {
606
+ --camera-height: 300px;
607
+ }
608
+ }
437
609
  ```
438
610
 
439
- ## API Reference
611
+ ### Theme Support
440
612
 
441
- ### Props/Atributos
442
-
443
- | Propiedad | Tipo | Predeterminado | Descripción |
444
- |-----------|------|---------------|-------------|
445
- | `debug` | `boolean` | `false` | Activa el modo debug para mostrar información de depuración |
446
- | `alignmentTolerance` | `number` | `10` | Tolerancia en píxeles para considerar un lado alineado |
447
- | `maskSize` | `number` | `90` | Porcentaje del video que ocupará la máscara de detección (50-100) |
448
- | `cropMargin` | `number` | `0` | Margen en píxeles agregado al recorte del documento (0-100) |
449
- | `useDocumentClassification` | `boolean` | `false` | Habilita la clasificación automática de documentos para determinar si solicitar reverso. Solo carga el modelo de clasificación cuando está habilitada. |
450
- | `preferredCamera` | `'auto' \| 'front' \| 'back'` | `'auto'` | Define qué cámara usar: `'auto'` (automático según dispositivo), `'front'` (cámara frontal), `'back'` (cámara trasera) |
451
-
452
- ### Métodos Públicos
453
-
454
- | Método | Retorna | Descripción |
455
- |--------|---------|-------------|
456
- | `startCapture()` | `Promise<void>` | Inicia el proceso de detección y captura |
457
- | `stopCapture()` | `Promise<void>` | Detiene el proceso y libera la cámara |
458
- | `resetCapture()` | `Promise<void>` | Reinicia el proceso de captura |
459
- | `preloadModel()` | `Promise<{success: boolean, message?: string, error?: string}>` | Precarga el modelo de detección |
460
- | `getCapturedImages()` | `Promise<CapturedImages>` | Obtiene las imágenes capturadas (solo después de completar) |
461
- | `isProcessCompleted()` | `Promise<boolean>` | Verifica si el proceso ha sido completado |
462
- | `getStatus()` | `Promise<Status>` | Obtiene el estado actual del detector |
463
- | `skipBackCapture()` | `Promise<void>` | Omite la captura del reverso (solo disponible en paso 'back') |
464
- | `getCameraInfo()` | `Promise<CameraInfo>` | Obtiene información detallada sobre las cámaras disponibles |
465
- | `setPreferredCamera(camera)` | `Promise<{success: boolean, selectedCamera: string, availableCameras: number}>` | Configura la cámara preferida ('auto', 'front', 'back') |
466
-
467
- ### Eventos
468
-
469
- | Evento | Payload | Descripción |
470
- |--------|---------|-------------|
471
- | `captureCompleted` | `CapturedImages` | Se emite cuando se completa el proceso de captura |
472
- | `isReady` | `boolean` | Se emite cuando el componente está listo para comenzar la detección (modelos cargados) |
473
-
474
- ### Tipos de Datos
613
+ The component automatically adapts to system dark/light mode preferences:
475
614
 
476
- ```typescript
477
- interface CapturedImages {
478
- front: {
479
- fullFrame: string | null; // Base64 de la imagen completa del frente
480
- cropped: string | null; // Base64 del documento recortado del frente
481
- };
482
- back: {
483
- fullFrame: string | null; // Base64 de la imagen completa del reverso
484
- cropped: string | null; // Base64 del documento recortado del reverso
485
- };
486
- timestamp: string; // ISO timestamp de la captura
487
- metadata: {
488
- totalImages: number; // Número total de imágenes capturadas
489
- processCompleted: boolean; // Si el proceso fue completado
490
- backCaptureSkipped?: boolean; // Si se omitió la captura del reverso
491
- };
615
+ ```css
616
+ /* Light theme (default) */
617
+ jaak-stamps {
618
+ --background-color: #ffffff;
619
+ --text-color: #333333;
620
+ --border-color: #dddddd;
492
621
  }
493
622
 
494
- interface Status {
495
- isVideoActive: boolean; // Si la cámara está activa
496
- captureStep: 'front' | 'back' | 'completed'; // Paso actual del proceso
497
- statusMessage: string; // Mensaje de estado actual
498
- hasImages: boolean; // Si hay imágenes capturadas
499
- isProcessCompleted: boolean; // Si el proceso está completado
500
- isModelPreloaded: boolean; // Si el modelo está precargado
623
+ /* Dark theme */
624
+ @media (prefers-color-scheme: dark) {
625
+ jaak-stamps {
626
+ --background-color: #1a1a1a;
627
+ --text-color: #ffffff;
628
+ --border-color: #444444;
629
+ }
501
630
  }
502
631
  ```
503
632
 
504
- ## Flujo de Trabajo
505
-
506
- ### Flujo Estándar (useDocumentClassification = false)
507
- 1. **Inicialización**: El componente se prepara para la detección
508
- 2. **Inicio de captura**: Se carga automáticamente el modelo de detección
509
- 3. **Captura del frente**: El usuario posiciona el documento y se captura automáticamente
510
- 4. **Solicitud de reverso**: Siempre se solicita voltear el documento para capturar el reverso
511
- 5. **Botón "Saltar reverso"**: Disponible para omitir manualmente la captura del reverso
512
- 6. **Completado**: Se emite el evento `captureCompleted` con las imágenes correspondientes
513
-
514
- ### Flujo con Clasificación Inteligente (useDocumentClassification = true)
515
- 1. **Inicialización**: El componente se prepara para la detección
516
- 2. **Inicio de captura**: Se cargan automáticamente los modelos de detección y clasificación
517
- 3. **Captura del frente**: El usuario posiciona el documento y se captura automáticamente
518
- 4. **Clasificación automática**: El sistema determina si el documento requiere captura de reverso
519
- 5. **Flujo adaptativo**:
520
- - **Si es pasaporte**: Se completa automáticamente el proceso (sin reverso)
521
- - **Si es otro documento**: Se solicita voltear el documento para capturar el reverso
522
- 6. **Completado**: Se emite el evento `captureCompleted` con las imágenes correspondientes
523
-
524
- ### Flujo con Precarga (Recomendado)
525
- 1. **Precarga de modelos**: Se llama a `preloadModel()` para cargar ambos modelos en memoria
526
- 2. **Inicio optimizado**: Al iniciar la captura, se usan los modelos ya cargados
527
- 3. **Captura del frente**: Detección y clasificación más rápidas con modelos precargados
528
- 4. **Flujo inteligente**:
529
- - **Documento sin reverso**: Proceso completado inmediatamente
530
- - **Documento con reverso**: Se solicita captura del reverso
531
- 5. **Completado**: Se emite el evento `captureCompleted` con todas las imágenes
532
-
533
- ### Clasificación de Documentos
534
-
535
- #### Modo con Clasificación Habilitada (`useDocumentClassification = true`)
536
- Cuando la propiedad está habilitada, el sistema utiliza un modelo de inteligencia artificial especializado para determinar automáticamente si el documento requiere captura de reverso:
537
- - **Pasaportes**: Se omite automáticamente la captura del reverso
538
- - **Otros documentos**: Requieren captura de ambos lados (frente y reverso)
539
- - **Botón "Saltar reverso"**: Siempre disponible para permitir flexibilidad manual
540
- - **Carga optimizada**: El modelo de clasificación se descarga solo cuando es necesario
541
-
542
- #### Modo Estándar (`useDocumentClassification = false`, por defecto)
543
- Cuando está deshabilitada, el comportamiento es más liviano y directo:
544
- - **Todos los documentos**: Siempre se solicita captura del reverso
545
- - **Botón "Saltar reverso"**: Disponible para omitir manualmente la captura
546
- - **Sin clasificación**: NO se carga el modelo de clasificación, solo el de detección
547
- - **Menor uso de ancho de banda**: Solo descarga el modelo de detección (~5MB menos)
548
-
549
- #### Consideraciones de Rendimiento
550
- - **Con clasificación deshabilitada**: Carga más rápida y menor uso de datos
551
- - **Con clasificación habilitada**: Experiencia más inteligente pero requiere descargar modelo adicional
552
- - **Carga bajo demanda**: El modelo de clasificación se carga solo cuando se captura el primer documento
553
-
554
- ## Requisitos del Sistema
555
-
556
- - **Navegadores**: Chrome/Edge 88+, Firefox 85+, Safari 14+
557
- - **Cámara**: Acceso a cámara trasera (móvil) o webcam (escritorio)
558
- - **Conexión**: Conexión a internet para cargar los modelos de IA
559
-
560
- ## Consideraciones de Privacidad y Rendimiento
561
-
562
- ### Privacidad
563
- - Las imágenes se procesan localmente en el navegador
564
- - No se envían datos a servidores externos
565
- - Los modelos de IA (detección y clasificación) se ejecutan completamente en el cliente
566
- - La clasificación de documentos se realiza de forma privada y local
567
- - Las imágenes capturadas solo están disponibles en el contexto de tu aplicación
568
-
569
- ### Optimización de Recursos
570
- - **Carga condicional**: Solo descarga modelos necesarios según configuración
571
- - **Modelo de detección**: ~15MB (siempre requerido)
572
- - **Modelo de clasificación**: ~5MB (solo si `useDocumentClassification = true`)
573
- - **Lazy loading**: Los modelos se cargan cuando realmente se necesitan
574
- - **Cache del navegador**: Los modelos se almacenan localmente después de la primera descarga
575
-
576
- ## Desarrollo
577
-
578
- ### Clonar el repositorio
579
- ```bash
580
- git clone [repository-url]
581
- cd jaak-stamps-webcomponent
633
+ ## Compatibility
634
+
635
+ ### Browser Support
636
+
637
+ - **Chrome/Edge**: 80+
638
+ - **Firefox**: 75+
639
+ - **Safari**: 13+
640
+ - **iOS Safari**: 13+
641
+ - **Chrome Android**: 80+
642
+
643
+ ### Framework Compatibility
644
+
645
+ - **Vanilla JavaScript**: Full support
646
+ - **React**: 16.8+
647
+ - **Angular**: 12+
648
+ - **Vue.js**: 3+
649
+ - **Svelte**: 3+
650
+
651
+ ### Required Dependencies
652
+
653
+ - **Modern browser** with WebRTC support
654
+ - **Camera access** permission
655
+ - **HTTPS context** (required for camera access)
656
+
657
+ ### Optional Dependencies
658
+
659
+ - **Web Workers** support for enhanced performance
660
+ - **WebAssembly** support for AI model acceleration
661
+
662
+ ## Troubleshooting
663
+
664
+ ### Common Issues
665
+
666
+ #### Camera Not Working
667
+
668
+ **Problem**: Camera preview is black or not showing
669
+
670
+ **Solutions**:
671
+ ```javascript
672
+ // Check camera permissions
673
+ const scanner = document.querySelector('jaak-stamps');
674
+ const cameraInfo = await scanner.getCameraInfo();
675
+ console.log('Available cameras:', cameraInfo.availableCameras);
676
+
677
+ // Try switching cameras
678
+ await scanner.setPreferredCamera('back');
582
679
  ```
583
680
 
584
- ### Instalar dependencias
585
- ```bash
586
- npm install
681
+ #### Slow Performance
682
+
683
+ **Problem**: Component is slow or laggy
684
+
685
+ **Solutions**:
686
+ ```javascript
687
+ // Preload the AI model
688
+ const scanner = document.querySelector('jaak-stamps');
689
+ await scanner.preloadModel();
690
+
691
+ // Reduce capture delay for faster response
692
+ await scanner.setCaptureDelay(1000);
693
+
694
+ // Disable debug mode in production
695
+ scanner.setAttribute('debug', 'false');
587
696
  ```
588
697
 
589
- ### Desarrollo local
590
- ```bash
591
- npm start
698
+ #### No Images Captured
699
+
700
+ **Problem**: Capture completes but no images returned
701
+
702
+ **Solutions**:
703
+ ```javascript
704
+ // Check component status
705
+ const status = await scanner.getStatus();
706
+ console.log('Component status:', status);
707
+
708
+ // Verify capture process completion
709
+ const isCompleted = await scanner.isProcessCompleted();
710
+ if (isCompleted) {
711
+ const images = await scanner.getCapturedImages();
712
+ console.log('Images:', images);
713
+ }
592
714
  ```
593
715
 
594
- ### Construir para producción
595
- ```bash
596
- npm run build
716
+ #### Memory Issues on Mobile
717
+
718
+ **Problem**: Component crashes on mobile devices
719
+
720
+ **Solutions**:
721
+ ```html
722
+ <!-- Reduce memory usage -->
723
+ <jaak-stamps
724
+ mask-size="60"
725
+ crop-margin="15"
726
+ capture-delay="2000">
727
+ </jaak-stamps>
597
728
  ```
598
729
 
599
- ### Ejecutar tests
600
- ```bash
601
- npm test
730
+ ### Frequently Asked Questions
731
+
732
+ **Q: Can I use this component without HTTPS?**
733
+ A: No, camera access requires HTTPS in production. Use `localhost` for development.
734
+
735
+ **Q: How do I handle multiple document types?**
736
+ A: Enable document classification:
737
+ ```html
738
+ <jaak-stamps use-document-classification="true"></jaak-stamps>
602
739
  ```
603
740
 
604
- ## Soporte
741
+ **Q: Can I customize the detection area?**
742
+ A: Yes, use the `mask-size` property to adjust the detection frame size.
743
+
744
+ **Q: How do I get only the cropped images?**
745
+ A: Access the `cropped` property from the captured images:
746
+ ```javascript
747
+ const images = await scanner.getCapturedImages();
748
+ const croppedFront = images.front.cropped; // Base64 string
749
+ ```
750
+
751
+ **Q: Is the component accessible?**
752
+ A: Yes, the component includes ARIA labels and keyboard navigation support.
753
+
754
+ **Q: Can I use this in a Progressive Web App (PWA)?**
755
+ A: Yes, the component is fully compatible with PWAs and works offline after initial load.
605
756
 
606
- Para reportar bugs o solicitar características, por favor crea un issue en el repositorio del proyecto.
757
+ ---
607
758
 
608
- ## Licencia
759
+ ## License
609
760
 
610
- MIT License - ver archivo LICENSE para detalles.
761
+ MIT © [Jaak AI](https://github.com/jaak-ai)