@jaak.ai/stamps 2.0.0-dev.40 → 2.0.0-dev.42

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 (43) hide show
  1. package/README.md +4 -1
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +868 -11
  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 +66 -0
  8. package/dist/collection/components/my-component/my-component.js +494 -8
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/CameraService.js +161 -0
  11. package/dist/collection/services/CameraService.js.map +1 -1
  12. package/dist/collection/services/DetectionService.js +164 -1
  13. package/dist/collection/services/DetectionService.js.map +1 -1
  14. package/dist/collection/services/ImageQualityService.js +329 -0
  15. package/dist/collection/services/ImageQualityService.js.map +1 -0
  16. package/dist/collection/services/ServiceContainer.js +6 -1
  17. package/dist/collection/services/ServiceContainer.js.map +1 -1
  18. package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
  19. package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
  20. package/dist/collection/services/interfaces/IImageQualityService.js +2 -0
  21. package/dist/collection/services/interfaces/IImageQualityService.js.map +1 -0
  22. package/dist/components/jaak-stamps.js +885 -12
  23. package/dist/components/jaak-stamps.js.map +1 -1
  24. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  25. package/dist/esm/jaak-stamps.entry.js +868 -11
  26. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  27. package/dist/esm/loader.js +1 -1
  28. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  29. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  30. package/dist/jaak-stamps-webcomponent/p-47f37982.entry.js +2 -0
  31. package/dist/jaak-stamps-webcomponent/p-47f37982.entry.js.map +1 -0
  32. package/dist/types/components/my-component/my-component.d.ts +48 -0
  33. package/dist/types/components.d.ts +65 -0
  34. package/dist/types/services/CameraService.d.ts +5 -0
  35. package/dist/types/services/DetectionService.d.ts +22 -2
  36. package/dist/types/services/ImageQualityService.d.ts +31 -0
  37. package/dist/types/services/ServiceContainer.d.ts +7 -0
  38. package/dist/types/services/interfaces/ICameraService.d.ts +2 -0
  39. package/dist/types/services/interfaces/IDetectionService.d.ts +13 -0
  40. package/dist/types/services/interfaces/IImageQualityService.d.ts +58 -0
  41. package/package.json +1 -1
  42. package/dist/jaak-stamps-webcomponent/p-c30c7b47.entry.js +0 -2
  43. package/dist/jaak-stamps-webcomponent/p-c30c7b47.entry.js.map +0 -1
@@ -8,6 +8,13 @@ export class JaakStamps {
8
8
  cropMargin = 0;
9
9
  useDocumentClassification = false;
10
10
  preferredCamera = 'auto';
11
+ captureDelay = 1000;
12
+ enableQualityValidation = true;
13
+ qualityThreshold = 60;
14
+ minQualityScore = 45;
15
+ minFocusScore = 16;
16
+ minBlurScore = 22;
17
+ maxReflectionScore = 15;
11
18
  captureCompleted;
12
19
  isReady;
13
20
  // State derived from services
@@ -19,12 +26,19 @@ export class JaakStamps {
19
26
  shouldMirrorVideo = true;
20
27
  showCameraSelector = false;
21
28
  isSwitchingCamera = false;
29
+ hasDocumentDetected = false;
22
30
  currentStatus = {
23
31
  message: 'Inicializando componente...',
24
32
  description: 'Configurando servicios y cargando recursos',
25
33
  type: 'initializing',
26
34
  isInitialized: false
27
35
  };
36
+ qualityFeedback = {
37
+ message: 'Analizando calidad...',
38
+ score: 0,
39
+ hasIssues: false,
40
+ canCapture: false
41
+ };
28
42
  performanceData = {
29
43
  fps: 0,
30
44
  inferenceTime: 0,
@@ -92,7 +106,14 @@ export class JaakStamps {
92
106
  maskSize: this.maskSize,
93
107
  cropMargin: this.cropMargin,
94
108
  useDocumentClassification: this.useDocumentClassification,
95
- preferredCamera: this.preferredCamera
109
+ preferredCamera: this.preferredCamera,
110
+ captureDelay: this.captureDelay,
111
+ enableQualityValidation: this.enableQualityValidation,
112
+ qualityThreshold: this.qualityThreshold,
113
+ minQualityScore: this.minQualityScore,
114
+ minFocusScore: this.minFocusScore,
115
+ minBlurScore: this.minBlurScore,
116
+ maxReflectionScore: this.maxReflectionScore
96
117
  };
97
118
  this.serviceContainer = new ServiceContainer(config);
98
119
  this.logger = this.serviceContainer.getLogger();
@@ -119,7 +140,8 @@ export class JaakStamps {
119
140
  maskSize: this.maskSize,
120
141
  cropMargin: this.cropMargin,
121
142
  useDocumentClassification: this.useDocumentClassification,
122
- preferredCamera: this.preferredCamera
143
+ preferredCamera: this.preferredCamera,
144
+ captureDelay: this.captureDelay
123
145
  });
124
146
  this.validateProps();
125
147
  if (this.debug) {
@@ -147,6 +169,30 @@ export class JaakStamps {
147
169
  this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
148
170
  this.preferredCamera = 'auto';
149
171
  }
172
+ if (this.captureDelay < 0 || this.captureDelay > 10000) {
173
+ this.logger.warn(`Propiedad captureDelay inválida. Valor: ${this.captureDelay}, esperado: 0-10000. Usando valor por defecto: 1000`);
174
+ this.captureDelay = 1000;
175
+ }
176
+ if (this.qualityThreshold < 0 || this.qualityThreshold > 100) {
177
+ this.logger.warn(`Propiedad qualityThreshold inválida. Valor: ${this.qualityThreshold}, esperado: 0-100. Usando valor por defecto: 60`);
178
+ this.qualityThreshold = 60;
179
+ }
180
+ if (this.minQualityScore < 0 || this.minQualityScore > 100) {
181
+ this.logger.warn(`Propiedad minQualityScore inválida. Valor: ${this.minQualityScore}, esperado: 0-100. Usando valor por defecto: 45`);
182
+ this.minQualityScore = 45;
183
+ }
184
+ if (this.minFocusScore < 0 || this.minFocusScore > 100) {
185
+ this.logger.warn(`Propiedad minFocusScore inválida. Valor: ${this.minFocusScore}, esperado: 0-100. Usando valor por defecto: 16`);
186
+ this.minFocusScore = 16;
187
+ }
188
+ if (this.minBlurScore < 0 || this.minBlurScore > 200) {
189
+ this.logger.warn(`Propiedad minBlurScore inválida. Valor: ${this.minBlurScore}, esperado: 0-200. Usando valor por defecto: 22`);
190
+ this.minBlurScore = 22;
191
+ }
192
+ if (this.maxReflectionScore < 0 || this.maxReflectionScore > 100) {
193
+ this.logger.warn(`Propiedad maxReflectionScore inválida. Valor: ${this.maxReflectionScore}, esperado: 0-100. Usando valor por defecto: 15`);
194
+ this.maxReflectionScore = 15;
195
+ }
150
196
  }
151
197
  async loadOnnxRuntime() {
152
198
  if (!window.ort) {
@@ -371,6 +417,91 @@ export class JaakStamps {
371
417
  availableCameras: this.cameraService.getAvailableCameras().length
372
418
  };
373
419
  }
420
+ async setCaptureDelay(delay) {
421
+ if (delay < 0 || delay > 10000) {
422
+ throw new Error('Capture delay must be between 0 and 10000 milliseconds');
423
+ }
424
+ this.captureDelay = delay;
425
+ this.serviceContainer.updateConfig({ captureDelay: delay });
426
+ return {
427
+ success: true,
428
+ captureDelay: this.captureDelay
429
+ };
430
+ }
431
+ async getCaptureDelay() {
432
+ return this.captureDelay;
433
+ }
434
+ async setTorchEnabled(enabled) {
435
+ const torchEnabled = await this.cameraService.setTorchEnabled(enabled, this.videoStream);
436
+ return {
437
+ success: torchEnabled,
438
+ enabled: torchEnabled ? enabled : false
439
+ };
440
+ }
441
+ async focusAtPoint(x, y) {
442
+ const focused = await this.cameraService.focusAtPoint(x, y, this.videoStream);
443
+ return {
444
+ success: focused,
445
+ coordinates: { x, y }
446
+ };
447
+ }
448
+ async getImageQuality() {
449
+ if (!this.videoRef || !this.detectionService.isModelLoaded()) {
450
+ return null;
451
+ }
452
+ try {
453
+ const qualityResult = this.detectionService.validateImageQuality(this.videoRef);
454
+ return {
455
+ qualityScore: qualityResult.qualityScore,
456
+ overallQuality: qualityResult.overallQuality,
457
+ issues: qualityResult.issues,
458
+ recommendations: qualityResult.recommendations,
459
+ canCapture: this.detectionService.isImageQualityAcceptable(qualityResult)
460
+ };
461
+ }
462
+ catch (error) {
463
+ this.logger.error('Error al analizar calidad de imagen:', error);
464
+ return null;
465
+ }
466
+ }
467
+ async setQualityThresholds(thresholds) {
468
+ // Validate thresholds
469
+ if (thresholds.minQualityScore !== undefined && (thresholds.minQualityScore < 0 || thresholds.minQualityScore > 100)) {
470
+ throw new Error('minQualityScore must be between 0 and 100');
471
+ }
472
+ if (thresholds.minFocusScore !== undefined && (thresholds.minFocusScore < 0 || thresholds.minFocusScore > 100)) {
473
+ throw new Error('minFocusScore must be between 0 and 100');
474
+ }
475
+ if (thresholds.minBlurScore !== undefined && (thresholds.minBlurScore < 0 || thresholds.minBlurScore > 200)) {
476
+ throw new Error('minBlurScore must be between 0 and 200');
477
+ }
478
+ if (thresholds.maxReflectionScore !== undefined && (thresholds.maxReflectionScore < 0 || thresholds.maxReflectionScore > 100)) {
479
+ throw new Error('maxReflectionScore must be between 0 and 100');
480
+ }
481
+ // Update component properties
482
+ if (thresholds.minQualityScore !== undefined)
483
+ this.minQualityScore = thresholds.minQualityScore;
484
+ if (thresholds.minFocusScore !== undefined)
485
+ this.minFocusScore = thresholds.minFocusScore;
486
+ if (thresholds.minBlurScore !== undefined)
487
+ this.minBlurScore = thresholds.minBlurScore;
488
+ if (thresholds.maxReflectionScore !== undefined)
489
+ this.maxReflectionScore = thresholds.maxReflectionScore;
490
+ // Update detection service
491
+ this.detectionService.updateQualityThresholds(thresholds);
492
+ return {
493
+ success: true,
494
+ thresholds: this.detectionService.getQualityThresholds()
495
+ };
496
+ }
497
+ async getQualityThresholds() {
498
+ return {
499
+ minQualityScore: this.minQualityScore,
500
+ minFocusScore: this.minFocusScore,
501
+ minBlurScore: this.minBlurScore,
502
+ maxReflectionScore: this.maxReflectionScore
503
+ };
504
+ }
374
505
  // DETECTION METHODS
375
506
  async startDetection() {
376
507
  this.logger.state('INICIANDO_DETECCION');
@@ -472,7 +603,36 @@ export class JaakStamps {
472
603
  // Measure preprocessing and inference time
473
604
  const inferenceStartTime = performance.now();
474
605
  const inputTensor = this.detectionService.preprocess(this.videoRef);
475
- const detections = await this.detectionService.runInference(inputTensor);
606
+ // Enhanced detection with quality validation if enabled
607
+ let detections;
608
+ let qualityResult = null;
609
+ let canCapture = true;
610
+ if (this.enableQualityValidation) {
611
+ // Use enhanced detection method with quality validation
612
+ const enhancedResult = await this.detectionService.runInferenceWithQuality(inputTensor, this.videoRef);
613
+ detections = enhancedResult.detections;
614
+ qualityResult = enhancedResult.qualityResult;
615
+ canCapture = enhancedResult.canCapture;
616
+ // Update quality feedback state
617
+ this.qualityFeedback = {
618
+ message: enhancedResult.feedback,
619
+ score: qualityResult.qualityScore,
620
+ hasIssues: qualityResult.issues.length > 0,
621
+ canCapture: canCapture
622
+ };
623
+ }
624
+ else {
625
+ // Standard detection without quality validation
626
+ detections = await this.detectionService.runInference(inputTensor);
627
+ // Quick quality feedback for real-time display
628
+ const quickFeedback = this.detectionService.getQuickQualityFeedback(this.videoRef);
629
+ this.qualityFeedback = {
630
+ message: quickFeedback.feedback,
631
+ score: 0, // Not calculated in quick mode
632
+ hasIssues: quickFeedback.hasBlur || quickFeedback.hasReflections || !quickFeedback.isFocused,
633
+ canCapture: true // Allow capture without strict validation
634
+ };
635
+ }
476
636
  const inferenceTime = performance.now() - inferenceStartTime;
477
637
  // Record ONNX performance metrics
478
638
  if (this.debug) {
@@ -488,6 +648,8 @@ export class JaakStamps {
488
648
  }
489
649
  });
490
650
  }
651
+ // Update document detection state
652
+ this.hasDocumentDetected = detections.length > 0;
491
653
  // Adaptive frame rate
492
654
  if (detections.length === 0) {
493
655
  this.consecutiveFailures++;
@@ -549,6 +711,7 @@ export class JaakStamps {
549
711
  }
550
712
  this.detectionBoxes = [];
551
713
  this.alignmentStartTime = undefined;
714
+ this.hasDocumentDetected = false;
552
715
  this.serviceContainer?.cleanup();
553
716
  }
554
717
  render() {
@@ -567,7 +730,7 @@ export class JaakStamps {
567
730
  deviceType: 'desktop',
568
731
  preferredFacing: null
569
732
  };
570
- return (h("div", { key: '91be2bd01dfa62ef439ff4b3d029539248b69364', class: "detector-container" }, h("div", { key: '7dbfbe2b782a8d06e2303ec3edff4b5c68ba4444', class: "video-container" }, h("video", { key: 'a2b7ec84a2f49935a94f1afb12deb1137f472fcd', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '63cf0b7a05fc6d604f3a273fd902717542ba47f5', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
733
+ return (h("div", { key: 'd71fc140c7fc87160ceaf479f9b9beae7685e9bc', class: "detector-container" }, h("div", { key: 'd83347ab6ccacfe940fb66d17adc21cff126d875', class: "video-container" }, h("video", { key: 'fb69de65e8133c7936cfb03ec86acdf89d2a8112', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'c957341c1a750a1732afd6bb1045c1ba9bb2993d', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
571
734
  position: 'absolute',
572
735
  left: `${box.x}px`,
573
736
  top: `${box.y}px`,
@@ -576,7 +739,9 @@ export class JaakStamps {
576
739
  border: '2px solid #32406C',
577
740
  pointerEvents: 'none',
578
741
  boxSizing: 'border-box'
579
- } })))), this.isMaskReady && (h("div", { key: '089227de9415e523e99261eeeca5dcb981816a83', class: "overlay-mask" }, h("div", { key: '846bd914025e8da92ca85381a566c6d93caea2ea', class: "card-outline" }, h("div", { key: '1f49ae76e9066a65e848e55e19839547d8b9bef0', class: "side side-top" }), h("div", { key: '5b69c6bcf604042f86383e9bf122204d4fe1aeb1', class: "side side-right" }), h("div", { key: '8aa4c1f09c8b7046a369ed8d7fae1a96a525f778', class: "side side-bottom" }), h("div", { key: 'd8826c0347a219c154703d165ee5b4ced33f7a3f', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '916e038d0d0f80971629916189ef39c2689e4e43', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: '02b01dff08bdbf042c6df1403f955601964bff28', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: 'cd88fd0567e5b792ddc623c281616ea35616f8f6', class: "camera-controls" }, h("button", { key: '9d2a63a53926d0c987e551bd49caa8143b4e15d4', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '43e9ea946f88498082339de62cf54cba352e765e', class: "camera-selector-dropdown" }, h("div", { key: '9fe39e8fa32ee458e4261d3f0d8fd40d50fefcf4', class: "camera-selector-header" }, h("span", { key: 'edf45c7bd8c5a43df9aba3ff6310cc5d06bb1aa2' }, "Seleccionar C\u00E1mara"), h("button", { key: 'dd5d417bb5eadd3f3b488ecd901d8dfa075c29c8', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '8edcd693f137ddd4ada9654c01615a17c90cb9c3', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'b7f050513f25af03ca72a735bd8745f71a456d76', class: "device-info" }, h("small", { key: '34305a7ca3418d46bceb2a9923a664fd5a0062fa' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '54ec511bd6d22c8c25eb41717abcebd89ed315f8', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '9392bccfd3576b1858610b543c0ac0b747cef153', class: "flip-animation" }, h("div", { key: '030dc63ea455afa24fd0b370425aae14ab322613', class: "id-card-icon" }), h("div", { key: '3f7d8985af2d6eaec21699b87d5aeb0b27d2d55e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '0da0a234e87013fb2ccc652cb920318cf00b6733', class: "success-animation" }, h("div", { key: 'f6b62ff21e147c8b365ab8c88e42dcb106cb8743', class: "check-icon" }), h("div", { key: '8c8b56561234e140cd658c5fad252cd6a1e0251c', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '684119ec540276cc28a49724d6177c72aff74b02', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'abc9a89349b9d8d76e03c765aa0084ec364f73a8', class: "status-spinner" })), h("div", { key: '6903b83962ee971a95a18e9ac793d872a7ce4551', class: "status-content" }, h("div", { key: 'c01dd0268a223ee269b616cea19a9cdb69cc048f', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '1f6ebecc7a1d058c34aca5b4572ace8a81b5ab69', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'eff03d22449aafac6c986e71af2aafd487b1776d', class: "performance-monitor" }, h("div", { key: '66dfd57c92f7ee67e7c52293b3ad3f1105793e70', class: "performance-expanded" }, h("div", { key: '0d952f89275e551a1e4cdb9b215e1ba48776ebd4', class: "metrics-row" }, h("div", { key: '36fab2101078e4da16a3a3ac50d927077d9e0874', class: "metric-compact" }, h("span", { key: 'a694bf26ff69522a6462963d735ffa145513d84c', class: "metric-label" }, "FPS"), h("span", { key: '02a250b3863f8f158a4103e0817cf14accdb7b30', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'd6e849779f13ae0ee3641d98148f0ab151877157', class: "metric-compact" }, h("span", { key: '983411baa6e79e3bfe7ad277879361c023dc6d48', class: "metric-label" }, "MEM"), h("span", { key: 'eea7060b9cef780ed47a534b7bc69c94162b67d9', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'e0e5e80c208f1eadc606c20df844efcc66f6599c', class: "metrics-row" }, h("div", { key: 'd27ae06d877097c99c1796c36284427861ae0be3', class: "metric-compact" }, h("span", { key: '229837e3037ce64fc5930a5732ad6a0e323371e9', class: "metric-label" }, "INF"), h("span", { key: '82002332976cddf7dbb2aa6346ad9da0ce0901df', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '243afc7933e169084eda28cca270198420a4341d', class: "metric-compact" }, h("span", { key: '834e9a69791abc42545dddc1c0de7372a3292d60', class: "metric-label" }, "FRAME"), h("span", { key: '9106f8989818aa9111d7571006224befe7837406', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: 'da911fc95ce6151fb5fb64b8d5df53d6b693ffef', class: "metrics-row" }, h("div", { key: 'c813a7152d51bd2402b9c2bdf5bd1fe09b6df7db', class: "metric-compact" }, h("span", { key: 'b5a7807764a897663ce9d37111188c26bcf2cb6d', class: "metric-label" }, "DET"), h("span", { key: '17ec09407ac24800e0ec965c7b3a037730a6afd4', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '5aa0b6f4d1643e3ec224d1a9d89d4d37407d3c58', class: "metric-compact" }, h("span", { key: '6b8efdcf07e9eac7d5036a508804b017ccedaea9', class: "metric-label" }, "RATE"), h("span", { key: '038a5c3377185784c806c614f0c098e417360acf', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '3317e18916b62bce70cc02540cfc274d588b466a', class: "watermark" }, h("img", { key: 'adf070f99c17c3fdfc3e2c69b1a63dfef66ad71e', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
742
+ } })))), this.isMaskReady && (h("div", { key: 'cf81504f7f129a2bb2789dd7addfe8d365fd7973', class: "overlay-mask" }, h("div", { key: 'b0f8ab1e02d1731295b95cbca4b44be430c6104d', class: "card-outline" }, h("div", { key: 'fadb33b8dc3c2cbfbb78aa0ff4d273b2b26a3565', class: "side side-top" }), h("div", { key: '3f3ca187195c78f2c3daffa481cc275f970c68a5', class: "side side-right" }), h("div", { key: '1e6bac145a85bbee2dab24da81b8064bc4ac282b', class: "side side-bottom" }), h("div", { key: '9bef1cd7afcef6cb1975a1103ec57d33d89f7640', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '9198d8b8fb1f4a17ec615943a09844f47a639048', class: "guide-text" }, this.enableQualityValidation && this.hasDocumentDetected && this.qualityFeedback.message ?
743
+ this.qualityFeedback.message :
744
+ 'Alinee su identificación con el marco')), this.enableQualityValidation && this.hasDocumentDetected && captureState.isVideoActive && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '04afa661c1d988d9bedb48c78672de233dd49f9b', class: `quality-indicator ${this.qualityFeedback.hasIssues ? 'warning' : 'good'}` }, h("div", { key: 'bc789e19894de1d3dfd4a232e05e612d804e0e6c', class: "quality-score" }, this.qualityFeedback.score > 0 ? `${this.qualityFeedback.score}%` : ''), h("div", { key: '0750f5e5dfdb7a7c054d01965146d34a5bb4485a', class: `quality-status ${this.qualityFeedback.canCapture ? 'ready' : 'not-ready'}` }, this.qualityFeedback.canCapture ? '✓' : '!')))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: 'cbe391726b5d62acbb466c36b135e0556fc488f7', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: '929f8fe054b30e3c30bf066319e957a633fa587c', class: "camera-controls" }, h("button", { key: 'b4f68dd3a6865cba49c3f5d38c43874b7b9b364d', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: 'c9315ff70704fdc265af07bf765b6247814563e6', class: "camera-selector-dropdown" }, h("div", { key: 'f4950e7e3c3e279c48310a4049550f3143fb4d3f', class: "camera-selector-header" }, h("span", { key: '4cfacf909549b0e18bd8ac6e54e127ce09b75335' }, "Seleccionar C\u00E1mara"), h("button", { key: '13d7679c5235f41f0dbc38b9f397483cf4ec9ffd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '6c97cb8ba9486c33a633901d9dd49765f87d92ed', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '35796316fc950b8491f6167303b549b098abbf58', class: "device-info" }, h("small", { key: '83c59a8cb0c0b67a37163fe75c1f1f383fcb00f4' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: 'be5b316abb21c06cf5bb69ed090d43d45537a9e6', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '43e64fcc325c7c96871a99838c5fa094c6774af2', class: "flip-animation" }, h("div", { key: '39223d9b593716e76912ade58b556c7212a7e05f', class: "id-card-icon" }), h("div", { key: '79463b43724c3c56c45bd5b41337117d7d415618', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '7be8ca4d0bd59b1e4127254ff68e41321aa387cc', class: "success-animation" }, h("div", { key: '03a3e625f622c546a3016f85e029f9ea165f65c3', class: "check-icon" }), h("div", { key: '9c63faad517dad4b2ede8ec5a0998ad0b5c807d5', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '94aa38c67fdaf328e74a2c1ec476987bf8e483d5', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '52a460f2fe11505158331cc06d7121db80bdfed9', class: "status-spinner" })), h("div", { key: '1eabfed00df31ff30fdf818f3d33cd837a7f31a5', class: "status-content" }, h("div", { key: '4bdbadf18fc48f2fad5dafd9ddf4a78f0a81dbf5', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '14c41346fc9fc306315cdda1c54ec1e828c38f62', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '21fb0ca08547143648097f2aa4f3eb3ade73bce1', class: "performance-monitor" }, h("div", { key: '88c990f42b5c87c9bc971acd228fc84bacec861b', class: "performance-expanded" }, h("div", { key: '351215f4b8ab202b42a4c9b0bfa8665485469f64', class: "metrics-row" }, h("div", { key: '28a2c5386ba99850f7dc41add6fb4ef073a6b8cb', class: "metric-compact" }, h("span", { key: '7a2ab758f9baf1264cd4ad70ff30f42a35c41065', class: "metric-label" }, "FPS"), h("span", { key: '1c4370d016f2bdcf61275b4869bddbd8109a7b2a', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'e79c27b4cbad3e70f2df917a5147dd125d51304d', class: "metric-compact" }, h("span", { key: 'aee4c178ae751cfe94735ef9fcb78f273f9cb885', class: "metric-label" }, "MEM"), h("span", { key: '823e98dec413968ee5080b29839729e6ac9f91d2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'e29037f684c82579a711aff022060c97ab9d93cf', class: "metrics-row" }, h("div", { key: '2917f3c4b026e3106fad741944e582aff36e10e0', class: "metric-compact" }, h("span", { key: 'e8783e8e2b18b29b37342eea376a75f257a46850', class: "metric-label" }, "INF"), h("span", { key: '30cdc1db3189135bc772aab5c31e081294ebd431', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'c481ec26394e35efe38047e5fa1b835e520f49b8', class: "metric-compact" }, h("span", { key: 'c806cae91f3fcad896bd6053b6a0391ab518ab6f', class: "metric-label" }, "FRAME"), h("span", { key: 'af9996e2093c918ea903d036e637905baeef1aa2', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '48422447a6d00f78a9f861bfc50de4ef5a628a57', class: "metrics-row" }, h("div", { key: 'c88a03fbff56f68796df33187256fd5a5a68f763', class: "metric-compact" }, h("span", { key: '5e366c35865f8bf45aecac126ce98e0e17be0825', class: "metric-label" }, "DET"), h("span", { key: '123570ac9ae59cf5ef1b992ea6c4563b753990c3', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'dea3a496f84e80f9d6f27173c8fa2e4149bf8e0b', class: "metric-compact" }, h("span", { key: 'fe47d6c16ca84fff8824bef92d8ce9efe50dfa38', class: "metric-label" }, "RATE"), h("span", { key: '873846eb42c1ed97f4e9493871e51741766c852f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'b485b1d8fafee0314ffbf1c7934ffc9a3d4c6902', class: "watermark" }, h("img", { key: 'f5fae340c3e96ecafe26aa819dbc2411038aa953', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
580
745
  }
581
746
  // Utility methods
582
747
  updateDetectionBoxes(boxes) {
@@ -648,15 +813,38 @@ export class JaakStamps {
648
813
  if (allSidesAligned && bestBox) {
649
814
  cardOutline?.classList.add('perfect-match');
650
815
  corners?.forEach(corner => corner.classList.add('perfect-match'));
651
- if (!this.hasScreenshotTaken) {
816
+ // Check quality validation before allowing capture
817
+ const qualityCheckPassed = !this.enableQualityValidation || this.qualityFeedback.canCapture;
818
+ // Debug logging
819
+ this.logger.state('CAPTURE_EVALUATION', {
820
+ allSidesAligned: true,
821
+ hasScreenshotTaken: this.hasScreenshotTaken,
822
+ qualityCheckPassed,
823
+ enableQualityValidation: this.enableQualityValidation,
824
+ qualityFeedback: this.qualityFeedback,
825
+ captureDelay: this.captureDelay,
826
+ alignmentStartTime: this.alignmentStartTime
827
+ });
828
+ if (!this.hasScreenshotTaken && qualityCheckPassed) {
652
829
  const currentTime = Date.now();
653
830
  // Initialize alignment start time if not set
654
831
  if (!this.alignmentStartTime) {
655
832
  this.alignmentStartTime = currentTime;
833
+ this.logger.state('ALIGNMENT_TIMER_STARTED', { startTime: currentTime });
656
834
  }
657
- // Check if document has been aligned for 1 second
835
+ // Check if document has been aligned for the configured delay
658
836
  const alignmentDuration = currentTime - this.alignmentStartTime;
659
- if (alignmentDuration >= 1000) {
837
+ this.logger.state('ALIGNMENT_DURATION_CHECK', {
838
+ alignmentDuration,
839
+ captureDelay: this.captureDelay,
840
+ readyToCapture: alignmentDuration >= this.captureDelay
841
+ });
842
+ if (alignmentDuration >= this.captureDelay) {
843
+ this.logger.state('TRIGGERING_CAPTURE', {
844
+ alignmentDuration,
845
+ captureDelay: this.captureDelay,
846
+ qualityScore: this.qualityFeedback.score
847
+ });
660
848
  this.lastDetectedBox = bestBox;
661
849
  this.takeScreenshot().catch(error => {
662
850
  this.logger.error('Error al tomar captura de pantalla:', error);
@@ -668,6 +856,17 @@ export class JaakStamps {
668
856
  }, 2000);
669
857
  }
670
858
  }
859
+ else if (!qualityCheckPassed) {
860
+ // Reset alignment timer if quality check fails
861
+ this.logger.state('QUALITY_CHECK_FAILED', {
862
+ enableQualityValidation: this.enableQualityValidation,
863
+ canCapture: this.qualityFeedback.canCapture,
864
+ qualityScore: this.qualityFeedback.score,
865
+ hasIssues: this.qualityFeedback.hasIssues
866
+ });
867
+ this.alignmentStartTime = undefined;
868
+ cardOutline?.classList.add('quality-warning');
869
+ }
671
870
  }
672
871
  else {
673
872
  cardOutline?.classList.remove('perfect-match');
@@ -856,6 +1055,7 @@ export class JaakStamps {
856
1055
  this.lastInferenceTime = 0;
857
1056
  this.detectionBoxes = [];
858
1057
  this.alignmentStartTime = undefined;
1058
+ this.hasDocumentDetected = false;
859
1059
  if (this.alignmentTimer) {
860
1060
  clearTimeout(this.alignmentTimer);
861
1061
  this.alignmentTimer = undefined;
@@ -1063,6 +1263,146 @@ export class JaakStamps {
1063
1263
  "setter": false,
1064
1264
  "reflect": false,
1065
1265
  "defaultValue": "'auto'"
1266
+ },
1267
+ "captureDelay": {
1268
+ "type": "number",
1269
+ "attribute": "capture-delay",
1270
+ "mutable": false,
1271
+ "complexType": {
1272
+ "original": "number",
1273
+ "resolved": "number",
1274
+ "references": {}
1275
+ },
1276
+ "required": false,
1277
+ "optional": false,
1278
+ "docs": {
1279
+ "tags": [],
1280
+ "text": ""
1281
+ },
1282
+ "getter": false,
1283
+ "setter": false,
1284
+ "reflect": false,
1285
+ "defaultValue": "1000"
1286
+ },
1287
+ "enableQualityValidation": {
1288
+ "type": "boolean",
1289
+ "attribute": "enable-quality-validation",
1290
+ "mutable": false,
1291
+ "complexType": {
1292
+ "original": "boolean",
1293
+ "resolved": "boolean",
1294
+ "references": {}
1295
+ },
1296
+ "required": false,
1297
+ "optional": false,
1298
+ "docs": {
1299
+ "tags": [],
1300
+ "text": ""
1301
+ },
1302
+ "getter": false,
1303
+ "setter": false,
1304
+ "reflect": false,
1305
+ "defaultValue": "true"
1306
+ },
1307
+ "qualityThreshold": {
1308
+ "type": "number",
1309
+ "attribute": "quality-threshold",
1310
+ "mutable": false,
1311
+ "complexType": {
1312
+ "original": "number",
1313
+ "resolved": "number",
1314
+ "references": {}
1315
+ },
1316
+ "required": false,
1317
+ "optional": false,
1318
+ "docs": {
1319
+ "tags": [],
1320
+ "text": ""
1321
+ },
1322
+ "getter": false,
1323
+ "setter": false,
1324
+ "reflect": false,
1325
+ "defaultValue": "60"
1326
+ },
1327
+ "minQualityScore": {
1328
+ "type": "number",
1329
+ "attribute": "min-quality-score",
1330
+ "mutable": false,
1331
+ "complexType": {
1332
+ "original": "number",
1333
+ "resolved": "number",
1334
+ "references": {}
1335
+ },
1336
+ "required": false,
1337
+ "optional": false,
1338
+ "docs": {
1339
+ "tags": [],
1340
+ "text": ""
1341
+ },
1342
+ "getter": false,
1343
+ "setter": false,
1344
+ "reflect": false,
1345
+ "defaultValue": "45"
1346
+ },
1347
+ "minFocusScore": {
1348
+ "type": "number",
1349
+ "attribute": "min-focus-score",
1350
+ "mutable": false,
1351
+ "complexType": {
1352
+ "original": "number",
1353
+ "resolved": "number",
1354
+ "references": {}
1355
+ },
1356
+ "required": false,
1357
+ "optional": false,
1358
+ "docs": {
1359
+ "tags": [],
1360
+ "text": ""
1361
+ },
1362
+ "getter": false,
1363
+ "setter": false,
1364
+ "reflect": false,
1365
+ "defaultValue": "16"
1366
+ },
1367
+ "minBlurScore": {
1368
+ "type": "number",
1369
+ "attribute": "min-blur-score",
1370
+ "mutable": false,
1371
+ "complexType": {
1372
+ "original": "number",
1373
+ "resolved": "number",
1374
+ "references": {}
1375
+ },
1376
+ "required": false,
1377
+ "optional": false,
1378
+ "docs": {
1379
+ "tags": [],
1380
+ "text": ""
1381
+ },
1382
+ "getter": false,
1383
+ "setter": false,
1384
+ "reflect": false,
1385
+ "defaultValue": "22"
1386
+ },
1387
+ "maxReflectionScore": {
1388
+ "type": "number",
1389
+ "attribute": "max-reflection-score",
1390
+ "mutable": false,
1391
+ "complexType": {
1392
+ "original": "number",
1393
+ "resolved": "number",
1394
+ "references": {}
1395
+ },
1396
+ "required": false,
1397
+ "optional": false,
1398
+ "docs": {
1399
+ "tags": [],
1400
+ "text": ""
1401
+ },
1402
+ "getter": false,
1403
+ "setter": false,
1404
+ "reflect": false,
1405
+ "defaultValue": "15"
1066
1406
  }
1067
1407
  };
1068
1408
  }
@@ -1074,7 +1414,9 @@ export class JaakStamps {
1074
1414
  "shouldMirrorVideo": {},
1075
1415
  "showCameraSelector": {},
1076
1416
  "isSwitchingCamera": {},
1417
+ "hasDocumentDetected": {},
1077
1418
  "currentStatus": {},
1419
+ "qualityFeedback": {},
1078
1420
  "performanceData": {}
1079
1421
  };
1080
1422
  }
@@ -1286,6 +1628,150 @@ export class JaakStamps {
1286
1628
  "text": "",
1287
1629
  "tags": []
1288
1630
  }
1631
+ },
1632
+ "setCaptureDelay": {
1633
+ "complexType": {
1634
+ "signature": "(delay: number) => Promise<{ success: boolean; captureDelay: number; }>",
1635
+ "parameters": [{
1636
+ "name": "delay",
1637
+ "type": "number",
1638
+ "docs": ""
1639
+ }],
1640
+ "references": {
1641
+ "Promise": {
1642
+ "location": "global",
1643
+ "id": "global::Promise"
1644
+ }
1645
+ },
1646
+ "return": "Promise<{ success: boolean; captureDelay: number; }>"
1647
+ },
1648
+ "docs": {
1649
+ "text": "",
1650
+ "tags": []
1651
+ }
1652
+ },
1653
+ "getCaptureDelay": {
1654
+ "complexType": {
1655
+ "signature": "() => Promise<number>",
1656
+ "parameters": [],
1657
+ "references": {
1658
+ "Promise": {
1659
+ "location": "global",
1660
+ "id": "global::Promise"
1661
+ }
1662
+ },
1663
+ "return": "Promise<number>"
1664
+ },
1665
+ "docs": {
1666
+ "text": "",
1667
+ "tags": []
1668
+ }
1669
+ },
1670
+ "setTorchEnabled": {
1671
+ "complexType": {
1672
+ "signature": "(enabled: boolean) => Promise<{ success: boolean; enabled: boolean; }>",
1673
+ "parameters": [{
1674
+ "name": "enabled",
1675
+ "type": "boolean",
1676
+ "docs": ""
1677
+ }],
1678
+ "references": {
1679
+ "Promise": {
1680
+ "location": "global",
1681
+ "id": "global::Promise"
1682
+ }
1683
+ },
1684
+ "return": "Promise<{ success: boolean; enabled: boolean; }>"
1685
+ },
1686
+ "docs": {
1687
+ "text": "",
1688
+ "tags": []
1689
+ }
1690
+ },
1691
+ "focusAtPoint": {
1692
+ "complexType": {
1693
+ "signature": "(x: number, y: number) => Promise<{ success: boolean; coordinates: { x: number; y: number; }; }>",
1694
+ "parameters": [{
1695
+ "name": "x",
1696
+ "type": "number",
1697
+ "docs": ""
1698
+ }, {
1699
+ "name": "y",
1700
+ "type": "number",
1701
+ "docs": ""
1702
+ }],
1703
+ "references": {
1704
+ "Promise": {
1705
+ "location": "global",
1706
+ "id": "global::Promise"
1707
+ }
1708
+ },
1709
+ "return": "Promise<{ success: boolean; coordinates: { x: number; y: number; }; }>"
1710
+ },
1711
+ "docs": {
1712
+ "text": "",
1713
+ "tags": []
1714
+ }
1715
+ },
1716
+ "getImageQuality": {
1717
+ "complexType": {
1718
+ "signature": "() => Promise<{ qualityScore: any; overallQuality: any; issues: any; recommendations: any; canCapture: boolean; }>",
1719
+ "parameters": [],
1720
+ "references": {
1721
+ "Promise": {
1722
+ "location": "global",
1723
+ "id": "global::Promise"
1724
+ }
1725
+ },
1726
+ "return": "Promise<{ qualityScore: any; overallQuality: any; issues: any; recommendations: any; canCapture: boolean; }>"
1727
+ },
1728
+ "docs": {
1729
+ "text": "",
1730
+ "tags": []
1731
+ }
1732
+ },
1733
+ "setQualityThresholds": {
1734
+ "complexType": {
1735
+ "signature": "(thresholds: QualityThresholds) => Promise<{ success: boolean; thresholds: QualityThresholds; }>",
1736
+ "parameters": [{
1737
+ "name": "thresholds",
1738
+ "type": "QualityThresholds",
1739
+ "docs": ""
1740
+ }],
1741
+ "references": {
1742
+ "Promise": {
1743
+ "location": "global",
1744
+ "id": "global::Promise"
1745
+ },
1746
+ "QualityThresholds": {
1747
+ "location": "import",
1748
+ "path": "../../services/interfaces/IDetectionService",
1749
+ "id": "src/services/interfaces/IDetectionService.ts::QualityThresholds"
1750
+ }
1751
+ },
1752
+ "return": "Promise<{ success: boolean; thresholds: QualityThresholds; }>"
1753
+ },
1754
+ "docs": {
1755
+ "text": "",
1756
+ "tags": []
1757
+ }
1758
+ },
1759
+ "getQualityThresholds": {
1760
+ "complexType": {
1761
+ "signature": "() => Promise<{ minQualityScore: number; minFocusScore: number; minBlurScore: number; maxReflectionScore: number; }>",
1762
+ "parameters": [],
1763
+ "references": {
1764
+ "Promise": {
1765
+ "location": "global",
1766
+ "id": "global::Promise"
1767
+ }
1768
+ },
1769
+ "return": "Promise<{ minQualityScore: number; minFocusScore: number; minBlurScore: number; maxReflectionScore: number; }>"
1770
+ },
1771
+ "docs": {
1772
+ "text": "",
1773
+ "tags": []
1774
+ }
1289
1775
  }
1290
1776
  };
1291
1777
  }