@jaak.ai/stamps 2.1.0-dev.5 → 2.1.0-dev.7

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.
@@ -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"],"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],"performanceDegradedMode":[32],"showPerformanceMessage":[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],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"processingButton":[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;
@@ -571,11 +571,12 @@ video {
571
571
  transform: translateY(1px);
572
572
  }
573
573
 
574
- .capture-button:disabled {
575
- background: #e9ecef;
576
- color: #6c757d;
577
- cursor: not-allowed;
578
- transform: none;
574
+ .capture-button:disabled,
575
+ .capture-button.primary-button:disabled {
576
+ cursor: not-allowed !important;
577
+ transform: none !important;
578
+ opacity: 0.7 !important;
579
+ transition: none !important;
579
580
  }
580
581
 
581
582
  /* Responsive para móviles */
@@ -599,6 +600,14 @@ video {
599
600
  padding: 10px 20px;
600
601
  font-size: 13px;
601
602
  }
603
+
604
+ /* Ensure disabled styles work on mobile too */
605
+ .capture-button:disabled,
606
+ .capture-button.primary-button:disabled,
607
+ .skip-button:disabled {
608
+ opacity: 0.7 !important;
609
+ cursor: not-allowed !important;
610
+ }
602
611
  }
603
612
 
604
613
  .skip-explanation {
@@ -663,6 +672,13 @@ video {
663
672
  transform: translateY(1px);
664
673
  }
665
674
 
675
+ .skip-button:disabled {
676
+ cursor: not-allowed !important;
677
+ transform: none !important;
678
+ opacity: 0.7 !important;
679
+ transition: none !important;
680
+ }
681
+
666
682
  /* Camera controls */
667
683
  .camera-controls {
668
684
  position: absolute;
@@ -724,10 +740,13 @@ video {
724
740
  .button-spinner {
725
741
  width: 16px;
726
742
  height: 16px;
727
- border: 2px solid rgba(255, 255, 255, 0.3);
728
- border-top: 2px solid #ffffff;
743
+ border: 2px solid rgba(0, 0, 0, 0.2);
744
+ border-top: 2px solid #333333;
729
745
  border-radius: 50%;
730
746
  animation: spin 1s linear infinite;
747
+ display: inline-block;
748
+ margin-right: 8px;
749
+ vertical-align: middle;
731
750
  }
732
751
 
733
752
  @keyframes spin {
@@ -1282,10 +1301,10 @@ video {
1282
1301
  }
1283
1302
 
1284
1303
  .manual-capture-button:disabled {
1285
- background: #e9ecef;
1286
- color: #6c757d;
1287
- cursor: not-allowed;
1288
- transform: none;
1304
+ cursor: not-allowed !important;
1305
+ transform: none !important;
1306
+ opacity: 0.7 !important;
1307
+ transition: none !important;
1289
1308
  }
1290
1309
 
1291
1310
  /* Responsive para móviles */
@@ -1300,4 +1319,10 @@ video {
1300
1319
  padding: 10px 20px;
1301
1320
  font-size: 13px;
1302
1321
  }
1322
+
1323
+ /* Ensure disabled styles work on mobile too */
1324
+ .manual-capture-button:disabled {
1325
+ opacity: 0.7 !important;
1326
+ cursor: not-allowed !important;
1327
+ }
1303
1328
  }
@@ -51,6 +51,8 @@ export class JaakStamps {
51
51
  showManualCaptureButton = false;
52
52
  performanceDegradedMode = false; // Auto-switched to manual mode due to performance
53
53
  showPerformanceMessage = false; // Show performance message temporarily
54
+ captureStateVersion = 0; // Triggers re-render when StateManager changes
55
+ processingButton = null; // Track which button is processing
54
56
  // Services
55
57
  serviceContainer;
56
58
  eventBus;
@@ -102,14 +104,19 @@ export class JaakStamps {
102
104
  canvasPool = [];
103
105
  MAX_CANVAS_POOL_SIZE = 3;
104
106
  async componentDidLoad() {
105
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
106
- await this.initializeServices();
107
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
108
- await this.setupEventListeners();
109
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
110
- await this.initializeComponent();
111
- if (this.debug) {
112
- this.initializePerformanceMonitor();
107
+ try {
108
+ this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
109
+ await this.initializeServices();
110
+ this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
111
+ await this.setupEventListeners();
112
+ this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
113
+ await this.initializeComponent();
114
+ if (this.debug) {
115
+ this.initializePerformanceMonitor();
116
+ }
117
+ }
118
+ catch (error) {
119
+ this.updateStatus('Error de inicialización', error.message, 'error');
113
120
  }
114
121
  }
115
122
  async initializeServices() {
@@ -256,6 +263,9 @@ export class JaakStamps {
256
263
  this.updateMaskDimensions(rect);
257
264
  }
258
265
  }
266
+ triggerRerender() {
267
+ this.captureStateVersion = this.captureStateVersion + 1;
268
+ }
259
269
  getGuideText(step) {
260
270
  if (step === 'completed') {
261
271
  return 'Proceso completado';
@@ -421,8 +431,14 @@ export class JaakStamps {
421
431
  }
422
432
  }
423
433
  async skipBackCapture() {
434
+ console.log('🟡 Botón clicked: Saltar reverso');
435
+ // Set processing state immediately
436
+ this.processingButton = 'skip-back';
437
+ // Add small delay to show spinner
438
+ await new Promise(resolve => setTimeout(resolve, 100));
424
439
  const readyCheck = this.isComponentReady();
425
440
  if (!readyCheck.ready) {
441
+ this.processingButton = null;
426
442
  return { success: false, error: readyCheck.message };
427
443
  }
428
444
  try {
@@ -439,6 +455,10 @@ export class JaakStamps {
439
455
  catch (error) {
440
456
  return { success: false, error: error.message };
441
457
  }
458
+ finally {
459
+ // Always clear processing state when done
460
+ this.processingButton = null;
461
+ }
442
462
  }
443
463
  async getStatus() {
444
464
  const readyCheck = this.isComponentReady();
@@ -616,9 +636,53 @@ export class JaakStamps {
616
636
  // Always allow getting capture delay, even during initialization
617
637
  return this.captureDelay;
618
638
  }
639
+ // Memory and device capability detection
640
+ checkDeviceCapabilities() {
641
+ const deviceMemory = navigator.deviceMemory;
642
+ const hardwareConcurrency = navigator.hardwareConcurrency || 1;
643
+ const isLowEndDevice = hardwareConcurrency <= 2;
644
+ const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
645
+ const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
646
+ // Si tenemos info de memoria del dispositivo
647
+ if (deviceMemory && deviceMemory < 4) {
648
+ return {
649
+ canUseML: false,
650
+ reason: `Memoria del dispositivo (${deviceMemory}GB) insuficiente para detección automática. Modo manual activado para mejor rendimiento.`,
651
+ memoryMB: deviceMemory * 1024
652
+ };
653
+ }
654
+ // Dispositivos móviles de gama baja
655
+ if (isMobile && isLowEndDevice) {
656
+ return {
657
+ canUseML: false,
658
+ reason: 'Dispositivo móvil optimizado detectado. Modo manual activado para mejor rendimiento y duración de batería.',
659
+ memoryMB: null
660
+ };
661
+ }
662
+ // Safari tiene limitaciones conocidas con WASM
663
+ if (isSafari) {
664
+ return {
665
+ canUseML: false,
666
+ reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
667
+ memoryMB: null
668
+ };
669
+ }
670
+ return {
671
+ canUseML: true,
672
+ reason: 'Dispositivo compatible con detección automática.',
673
+ memoryMB: deviceMemory ? deviceMemory * 1024 : null
674
+ };
675
+ }
619
676
  // DETECTION METHODS
620
677
  async startDetection() {
621
678
  try {
679
+ // Verificar capacidades del dispositivo antes de cargar modelos
680
+ const capabilities = this.checkDeviceCapabilities();
681
+ // Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
682
+ if (!capabilities.canUseML && this.useDocumentDetector) {
683
+ this.switchToManualModeWithWarning(capabilities.reason);
684
+ return;
685
+ }
622
686
  // Paso 1: Verificar y cargar modelos si es necesario
623
687
  if (!this.detectionService.isModelLoaded()) {
624
688
  const loadStartTime = performance.now();
@@ -628,16 +692,32 @@ export class JaakStamps {
628
692
  await this.detectionService.loadModel();
629
693
  }
630
694
  catch (modelError) {
631
- this.switchToManualModeWithError('Error al cargar modelo de detección');
632
- throw modelError;
695
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
696
+ if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
697
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
698
+ this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
699
+ return;
700
+ }
701
+ else {
702
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
703
+ throw modelError;
704
+ }
633
705
  }
634
706
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
635
707
  try {
636
708
  await this.detectionService.loadClassificationModel();
637
709
  }
638
710
  catch (classificationError) {
639
- this.switchToManualModeWithError('Error al cargar modelo de clasificación');
640
- throw classificationError;
711
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
712
+ if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
713
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
714
+ this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
715
+ return;
716
+ }
717
+ else {
718
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
719
+ throw classificationError;
720
+ }
641
721
  }
642
722
  const loadEndTime = performance.now();
643
723
  const loadTime = loadEndTime - loadStartTime;
@@ -648,11 +728,27 @@ export class JaakStamps {
648
728
  }
649
729
  // Paso 2: Detectar y configurar dispositivos
650
730
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
651
- await this.cameraService.enumerateDevices();
652
- await this.updateCameraInfoWithAutofocus();
731
+ try {
732
+ await this.cameraService.enumerateDevices();
733
+ }
734
+ catch (enumerateError) {
735
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
736
+ }
737
+ try {
738
+ await this.updateCameraInfoWithAutofocus();
739
+ }
740
+ catch (cameraInfoError) {
741
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
742
+ }
653
743
  // Paso 3: Configurar cámara seleccionada
654
744
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
655
- const stream = await this.cameraService.setupCamera();
745
+ let stream;
746
+ try {
747
+ stream = await this.cameraService.setupCamera();
748
+ }
749
+ catch (setupError) {
750
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
751
+ }
656
752
  // Paso 4: Inicializar video y mostrar estado de análisis inicial
657
753
  if (this.useDocumentDetector) {
658
754
  this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
@@ -660,7 +756,12 @@ export class JaakStamps {
660
756
  else {
661
757
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
662
758
  }
663
- await this.initializeVideoStream(stream);
759
+ try {
760
+ await this.initializeVideoStream(stream);
761
+ }
762
+ catch (videoError) {
763
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
764
+ }
664
765
  // Paso 5: Calibrar máscara
665
766
  if (this.detectionContainer) {
666
767
  const container = this.detectionContainer.parentElement;
@@ -885,7 +986,7 @@ export class JaakStamps {
885
986
  isCapturing: false
886
987
  };
887
988
  const cameraInfo = this.cameraInfoWithAutofocus;
888
- return (h("div", { key: '687a01ebb87f3c9e4424f4bcd355cd68c21366f0', class: "detector-container" }, h("div", { key: 'd4c9f1fbf653ea7764dcc7265f5f7180c99fe5e7', class: "video-container" }, h("video", { key: '37a159daaaa4682d8e14a1dcdef32225706e1f20', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '74fe5c8612be7a8d904510c226d68a6860a0f67d', 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: {
989
+ return (h("div", { key: '51cc6d0f873691852f938940b131732252b970c1', class: "detector-container" }, h("div", { key: '009480499c0a2833bf9f6bd28003142ca3dd3750', class: "video-container" }, h("video", { key: '88a99a1a90c8d817e40549a255324b2da342797f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '7ede15ccd6f67b3081d907777de7b0d1a1b4cfd6', 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: {
889
990
  position: 'absolute',
890
991
  left: `${box.x}px`,
891
992
  top: `${box.y}px`,
@@ -894,9 +995,9 @@ export class JaakStamps {
894
995
  border: '2px solid #32406C',
895
996
  pointerEvents: 'none',
896
997
  boxSizing: 'border-box'
897
- } })))), this.isMaskReady && (h("div", { key: '4f2e282a8eb99947b1e67690ef236019e87ad87e', class: "overlay-mask" }, h("div", { key: '1d102684d59e5f7f5d6243f95e39ee0e1812032e', class: "card-outline" }, h("div", { key: '4ba0df1528de02cc037022948e425ffb13c49822', class: "side side-top" }), h("div", { key: '36bc3f2d39f61d39af2522ffe13b6df16c8bf00b', class: "side side-right" }), h("div", { key: '88d2402044de0309055c31fd98e596b88b1d51ce', class: "side side-bottom" }), h("div", { key: '72262621ee2c2b4824f3b313f27a5793fa4c123f', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'df9320c0f5518c04b1504d355223730e6d686779', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'e86f6eec1ea7712fb8c94d1455dd9c0c7c01db21', class: "back-capture-section" }, h("div", { key: 'ad965847b25eeae92eb22c3d89b09e953fabb2d2', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: '3e57f0bf7600bc650e44f40e3c1fd13747885781', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), h("button", { key: 'd598cd7778869b46b5c7ceafcb43c469b4b5c7e7', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
998
+ } })))), this.isMaskReady && (h("div", { key: '63beb4330ab171a5e63237b1d83dcace61422018', class: "overlay-mask" }, h("div", { key: '93cfac6898de6ece6c7dd8f02ba2b79ce6591755', class: "card-outline" }, h("div", { key: '3087d3c51f42ea8d720cea4170dcb68e8bb385e1', class: "side side-top" }), h("div", { key: '992e311b895da161d3b5c49dde346ae378139641', class: "side side-right" }), h("div", { key: '5fb3d7b704f08177b2b9c6e4fbaf685f251a3e53', class: "side side-bottom" }), h("div", { key: '76f6e2cb856632c58809ccdabdf3f7d9a588c9da', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '142dfc187e07100278e16b19018e4b5fb96d964a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'bba078ce4c9585631b6a8154f878464b1f779c8b', class: "back-capture-section" }, h("div", { key: 'ec94c9ba2cb373a539f0e6cc3eb356294cebf3ea', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: '30561e1fc88090c6bb4d0cd7909a33a9525b0ac2', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '4b3e3356507c7f654705425e201d0303d727db72', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: '1abfada41312658eab4966d8fcdb711a6c4762e6', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '3cab3ce751cb38174b6d061170a66ac654a90a38', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
898
999
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
899
- : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'b1508382d2b75584a19dade3f8e685a34ff2c3a3', class: "camera-controls" }, h("button", { key: '9c1f945b051f8e000f87fb3674ecfd386bdc2208', 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: '4adab3b896be86322be168db377cbacbcb3a7422', class: "camera-selector-dropdown" }, h("div", { key: 'a60e3f46cfe5997cf9b82d685a1574b7002c0071', class: "camera-selector-header" }, h("span", { key: '71e012a8a4953f584aeffc985040e434fadfb00b' }, "Seleccionar C\u00E1mara"), h("button", { key: '046ee4f6d81b34349ecad4e9de98f4d8653dad6b', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '539ad135914d9ceb3a6b9a2de2d9ae4ea7a996b3', 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: '082d1cc259af3c1fc95b53da1e754382e874b837', class: "device-info" }, h("small", { key: 'dad398582ac7bfd31ee43704d47fd3036fc59d06' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '231ff3eb4672b4ac310419cccc8cd6d1caab9e14', class: "manual-capture-section" }, h("button", { key: 'fa2fcc4bfe63bf2ccdba2c2ad3c8c4b4a5aabbb5', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (h("div", { key: '39426754973f1a9e07b56399f344add21bf9b848', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '307a7eb06b41a8baebe37ad26b1f5e4cf0a43ec1', class: "flip-animation" }, h("div", { key: '7070ad263e3894dc1488bb8a17f5bf23b7b757f0', class: "id-card-icon" }), h("div", { key: '81a0d8b6377b397bbeebe2e31800fe88f88593a0', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '741e5043bd57a3e13e39fd897a64cd20a75dad2e', class: "success-animation" }, h("div", { key: '6269a51c0651e5f76490906e50eb111f6a88f267', class: "check-icon" }), h("div", { key: '1c90a9cf6c90bb10af90660c714b7ff1ed6acfa0', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: 'f6746c1fb15cc238846f047743a9d45b1d4a41f8', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '00d198abdc90683e5f05a9fd9b54d44e55c16630', class: "status-spinner" })), h("div", { key: 'b0254dea36bae65692bfbd200dfb75eb5a3a50dc', class: "status-content" }, h("div", { key: 'e3e48ea04f40ddd7c66aa8eda3d341f24a9c3dc4', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'd3f40ca5b72a661f9631145880fc6d1bba878961', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'eabe40809e744eb62b2ac76733952060988b675c', class: "performance-monitor" }, h("div", { key: '44e51257d4ba2c2153749cf14f3aebbf22ca02ec', class: "performance-expanded" }, h("div", { key: '493fc680db2d1e82068eadda3786fad57e8594cc', class: "metrics-row" }, h("div", { key: 'aaa734d066246c9364d51c04d41a0a84d0b7e216', class: "metric-compact" }, h("span", { key: 'f0b59f76e8aa1723a429289b7898d35f22ff86d4', class: "metric-label" }, "FPS"), h("span", { key: 'a562359df92cabe40f54b355ffa264f246e7ec8e', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '9adca77e934ee3cd07fb54a056132e305637848c', class: "metric-compact" }, h("span", { key: '65ec83dca0e74b42065962d2a2e4bae7515b9fc1', class: "metric-label" }, "MEM"), h("span", { key: '427fc6b5b538742d0987b0240f526616b910ae69', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'fc551d8ef39308980437b72b3fb4c0bdb2bfbf67', class: "metrics-row" }, h("div", { key: 'b88785c108e5586fecc8702f3e4da883ace00f15', class: "metric-compact" }, h("span", { key: '31db440861e6fd9c2ee36ccd242f1b7bb3d47f45', class: "metric-label" }, "INF"), h("span", { key: 'af45bbb41110a04ed3d22a9e112b8644dd7ec355', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'deabe5ce0661557fabe4ccdde1af48beed564ad9', class: "metric-compact" }, h("span", { key: '072f9159e95b3f3226f07b879b377a3d34940825', class: "metric-label" }, "FRAME"), h("span", { key: '841de1ae8df76919c2b644c6ff813dd927b52013', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '68c6c777db1430987f20536849a3ed9169ec794a', class: "metrics-row" }, h("div", { key: '736e361beaa88d268041ec55fd73db0ee405e1ae', class: "metric-compact" }, h("span", { key: 'bfa6c3aa54a96e5eab32dc4fa0227ca836142561', class: "metric-label" }, "DET"), h("span", { key: 'f9a2479c470779403bf1cbfd4f433f80e5fa34d8', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'bfc1d7f8cbc7449a96e751b005a02bb112e63a86', class: "metric-compact" }, h("span", { key: '45a7171a72ac6ae9c29a4de3c7a260ae78681dc1', class: "metric-label" }, "RATE"), h("span", { key: '7419ace73d0302d662b3a6308f313be271049b5a', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '786544dff913bb4b1003e62d9ab35b3f29f4b497', class: "watermark" }, h("img", { key: 'e08c893afa8bad94d04b01ea4732044b1c9a2d6c', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1000
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'b773fe579cc039fd90ff5e61976752bcf631d16e', class: "camera-controls" }, h("button", { key: '8535463b13cc02e0030d14c1d57390e560572b4f', 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: 'c4b159cf28c24ba2ec0a658bf6f1296c6aba47e3', class: "camera-selector-dropdown" }, h("div", { key: '033273241b77cedc414dee00b70926c3a659910a', class: "camera-selector-header" }, h("span", { key: '83fb4c7ae2387b85134a3ca0c5a156beaf73e481' }, "Seleccionar C\u00E1mara"), h("button", { key: 'f65b6e994603e616af8d69df198c3d8471125695', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'fd90d4c4db1e910c5d05b9ce19a252967c71ad1f', 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: '49faa944b3c0c052e18f48e9dda2758cd2c0ae6b', class: "device-info" }, h("small", { key: '15e95207aef60a78b2912a6c00a1b61ab760b72d' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'a67a3795837adcd307fa3666c338376cecae16d0', class: "manual-capture-section" }, h("button", { key: '7f21ccd9b622d97bfce80962038de8be7cd6e33d', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: 'a4b5e865f373cc0e7787098eedd2d4df413d310f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '0b02e81963136dfda62a8980bc5a1f5662509fb8', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '5eb16e12a7ce16720ca7bdd11aefd902ba772e24', class: "flip-animation" }, h("div", { key: '4c6f3ed33c5080e9e18d17c4594cf394250c3517', class: "id-card-icon" }), h("div", { key: 'd848d7a98540988a8f491bcf8f6f6ad9c5dccfd4', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '94863e6d0c404188d548db428af918274d882652', class: "success-animation" }, h("div", { key: 'a90b777e7b7eb533fabec2c15f79a4bd18778f0b', class: "check-icon" }), h("div", { key: '2152e5a3756f58f996c520b58cb1b6cca1763e94', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: 'ae18e06bf291429a74d1b6c8a0c4d37d473f7b30', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'b6b03ed285868d7d3350a2a73e59cb1d65a47401', class: "status-spinner" })), h("div", { key: 'dc85b49dd1cfcddf7300fb3f327d2e1de8ab9bff', class: "status-content" }, h("div", { key: '82461ed0c6d70a0c95ae6b7144ff1d8b8371c6fc', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '24aa77ec92ca5a48d1bed4cf98ae591bdbff74f0', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '3b56a0b305ca07407c09a73624a4040cf6625b56', class: "performance-monitor" }, h("div", { key: '52f975da1502f6b08a0b2c828bfd0be8d10e36f8', class: "performance-expanded" }, h("div", { key: 'ea2f444255add0415c7f88cdea2733e1244b17bd', class: "metrics-row" }, h("div", { key: 'e3c2370f1a900594940cb321d5b1661690018025', class: "metric-compact" }, h("span", { key: '25c2f402c9b69f21736c494420e7a396673e9893', class: "metric-label" }, "FPS"), h("span", { key: '505de593f7927544cd874332127e84c72131da72', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '5cb1ff4bc1ac33988404e59dac3d01d5a55893b9', class: "metric-compact" }, h("span", { key: 'ce701538abbfe797b288520ab4c277bf7a1d6a05', class: "metric-label" }, "MEM"), h("span", { key: 'efc383e993ad72c20cbfc630e20b2bf9a617a02d', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'b6b6b832740c44d4f78cddf99ba1d139dc3d4739', class: "metrics-row" }, h("div", { key: '3a8903a05aed8adb1626f93bee6ba258fbea9a7d', class: "metric-compact" }, h("span", { key: 'aabc56057156217b3be771ab3b1e92590fa886c5', class: "metric-label" }, "INF"), h("span", { key: '1080df34860cc891cda94c5aacc754cbde18a138', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'ac152093d4c0cc25862aac638fc4693dd0c51a69', class: "metric-compact" }, h("span", { key: 'b23712dbc0b9b8e9cb6e2c659e5af3844ec0ebe7', class: "metric-label" }, "FRAME"), h("span", { key: '461f88519b339fbad6444f009312f72efbc6e450', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '8f99afe2c94f37fd3a6feb082cc9aa96f54fc5e1', class: "metrics-row" }, h("div", { key: 'da7ac0fd842f38bbdcac01550764f9fe1bd666a2', class: "metric-compact" }, h("span", { key: 'a5dcd9c8753ba42038f869efa50ae9602cdf6256', class: "metric-label" }, "DET"), h("span", { key: 'e98b91ea98a41637baea1fcfda0bb590cf5d24a0', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '3ac54161a712d305a098eae0a3b5d21a731c6019', class: "metric-compact" }, h("span", { key: '3c8cc465433be3b490887457a6c3967dbbda6a7d', class: "metric-label" }, "RATE"), h("span", { key: 'f2e083118fe107c7dc2a675e3f661466cea6d479', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '10b6c32b0460e194d680c63b30a078c51a26e9c9', class: "watermark" }, h("img", { key: 'f5954a0ba3f90a642e0d5c6c14d5b549cd193018', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
900
1001
  }
901
1002
  // Utility methods
902
1003
  updateDetectionBoxes(boxes) {
@@ -1012,9 +1113,24 @@ export class JaakStamps {
1012
1113
  }
1013
1114
  }
1014
1115
  async takeManualScreenshot() {
1015
- if (!this.videoRef)
1116
+ console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
1117
+ // Set processing state immediately
1118
+ const captureState = this.stateManager?.getCaptureState();
1119
+ if (captureState?.step === 'front') {
1120
+ this.processingButton = 'capture-front';
1121
+ }
1122
+ else if (captureState?.step === 'back') {
1123
+ this.processingButton = 'capture-back';
1124
+ }
1125
+ console.log('🔵 processingButton set to:', this.processingButton);
1126
+ // Add small delay to show spinner
1127
+ await new Promise(resolve => setTimeout(resolve, 100));
1128
+ if (!this.videoRef) {
1129
+ this.processingButton = null;
1016
1130
  return;
1131
+ }
1017
1132
  // When using manual capture, use mask coordinates for cropping
1133
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
1018
1134
  await this.takeScreenshotWithMaskCoordinates();
1019
1135
  }
1020
1136
  async takeScreenshotWithMaskCoordinates() {
@@ -1090,6 +1206,8 @@ export class JaakStamps {
1090
1206
  this.completeProcess(true);
1091
1207
  this.returnCanvasToPool(captureCanvas);
1092
1208
  this.returnCanvasToPool(croppedCanvas);
1209
+ // Clear processing button when passport is detected
1210
+ this.processingButton = null;
1093
1211
  return;
1094
1212
  }
1095
1213
  }
@@ -1102,12 +1220,16 @@ export class JaakStamps {
1102
1220
  isDetectionPaused: true,
1103
1221
  showFlipAnimation: true
1104
1222
  });
1223
+ this.triggerRerender();
1105
1224
  setTimeout(() => {
1106
1225
  this.stateManager.updateCaptureState({
1107
1226
  showFlipAnimation: false,
1108
1227
  isDetectionPaused: false
1109
1228
  });
1229
+ this.triggerRerender();
1110
1230
  this.startBackDocumentTimer();
1231
+ // Clear processing button after animation completes
1232
+ this.processingButton = null;
1111
1233
  }, 3000);
1112
1234
  }
1113
1235
  else if (captureState.step === 'back') {
@@ -1118,6 +1240,8 @@ export class JaakStamps {
1118
1240
  }
1119
1241
  });
1120
1242
  this.completeProcess(false);
1243
+ // Clear processing button after back capture completes
1244
+ this.processingButton = null;
1121
1245
  }
1122
1246
  // Return canvases to pool after use
1123
1247
  this.returnCanvasToPool(captureCanvas);
@@ -1211,6 +1335,9 @@ export class JaakStamps {
1211
1335
  this.stopPerformanceMonitoring();
1212
1336
  // Liberar recursos de ONNX
1213
1337
  this.releaseOnnxResources();
1338
+ // Resetear máscara a blanco y limpiar detecciones
1339
+ this.resetMaskToWhite();
1340
+ this.detectionBoxes = [];
1214
1341
  this.hasAutoSwitchedToManual = true;
1215
1342
  this.performanceDegradedMode = true;
1216
1343
  this.showManualCaptureButton = true;
@@ -1221,6 +1348,75 @@ export class JaakStamps {
1221
1348
  }, 5000);
1222
1349
  }
1223
1350
  // Método para cambiar a modo manual por errores de ONNX
1351
+ switchToManualModeWithWarning(warningMessage) {
1352
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1353
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
1354
+ return;
1355
+ }
1356
+ // No necesitamos liberar recursos ONNX porque no se han cargado aún
1357
+ // Resetear máscara a blanco y limpiar detecciones
1358
+ this.resetMaskToWhite();
1359
+ this.detectionBoxes = [];
1360
+ // Cambiar a modo manual
1361
+ this.hasAutoSwitchedToManual = true;
1362
+ this.performanceDegradedMode = true;
1363
+ this.showManualCaptureButton = true;
1364
+ this.useDocumentDetector = false; // Desactivar detector automático
1365
+ // Actualizar el estado con mensaje informativo (no error)
1366
+ this.updateStatus('Modo manual activado', warningMessage, 'active');
1367
+ // Continuar con la configuración de cámara
1368
+ this.continueWithCameraSetup();
1369
+ }
1370
+ async continueWithCameraSetup() {
1371
+ try {
1372
+ // Paso 2: Detectar y configurar dispositivos
1373
+ this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1374
+ try {
1375
+ await this.cameraService.enumerateDevices();
1376
+ }
1377
+ catch (enumerateError) {
1378
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
1379
+ }
1380
+ try {
1381
+ await this.updateCameraInfoWithAutofocus();
1382
+ }
1383
+ catch (cameraInfoError) {
1384
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
1385
+ }
1386
+ // Paso 3: Configurar cámara seleccionada
1387
+ this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1388
+ let stream;
1389
+ try {
1390
+ stream = await this.cameraService.setupCamera();
1391
+ }
1392
+ catch (setupError) {
1393
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
1394
+ }
1395
+ // Paso 4: Inicializar video
1396
+ this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
1397
+ try {
1398
+ await this.initializeVideoStream(stream);
1399
+ }
1400
+ catch (videoError) {
1401
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
1402
+ }
1403
+ // Paso 5: Calibrar máscara
1404
+ if (this.detectionContainer) {
1405
+ const container = this.detectionContainer.parentElement;
1406
+ const rect = container.getBoundingClientRect();
1407
+ this.updateMaskDimensions(rect);
1408
+ }
1409
+ // Finalizar
1410
+ this.startTime = Date.now();
1411
+ this.stateManager.updateCaptureState({ isLoading: false });
1412
+ // Mostrar botón manual (ya debe estar habilitado)
1413
+ this.showManualCaptureButton = true;
1414
+ }
1415
+ catch (error) {
1416
+ this.updateStatus('Error de cámara', error.message, 'error');
1417
+ throw error;
1418
+ }
1419
+ }
1224
1420
  switchToManualModeWithError(errorMessage) {
1225
1421
  // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1226
1422
  if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
@@ -1228,6 +1424,9 @@ export class JaakStamps {
1228
1424
  }
1229
1425
  // Liberar recursos de ONNX inmediatamente
1230
1426
  this.releaseOnnxResources();
1427
+ // Resetear máscara a blanco y limpiar detecciones
1428
+ this.resetMaskToWhite();
1429
+ this.detectionBoxes = [];
1231
1430
  // Cambiar a modo manual
1232
1431
  this.hasAutoSwitchedToManual = true;
1233
1432
  this.performanceDegradedMode = true;
@@ -1478,6 +1677,24 @@ export class JaakStamps {
1478
1677
  // Performance is good, use base frame skip
1479
1678
  return this.BASE_FRAME_SKIP;
1480
1679
  }
1680
+ // Método para resetear la máscara a color blanco
1681
+ resetMaskToWhite() {
1682
+ const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
1683
+ const corners = this.el.shadowRoot?.querySelectorAll('.corner');
1684
+ const topSide = this.el.shadowRoot?.querySelector('.side-top');
1685
+ const rightSide = this.el.shadowRoot?.querySelector('.side-right');
1686
+ const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
1687
+ const leftSide = this.el.shadowRoot?.querySelector('.side-left');
1688
+ // Remover todas las clases de alineación y estado perfecto
1689
+ cardOutline?.classList.remove('perfect-match');
1690
+ corners?.forEach(corner => corner.classList.remove('perfect-match'));
1691
+ topSide?.classList.remove('aligned');
1692
+ rightSide?.classList.remove('aligned');
1693
+ bottomSide?.classList.remove('aligned');
1694
+ leftSide?.classList.remove('aligned');
1695
+ // Resetear estado de alineación
1696
+ this.sideAlignment = { top: false, right: false, bottom: false, left: false };
1697
+ }
1481
1698
  // Canvas pool management for screenshots
1482
1699
  getPooledCanvas(width, height) {
1483
1700
  // Try to find a canvas with matching dimensions
@@ -1739,7 +1956,9 @@ export class JaakStamps {
1739
1956
  "backDocumentTimerRemaining": {},
1740
1957
  "showManualCaptureButton": {},
1741
1958
  "performanceDegradedMode": {},
1742
- "showPerformanceMessage": {}
1959
+ "showPerformanceMessage": {},
1960
+ "captureStateVersion": {},
1961
+ "processingButton": {}
1743
1962
  };
1744
1963
  }
1745
1964
  static get events() {