@jaak.ai/stamps 2.1.0-dev.1 → 2.1.0-dev.2

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 (30) hide show
  1. package/README.md +11 -1
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +219 -18
  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 +164 -9
  8. package/dist/collection/components/my-component/my-component.js +223 -16
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/DetectionService.js +16 -1
  11. package/dist/collection/services/DetectionService.js.map +1 -1
  12. package/dist/collection/services/ServiceContainer.js +3 -2
  13. package/dist/collection/services/ServiceContainer.js.map +1 -1
  14. package/dist/components/jaak-stamps.js +221 -18
  15. package/dist/components/jaak-stamps.js.map +1 -1
  16. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  17. package/dist/esm/jaak-stamps.entry.js +219 -18
  18. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  19. package/dist/esm/loader.js +1 -1
  20. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  21. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  22. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +2 -0
  23. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +1 -0
  24. package/dist/types/components/my-component/my-component.d.ts +6 -0
  25. package/dist/types/components.d.ts +8 -0
  26. package/dist/types/services/DetectionService.d.ts +2 -1
  27. package/dist/types/services/ServiceContainer.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
  30. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js.map +0 -1
@@ -5,7 +5,7 @@ var index = require('./index-BfhtOB0D.js');
5
5
  const defineCustomElements = async (win, options) => {
6
6
  if (typeof window === 'undefined') return undefined;
7
7
  await index.globalScripts();
8
- return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
8
+ return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
9
9
  };
10
10
 
11
11
  exports.setNonce = index.setNonce;
@@ -170,18 +170,21 @@ video {
170
170
  top: 50%;
171
171
  left: 50%;
172
172
  transform: translate(-50%, -50%);
173
- color: #fff;
174
- font-size: 14px;
175
- font-weight: 600;
173
+ background: rgba(0, 0, 0, 0.25);
174
+ backdrop-filter: blur(20px);
175
+ border-radius: 12px;
176
+ border: 1px solid rgba(255, 255, 255, 0.1);
177
+ color: #ffffff;
178
+ font-size: 12px;
179
+ font-weight: 500;
176
180
  text-align: center;
177
181
  white-space: normal;
178
- background: rgba(0, 0, 0, 0.25);
179
- backdrop-filter: blur(12px);
180
- padding: 12px 20px;
181
- border-radius: 8px;
182
+ padding: 13px;
182
183
  max-width: 300px;
183
184
  z-index: 20;
184
- border: 1px solid rgba(255, 255, 255, 0.1);
185
+ height: fit-content;
186
+ width: fit-content;
187
+ animation: slideInFromTopCentered 0.3s ease-out;
185
188
  }
186
189
 
187
190
  /* Quality indicator */
@@ -523,6 +526,81 @@ video {
523
526
  max-width: 300px;
524
527
  }
525
528
 
529
+ /* Sección unificada para captura del reverso */
530
+ .back-capture-section {
531
+ position: absolute;
532
+ bottom: 20px;
533
+ left: 50%;
534
+ transform: translateX(-50%);
535
+ z-index: 25;
536
+ display: flex;
537
+ flex-direction: column;
538
+ align-items: center;
539
+ width: 100%;
540
+ max-width: 320px;
541
+ }
542
+
543
+
544
+ .back-capture-buttons {
545
+ display: flex;
546
+ gap: 12px;
547
+ align-items: center;
548
+ justify-content: center;
549
+ flex-wrap: wrap;
550
+ }
551
+
552
+ .capture-button {
553
+ pointer-events: auto;
554
+ background: #fff;
555
+ color: #333;
556
+ border: none;
557
+ border-radius: 25px;
558
+ padding: 12px 24px;
559
+ font-size: 14px;
560
+ font-weight: 600;
561
+ cursor: pointer;
562
+ transition: all 0.3s ease;
563
+ }
564
+
565
+ .capture-button:hover {
566
+ background: #f8f9fa;
567
+ }
568
+
569
+ .capture-button:active {
570
+ background: #e9ecef;
571
+ transform: translateY(1px);
572
+ }
573
+
574
+ .capture-button:disabled {
575
+ background: #e9ecef;
576
+ color: #6c757d;
577
+ cursor: not-allowed;
578
+ transform: none;
579
+ }
580
+
581
+ /* Responsive para móviles */
582
+ @media (max-width: 480px) {
583
+ .back-capture-section {
584
+ bottom: 16px;
585
+ max-width: 300px;
586
+ }
587
+
588
+
589
+ .back-capture-buttons {
590
+ flex-direction: column;
591
+ gap: 8px;
592
+ width: 100%;
593
+ }
594
+
595
+ .capture-button,
596
+ .back-capture-buttons .skip-button {
597
+ width: 100%;
598
+ max-width: 200px;
599
+ padding: 10px 20px;
600
+ font-size: 13px;
601
+ }
602
+ }
603
+
526
604
  .skip-explanation {
527
605
  background: rgba(0, 0, 0, 0.5);
528
606
  color: #ffffff;
@@ -575,6 +653,7 @@ video {
575
653
  display: flex;
576
654
  gap: 8px;
577
655
  pointer-events: auto;
656
+ animation: slideInFromTop 0.3s ease-out;
578
657
  }
579
658
 
580
659
 
@@ -665,6 +744,18 @@ video {
665
744
  }
666
745
  }
667
746
 
747
+ /* Animación específica para elementos centrados */
748
+ @keyframes slideInFromTopCentered {
749
+ 0% {
750
+ opacity: 0;
751
+ transform: translate(-50%, -50%) translateY(-8px) scale(0.95);
752
+ }
753
+ 100% {
754
+ opacity: 1;
755
+ transform: translate(-50%, -50%) translateY(0) scale(1);
756
+ }
757
+ }
758
+
668
759
  .camera-selector-header {
669
760
  display: flex;
670
761
  justify-content: space-between;
@@ -903,6 +994,12 @@ video {
903
994
  .status-description {
904
995
  font-size: 9px;
905
996
  }
997
+
998
+ .guide-text {
999
+ font-size: 11px;
1000
+ padding: 4px 8px;
1001
+ border-radius: 8px;
1002
+ }
906
1003
  }
907
1004
 
908
1005
  /* Animación de carga mejorada */
@@ -968,7 +1065,7 @@ video {
968
1065
  border: 1px solid rgba(255, 255, 255, 0.1);
969
1066
  z-index: 35;
970
1067
  width: fit-content;
971
- animation: slideInFromLeft 0.3s ease-out;
1068
+ animation: slideInFromTop 0.3s ease-out;
972
1069
  transition: all 0.3s ease;
973
1070
  }
974
1071
 
@@ -1127,3 +1224,61 @@ video {
1127
1224
  font-size: 10px;
1128
1225
  }
1129
1226
  }
1227
+
1228
+ /* Sección de captura manual */
1229
+ .manual-capture-section {
1230
+ position: absolute;
1231
+ bottom: 20px;
1232
+ left: 50%;
1233
+ transform: translateX(-50%);
1234
+ z-index: 25;
1235
+ display: flex;
1236
+ flex-direction: column;
1237
+ align-items: center;
1238
+ width: 100%;
1239
+ max-width: 300px;
1240
+ }
1241
+
1242
+
1243
+ .manual-capture-button {
1244
+ pointer-events: auto;
1245
+ background: #fff;
1246
+ color: #333;
1247
+ border: none;
1248
+ border-radius: 25px;
1249
+ padding: 12px 24px;
1250
+ font-size: 14px;
1251
+ font-weight: 600;
1252
+ cursor: pointer;
1253
+ transition: all 0.3s ease;
1254
+ }
1255
+
1256
+ .manual-capture-button:hover {
1257
+ background: #f8f9fa;
1258
+ }
1259
+
1260
+ .manual-capture-button:active {
1261
+ background: #e9ecef;
1262
+ transform: translateY(1px);
1263
+ }
1264
+
1265
+ .manual-capture-button:disabled {
1266
+ background: #e9ecef;
1267
+ color: #6c757d;
1268
+ cursor: not-allowed;
1269
+ transform: none;
1270
+ }
1271
+
1272
+ /* Responsive para móviles */
1273
+ @media (max-width: 480px) {
1274
+ .manual-capture-section {
1275
+ bottom: 16px;
1276
+ max-width: 280px;
1277
+ }
1278
+
1279
+
1280
+ .manual-capture-button {
1281
+ padding: 10px 20px;
1282
+ font-size: 13px;
1283
+ }
1284
+ }
@@ -7,6 +7,7 @@ export class JaakStamps {
7
7
  maskSize = 90;
8
8
  cropMargin = 20;
9
9
  useDocumentClassification = false;
10
+ useDocumentDetector = true;
10
11
  preferredCamera = 'auto';
11
12
  captureDelay = 1500;
12
13
  enableBackDocumentTimer = false;
@@ -47,6 +48,7 @@ export class JaakStamps {
47
48
  detectionRate: 0
48
49
  };
49
50
  backDocumentTimerRemaining = 0;
51
+ showManualCaptureButton = false;
50
52
  // Services
51
53
  serviceContainer;
52
54
  eventBus;
@@ -110,6 +112,7 @@ export class JaakStamps {
110
112
  maskSize: this.maskSize,
111
113
  cropMargin: this.cropMargin,
112
114
  useDocumentClassification: this.useDocumentClassification,
115
+ useDocumentDetector: this.useDocumentDetector,
113
116
  preferredCamera: this.preferredCamera,
114
117
  captureDelay: this.captureDelay
115
118
  };
@@ -167,27 +170,39 @@ export class JaakStamps {
167
170
  document.head.appendChild(script);
168
171
  await new Promise((resolve) => {
169
172
  script.onload = () => {
170
- setTimeout(() => {
171
- this.finalizeInitialization();
173
+ setTimeout(async () => {
174
+ await this.finalizeInitialization();
172
175
  resolve(undefined);
173
176
  }, 300);
174
177
  };
175
178
  });
176
179
  }
177
180
  else {
178
- setTimeout(() => {
179
- this.finalizeInitialization();
181
+ setTimeout(async () => {
182
+ await this.finalizeInitialization();
180
183
  }, 300);
181
184
  }
182
185
  }
183
- finalizeInitialization() {
186
+ async finalizeInitialization() {
184
187
  this.stateManager.updateCaptureState({ isLoading: false });
185
- this.currentStatus = {
186
- message: 'Listo para capturar',
187
- description: '',
188
- type: 'ready',
189
- isInitialized: true
190
- };
188
+ // Check camera permissions before showing "ready" status
189
+ const hasPermissions = await this.checkCameraPermissions();
190
+ if (hasPermissions) {
191
+ this.currentStatus = {
192
+ message: 'Listo para capturar',
193
+ description: '',
194
+ type: 'ready',
195
+ isInitialized: true
196
+ };
197
+ }
198
+ else {
199
+ this.currentStatus = {
200
+ message: 'Permisos de cámara requeridos',
201
+ description: 'Por favor, otorgue permisos de cámara para continuar',
202
+ type: 'error',
203
+ isInitialized: true
204
+ };
205
+ }
191
206
  this.emitReadyEvent();
192
207
  }
193
208
  updateStatus(message, description, type = 'loading') {
@@ -202,6 +217,26 @@ export class JaakStamps {
202
217
  const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
203
218
  this.isReady.emit(isDocumentReady);
204
219
  }
220
+ async checkCameraPermissions() {
221
+ try {
222
+ if (!navigator.permissions) {
223
+ // Fallback: try to access camera directly
224
+ try {
225
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true });
226
+ stream.getTracks().forEach(track => track.stop());
227
+ return true;
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ }
233
+ const permission = await navigator.permissions.query({ name: 'camera' });
234
+ return permission.state === 'granted';
235
+ }
236
+ catch (error) {
237
+ return false;
238
+ }
239
+ }
205
240
  handleStateChange(data) {
206
241
  // React to state changes from StateManager
207
242
  // This is where the component updates its internal state based on service state changes
@@ -225,6 +260,29 @@ export class JaakStamps {
225
260
  this.updateMaskDimensions(rect);
226
261
  }
227
262
  }
263
+ getGuideText(step) {
264
+ if (step === 'completed') {
265
+ return 'Proceso completado';
266
+ }
267
+ if (step === 'front') {
268
+ // Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
269
+ if (this.showManualCaptureButton) {
270
+ return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
271
+ }
272
+ // Instrucción básica de alineación
273
+ return 'Alinee su identificación con el marco';
274
+ }
275
+ else if (step === 'back') {
276
+ // Para el reverso, mostrar instrucciones específicas según el modo
277
+ if (this.useDocumentDetector) {
278
+ return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
279
+ }
280
+ else {
281
+ return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
282
+ }
283
+ }
284
+ return 'Alinee su identificación con el marco';
285
+ }
228
286
  updateMaskDimensions(containerRect) {
229
287
  if (!this.videoRef)
230
288
  return;
@@ -572,7 +630,12 @@ export class JaakStamps {
572
630
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
573
631
  const stream = await this.cameraService.setupCamera();
574
632
  // Paso 4: Inicializar video y ocultar estado inmediatamente
575
- this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
633
+ if (this.useDocumentDetector) {
634
+ this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
635
+ }
636
+ else {
637
+ this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
638
+ }
576
639
  await this.initializeVideoStream(stream);
577
640
  // Paso 5: Calibrar máscara
578
641
  if (this.detectionContainer) {
@@ -583,6 +646,10 @@ export class JaakStamps {
583
646
  // Finalizar e iniciar captura
584
647
  this.startTime = Date.now();
585
648
  this.stateManager.updateCaptureState({ isLoading: false });
649
+ // Mostrar botón manual si el detector está deshabilitado
650
+ if (!this.useDocumentDetector) {
651
+ this.showManualCaptureButton = true;
652
+ }
586
653
  this.detectFrame();
587
654
  }
588
655
  catch (err) {
@@ -614,6 +681,18 @@ export class JaakStamps {
614
681
  const captureState = this.stateManager.getCaptureState();
615
682
  if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
616
683
  return;
684
+ // Show manual capture button when document detector is disabled
685
+ if (!this.useDocumentDetector) {
686
+ this.showManualCaptureButton = true;
687
+ // Continue the loop for UI updates but skip detection logic
688
+ if (captureState.step !== 'completed') {
689
+ this.animationId = requestAnimationFrame(() => this.detectFrame());
690
+ }
691
+ return;
692
+ }
693
+ else {
694
+ this.showManualCaptureButton = false;
695
+ }
617
696
  if (captureState.isDetectionPaused) {
618
697
  if (captureState.step !== 'completed') {
619
698
  this.animationId = requestAnimationFrame(() => this.detectFrame());
@@ -742,7 +821,7 @@ export class JaakStamps {
742
821
  isCapturing: false
743
822
  };
744
823
  const cameraInfo = this.cameraInfoWithAutofocus;
745
- return (h("div", { key: '69a8df7969288e19088e8809f1fefcc89b237cda', class: "detector-container" }, h("div", { key: '79e59a315d266be9d4fb5eed5740e5ad825d5890', class: "video-container" }, h("video", { key: 'a3312788c739748b774a25acb30c2dcea7f01695', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'ddab107500c2dab24c140049bef0b44aafc35dc7', 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: {
824
+ return (h("div", { key: 'e0b32b1b41b58e3a58b552199384080e8f2c069b', class: "detector-container" }, h("div", { key: '03c77af311ff12d936994d629d58550cbaae190c', class: "video-container" }, h("video", { key: '94f15b34f34367a10d17816a31cd147adb57bc90', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '982be192792dfaf644eb173bfee9b26ebd229288', 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: {
746
825
  position: 'absolute',
747
826
  left: `${box.x}px`,
748
827
  top: `${box.y}px`,
@@ -751,9 +830,9 @@ export class JaakStamps {
751
830
  border: '2px solid #32406C',
752
831
  pointerEvents: 'none',
753
832
  boxSizing: 'border-box'
754
- } })))), this.isMaskReady && (h("div", { key: '370337089a3de949b8cb4ae84dc0037e6044b2d5', class: "overlay-mask" }, h("div", { key: 'cb35c2e42bf1729ad02a8fe7e7abaf27cf1a8d7a', class: "card-outline" }, h("div", { key: '637d72ada63805e7f25051cd1ad9093030cb025a', class: "side side-top" }), h("div", { key: 'c7a465aa686afa41f80433d949715b40f6f349dc', class: "side side-right" }), h("div", { key: '384aade0610b5bc7bdd7db350a70d82f9bebd8ab', class: "side side-bottom" }), h("div", { key: '5506cc346f3fd718e3fda4365f69ea780304b6d6', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'f5b9b62937eabab7c096b7417a13bbf6f822ee8a', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '74b122125a42839d2600cf99fb855140042ab761', class: "skip-section" }, h("div", { key: '0b4a39d1599854d6c445e2c35dcc89a0e4970846', class: "skip-explanation" }, "Si tu documento no tiene lado trasero, da clic en el bot\u00F3n para continuar con el proceso"), h("button", { key: 'b52b47df390cc099c8847ca8a676270bb154294d', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
833
+ } })))), this.isMaskReady && (h("div", { key: 'f5212ff70db65d2b836a3ac456a1701701630660', class: "overlay-mask" }, h("div", { key: '5080217f4962c2c7152def39cc978ba1c40a3a1c', class: "card-outline" }, h("div", { key: 'd37f1ca8723d79b10d1c6a3afaf54e2f2657ae79', class: "side side-top" }), h("div", { key: 'c5bb4f7d646a991f43598688ffa909c04697bb06', class: "side side-right" }), h("div", { key: '4a5040b4a236367dcdf72f4e72d497b8b6dee6c0', class: "side side-bottom" }), h("div", { key: 'c74ecef56eee8e92cd9491d693fa89a6c01bdbbd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '2a2932b0c03835193ebf86ddb5f13173b1dba367', class: "guide-text" }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '41fcf2d79106d3ed928fc2842cb6a6725ba4d4f6', class: "back-capture-section" }, h("div", { key: '98dbe1bbe454bad42c3e155247f25a9151aaf1f9', class: "back-capture-buttons" }, !this.useDocumentDetector && (h("button", { key: '01693bc0d6915a4d02f318268020dfbcf3a70b94', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), h("button", { key: '0a4ba55cbbea867b936a83a16a622a3c36a225f9', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
755
834
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
756
- : 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: 'f04e47749a25197c434b849844c67eadca5c6af3', class: "camera-controls" }, h("button", { key: '82625eef057f59b6dac11c4443ccb315231eac7f', 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: '61cfb89cd9bb549cfff2cb70293db06ee6cdf06b', class: "camera-selector-dropdown" }, h("div", { key: '60f751d8db7b305f2bdfdc089d2110bcff940150', class: "camera-selector-header" }, h("span", { key: '9dae045a34cc06f323ff4850921b495fc845be47' }, "Seleccionar C\u00E1mara"), h("button", { key: '4584d7964d91ddd7f3d980c3e5e90493530d33bd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '9881f2b4fc6ffbdc76db06a7b49d060cefbe6277', 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}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '3dfe9ffc7b4cc9afb199a78026cef6d4b469805e', class: "device-info" }, h("small", { key: 'bddc865a30469bd2724624b2f7c8f641f9d97a07' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '2fd963e44f69f80d17c6bbbfa3c9b5e11bc816be', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'ba4872ea5388a0ac3134be61feff5412373465ab', class: "flip-animation" }, h("div", { key: 'e83a92cea79f3e85aefc6964c5d9de7b495d511e', class: "id-card-icon" }), h("div", { key: '1d06b51fd6ad14d9b37aa6bca72bd13c951a386a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '503c377896d5fdc3ae8476b09f511d571f23ae72', class: "success-animation" }, h("div", { key: 'e1929c245b0d1479a6fb1513b0b37c53070c4291', class: "check-icon" }), h("div", { key: '024c897c70427b205390770fc3dfb999ffcc84d3', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '54e350707628e5c88ae2900e412e6bda2eace943', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '82ea10569ed46285686cf3e63885ca1941bb559c', class: "status-spinner" })), h("div", { key: '1fecf179c10965cccf1be3dc0a54afd377ba38ad', class: "status-content" }, h("div", { key: '1b301c1bbb02e5cac2e6babcb20c190662d45c68', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '5b6c61308d17176daade02c951a436d114a74100', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'b8104db64208dc87a617a072957c007f28055194', class: "performance-monitor" }, h("div", { key: 'e66aec071f10821be2713858dcc251c04f480e63', class: "performance-expanded" }, h("div", { key: '1ae09a91d0704ab21b9eb51097cf452fd5f44359', class: "metrics-row" }, h("div", { key: '340d4d16d048a5217fe13d5de63618c9ea221165', class: "metric-compact" }, h("span", { key: '20cfda14969ef70d232edd68f2e9902bd44b5d9a', class: "metric-label" }, "FPS"), h("span", { key: '3a089c2814a71ed6513bf05b0120a66aa159daee', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '23f700def988c3220fc5df37aeb57a0a20ff56b3', class: "metric-compact" }, h("span", { key: '441e8490a871469813a6d832114e03583d375e91', class: "metric-label" }, "MEM"), h("span", { key: '51f7c49164c9d5e699141c92c88375fc99b312ed', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '6547fee80e2a1db7c9ff889a1c478fc8248159e6', class: "metrics-row" }, h("div", { key: '7b2d1642fd122ee0fbed36387afb23152afe10c2', class: "metric-compact" }, h("span", { key: 'a9137119bf7da366f18feaf8f28807ee40ad7a25', class: "metric-label" }, "INF"), h("span", { key: '5cbc0c040102688f1039cdcd4bb58020248cf700', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'cb4a86f5da46e4081d997c8b8bb6a899f06dfcd2', class: "metric-compact" }, h("span", { key: '88ad5e1d842eb156cf6813ecaa9d2bec76a5999e', class: "metric-label" }, "FRAME"), h("span", { key: 'a9b14cd98b64dcfc058d3b68a3c168a9e092fb3c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '86e01f3596682cb8ad2ff5464748e6ecb7db374a', class: "metrics-row" }, h("div", { key: 'e73b9d39e9bcbc5d46fa0ba0a737700e3e5566d9', class: "metric-compact" }, h("span", { key: '8ecfe969dfe77c87d9da76d7390706014b4872f3', class: "metric-label" }, "DET"), h("span", { key: 'bd1a6a52e0bd1fb4d377413d7515e4e0f00da10f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '36539ae98787fd60642082db1bf89031003c4bef', class: "metric-compact" }, h("span", { key: '7f3a81629c09053e3beeca97f485da0f60412052', class: "metric-label" }, "RATE"), h("span", { key: '72399e0ac39270c82b265ce64e402f84cda9fb2c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4e3a914ba2f5993f85de7b9dd6dc93d76b41c96d', class: "watermark" }, h("img", { key: 'aa634c2ba2090f64ee9b41aa1d75a9114d46fd63', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
835
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '1db208a53dea37211bdd88254e575142b7abf91f', class: "camera-controls" }, h("button", { key: '90755839358f997c3ce2f4c36a8d17d39ea5f8ad', 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: 'ef7889ab82f9eaabcabec3e3dc3ebb7850818dd4', class: "camera-selector-dropdown" }, h("div", { key: 'e28fb005316a742318be3cf8582f520392b71bba', class: "camera-selector-header" }, h("span", { key: '97338030b1aedb50c5f5a588ea03c0f19bcb0ddd' }, "Seleccionar C\u00E1mara"), h("button", { key: '1537c907dfab17c232a256f9fc9210cbd0125cc6', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '509178119e6eed5fe0a6ea287904a53bd9fbecbc', 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}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '485f7e1a97c43ed280a106bde3c6ee910625025c', class: "device-info" }, h("small", { key: 'd76edba943f4e06c14024923496555fa1fe6d845' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'c5184dd93aa41cff61b88ea2046b481efc87c1f2', class: "manual-capture-section" }, h("button", { key: '6e45d9a0fec60469eb926ff353cb1a440d59f5fe', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (h("div", { key: '9e296a942ba51cc1fd152e76ae406620c84ed721', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '517db50f7273788d94d5e6db6c40f65e00e18784', class: "flip-animation" }, h("div", { key: 'fbf6a26c4cc0539bd16b45899d19984af3449610', class: "id-card-icon" }), h("div", { key: 'fb3a201864d04531a078074458aa6e7f6580d565', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '10c694e233d26523d538ff0d2edbf6dcfb8e37c8', class: "success-animation" }, h("div", { key: '87e545650825ab805e887667281ecdf8bdb34c45', class: "check-icon" }), h("div", { key: '359da71f391d68e9437b749b949ee90685b96b3f', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '7c5533bb875eb3d9cdcc6cfdfc20e4d5bd98d103', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'f32df46ab83279387de606eadf53cd4a4009f449', class: "status-spinner" })), h("div", { key: 'dd640de5a3b64f5f99363318e2fc70e32ccbc7db', class: "status-content" }, h("div", { key: '618c0ab9cb0a24b0af3f6946052beab0d660fe6d', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'e52c2a4945c7f5a190a446ac588aa1668c21f26d', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '61cea10bc3438fb74aa176d71c4238b070cb0f09', class: "performance-monitor" }, h("div", { key: '44f6b1accb56d99a236b22c0769f870638d9c768', class: "performance-expanded" }, h("div", { key: '6febc85e319752b0aebc0a4e4050ea6d597375a7', class: "metrics-row" }, h("div", { key: '96b81895ad3cd742c96308ed53a2126af76eb619', class: "metric-compact" }, h("span", { key: '774a11c6fed6a87db7673b018b3cdc378c5252ef', class: "metric-label" }, "FPS"), h("span", { key: 'cb866dcbea661e5f715b82c4dd760f786fd5bb2a', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'a935aaadb83ea400bfaa984d1d845bdfd87becc8', class: "metric-compact" }, h("span", { key: 'ffacaa2cb7838403a297145b70586e255cc845c7', class: "metric-label" }, "MEM"), h("span", { key: 'a1d7a7abec97dc0544d50b11b372f699f750b76f', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'bb3d156a46569a8c290bc518c42ec7957bc5b94e', class: "metrics-row" }, h("div", { key: '4cadd8f845e4c12120ed549e4a73e02c6cbd9257', class: "metric-compact" }, h("span", { key: '1fc5661222f495b79fd7eb126e04f52eecab6fb3', class: "metric-label" }, "INF"), h("span", { key: 'eca1f59d1c41ab99158eec81592bc7150bfeabe1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'f708e2757adcb03e633111d96bd6786fb6b80ea6', class: "metric-compact" }, h("span", { key: 'a13099ded64180996d6f65bf215483ba83daccbc', class: "metric-label" }, "FRAME"), h("span", { key: '2765cd1f7c1a49df9de64639cc1b777c79f89f4c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '13aaf85a1b4b66294c4204f02d956b64665aa766', class: "metrics-row" }, h("div", { key: '40012f3f646d70c535a606ebbe581a2fe6fd2e50', class: "metric-compact" }, h("span", { key: '03f8b17746fe404485820d06a50173678db2fd25', class: "metric-label" }, "DET"), h("span", { key: 'c4f87430f142355054d9361330d6a5d508974b70', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'cf366a41d7f7cda6046c32cc7c514eb7b46b05d1', class: "metric-compact" }, h("span", { key: '925e7de15b96ba4dbbcdbf8a9f0e3173bae49573', class: "metric-label" }, "RATE"), h("span", { key: '9701e60c7793f2c205f3c9ffd254b6a115b8a68c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '636aeb9b2b8dd992d8a28a8db44b213e12514096', class: "watermark" }, h("img", { key: '0407b550d962a6113e05ad75e25a1a1bbd76d9ce', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
757
836
  }
758
837
  // Utility methods
759
838
  updateDetectionBoxes(boxes) {
@@ -868,6 +947,113 @@ export class JaakStamps {
868
947
  }
869
948
  }
870
949
  }
950
+ async takeManualScreenshot() {
951
+ if (!this.videoRef)
952
+ return;
953
+ // When using manual capture, use mask coordinates for cropping
954
+ await this.takeScreenshotWithMaskCoordinates();
955
+ }
956
+ async takeScreenshotWithMaskCoordinates() {
957
+ if (!this.videoRef || !this.detectionContainer)
958
+ return;
959
+ this.stateManager.updateCaptureState({ isCapturing: true });
960
+ this.triggerCaptureAnimation();
961
+ // Use pooled canvas for optimization
962
+ const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
963
+ const captureCtx = captureCanvas.getContext('2d', { alpha: false });
964
+ captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
965
+ captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
966
+ // Calculate mask coordinates for cropping
967
+ const container = this.detectionContainer.parentElement;
968
+ const containerRect = container.getBoundingClientRect();
969
+ // Get mask dimensions from CSS properties
970
+ const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
971
+ const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
972
+ const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
973
+ const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
974
+ // Convert percentages to actual video coordinates
975
+ const videoWidth = this.videoRef.videoWidth;
976
+ const videoHeight = this.videoRef.videoHeight;
977
+ const videoAspectRatio = videoWidth / videoHeight;
978
+ const containerAspectRatio = containerRect.width / containerRect.height;
979
+ let displayedVideoWidth, displayedVideoHeight;
980
+ let videoOffsetX = 0, videoOffsetY = 0;
981
+ if (videoAspectRatio > containerAspectRatio) {
982
+ displayedVideoWidth = containerRect.width;
983
+ displayedVideoHeight = containerRect.width / videoAspectRatio;
984
+ videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
985
+ }
986
+ else {
987
+ displayedVideoHeight = containerRect.height;
988
+ displayedVideoWidth = containerRect.height * videoAspectRatio;
989
+ videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
990
+ }
991
+ // Calculate mask coordinates in video space
992
+ const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
993
+ const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
994
+ const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
995
+ const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
996
+ // Convert to video coordinates
997
+ const scaleX = videoWidth / displayedVideoWidth;
998
+ const scaleY = videoHeight / displayedVideoHeight;
999
+ const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
1000
+ const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
1001
+ const maskWidthInVideo = maskWidthInContainer * scaleX;
1002
+ const maskHeightInVideo = maskHeightInContainer * scaleY;
1003
+ // Calculate crop coordinates
1004
+ const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
1005
+ const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
1006
+ const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
1007
+ const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
1008
+ // Use pooled canvas for cropped version
1009
+ const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
1010
+ const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
1011
+ croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
1012
+ croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
1013
+ const captureState = this.stateManager.getCaptureState();
1014
+ if (captureState.step === 'front') {
1015
+ this.stateManager.setCapturedImages({
1016
+ front: {
1017
+ fullFrame: captureCanvas.toDataURL('image/png'),
1018
+ cropped: croppedCanvas.toDataURL('image/png')
1019
+ }
1020
+ });
1021
+ // Check if document classification is enabled (independent of detector)
1022
+ if (this.useDocumentClassification) {
1023
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
1024
+ if (classification && classification.class === 'passport') {
1025
+ this.completeProcess(true);
1026
+ this.returnCanvasToPool(captureCanvas);
1027
+ this.returnCanvasToPool(croppedCanvas);
1028
+ return;
1029
+ }
1030
+ }
1031
+ this.stateManager.updateCaptureState({
1032
+ step: 'back',
1033
+ isDetectionPaused: true,
1034
+ showFlipAnimation: true
1035
+ });
1036
+ setTimeout(() => {
1037
+ this.stateManager.updateCaptureState({
1038
+ showFlipAnimation: false,
1039
+ isDetectionPaused: false
1040
+ });
1041
+ this.startBackDocumentTimer();
1042
+ }, 3000);
1043
+ }
1044
+ else if (captureState.step === 'back') {
1045
+ this.stateManager.setCapturedImages({
1046
+ back: {
1047
+ fullFrame: captureCanvas.toDataURL('image/png'),
1048
+ cropped: croppedCanvas.toDataURL('image/png')
1049
+ }
1050
+ });
1051
+ this.completeProcess(false);
1052
+ }
1053
+ // Return canvases to pool after use
1054
+ this.returnCanvasToPool(captureCanvas);
1055
+ this.returnCanvasToPool(croppedCanvas);
1056
+ }
871
1057
  async takeScreenshot() {
872
1058
  if (!this.videoRef || !this.lastDetectedBox)
873
1059
  return;
@@ -1301,6 +1487,26 @@ export class JaakStamps {
1301
1487
  "reflect": false,
1302
1488
  "defaultValue": "false"
1303
1489
  },
1490
+ "useDocumentDetector": {
1491
+ "type": "boolean",
1492
+ "attribute": "use-document-detector",
1493
+ "mutable": false,
1494
+ "complexType": {
1495
+ "original": "boolean",
1496
+ "resolved": "boolean",
1497
+ "references": {}
1498
+ },
1499
+ "required": false,
1500
+ "optional": false,
1501
+ "docs": {
1502
+ "tags": [],
1503
+ "text": ""
1504
+ },
1505
+ "getter": false,
1506
+ "setter": false,
1507
+ "reflect": false,
1508
+ "defaultValue": "true"
1509
+ },
1304
1510
  "preferredCamera": {
1305
1511
  "type": "string",
1306
1512
  "attribute": "preferred-camera",
@@ -1395,7 +1601,8 @@ export class JaakStamps {
1395
1601
  "cameraInfoWithAutofocus": {},
1396
1602
  "currentStatus": {},
1397
1603
  "performanceData": {},
1398
- "backDocumentTimerRemaining": {}
1604
+ "backDocumentTimerRemaining": {},
1605
+ "showManualCaptureButton": {}
1399
1606
  };
1400
1607
  }
1401
1608
  static get events() {