@jaak.ai/stamps 2.1.0-dev.6 → 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],"captureStateVersion":[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
  }
@@ -52,6 +52,7 @@ export class JaakStamps {
52
52
  performanceDegradedMode = false; // Auto-switched to manual mode due to performance
53
53
  showPerformanceMessage = false; // Show performance message temporarily
54
54
  captureStateVersion = 0; // Triggers re-render when StateManager changes
55
+ processingButton = null; // Track which button is processing
55
56
  // Services
56
57
  serviceContainer;
57
58
  eventBus;
@@ -103,14 +104,19 @@ export class JaakStamps {
103
104
  canvasPool = [];
104
105
  MAX_CANVAS_POOL_SIZE = 3;
105
106
  async componentDidLoad() {
106
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
107
- await this.initializeServices();
108
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
109
- await this.setupEventListeners();
110
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
111
- await this.initializeComponent();
112
- if (this.debug) {
113
- 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');
114
120
  }
115
121
  }
116
122
  async initializeServices() {
@@ -425,8 +431,14 @@ export class JaakStamps {
425
431
  }
426
432
  }
427
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));
428
439
  const readyCheck = this.isComponentReady();
429
440
  if (!readyCheck.ready) {
441
+ this.processingButton = null;
430
442
  return { success: false, error: readyCheck.message };
431
443
  }
432
444
  try {
@@ -443,6 +455,10 @@ export class JaakStamps {
443
455
  catch (error) {
444
456
  return { success: false, error: error.message };
445
457
  }
458
+ finally {
459
+ // Always clear processing state when done
460
+ this.processingButton = null;
461
+ }
446
462
  }
447
463
  async getStatus() {
448
464
  const readyCheck = this.isComponentReady();
@@ -620,9 +636,53 @@ export class JaakStamps {
620
636
  // Always allow getting capture delay, even during initialization
621
637
  return this.captureDelay;
622
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
+ }
623
676
  // DETECTION METHODS
624
677
  async startDetection() {
625
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
+ }
626
686
  // Paso 1: Verificar y cargar modelos si es necesario
627
687
  if (!this.detectionService.isModelLoaded()) {
628
688
  const loadStartTime = performance.now();
@@ -632,16 +692,32 @@ export class JaakStamps {
632
692
  await this.detectionService.loadModel();
633
693
  }
634
694
  catch (modelError) {
635
- this.switchToManualModeWithError('Error al cargar modelo de detección');
636
- 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
+ }
637
705
  }
638
706
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
639
707
  try {
640
708
  await this.detectionService.loadClassificationModel();
641
709
  }
642
710
  catch (classificationError) {
643
- this.switchToManualModeWithError('Error al cargar modelo de clasificación');
644
- 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
+ }
645
721
  }
646
722
  const loadEndTime = performance.now();
647
723
  const loadTime = loadEndTime - loadStartTime;
@@ -652,11 +728,27 @@ export class JaakStamps {
652
728
  }
653
729
  // Paso 2: Detectar y configurar dispositivos
654
730
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
655
- await this.cameraService.enumerateDevices();
656
- 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
+ }
657
743
  // Paso 3: Configurar cámara seleccionada
658
744
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
659
- 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
+ }
660
752
  // Paso 4: Inicializar video y mostrar estado de análisis inicial
661
753
  if (this.useDocumentDetector) {
662
754
  this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
@@ -664,7 +756,12 @@ export class JaakStamps {
664
756
  else {
665
757
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
666
758
  }
667
- 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
+ }
668
765
  // Paso 5: Calibrar máscara
669
766
  if (this.detectionContainer) {
670
767
  const container = this.detectionContainer.parentElement;
@@ -889,7 +986,7 @@ export class JaakStamps {
889
986
  isCapturing: false
890
987
  };
891
988
  const cameraInfo = this.cameraInfoWithAutofocus;
892
- return (h("div", { key: '42958507f3463e8506bdd240b3f478f6e3bf1746', class: "detector-container" }, h("div", { key: '775a9c1df2075236c0b0bfe4a39eace38d099b09', class: "video-container" }, h("video", { key: '035d92f2c167087e95689dd7997ac20b8404ac1e', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'e7fee77cba28b3e4149319ce7998b06af5c4075c', 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: {
893
990
  position: 'absolute',
894
991
  left: `${box.x}px`,
895
992
  top: `${box.y}px`,
@@ -898,9 +995,9 @@ export class JaakStamps {
898
995
  border: '2px solid #32406C',
899
996
  pointerEvents: 'none',
900
997
  boxSizing: 'border-box'
901
- } })))), this.isMaskReady && (h("div", { key: '3d7ab5706479922c4321953a18c0026f4be2ba07', class: "overlay-mask" }, h("div", { key: '410d98aa7ed52604760e12a1d8c8dc5a94a4e0d8', class: "card-outline" }, h("div", { key: 'b51b8b7df3ef5c5d3a9bfa43d704d06aef14b2cf', class: "side side-top" }), h("div", { key: 'e2c8f24094d2a9356248234c8a7a2d8aca07f3e6', class: "side side-right" }), h("div", { key: 'e4f92a79ac4c8af8d1e51e03d743b4814ffd2c00', class: "side side-bottom" }), h("div", { key: '400c294159a44f6211eb84a4e457b1406f541f44', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '69c4ed1c4d5bd21bdaf0795a49cf3dd9a59502f4', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '92164199e91c9cbf40a94a87b0457e76c566e252', class: "back-capture-section" }, h("div", { key: '22ad40233923be0909529927779f09a6917895ee', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: '3c65c3bd5687d709a5f4d1c9fe2293cfd320f269', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), h("button", { key: 'ad6cb7eb1ce70af2fb3d5a8d031801a850605baa', 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
902
999
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
903
- : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: '9b87c7e4f5ab646ec35e5f81833a6c9e9ac5d5c8', class: "camera-controls" }, h("button", { key: 'c9c741f3885b56162de343b18efa8764f0c2c001', 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: 'b4f5c63464085c135dda068c929b147cfd84238f', class: "camera-selector-dropdown" }, h("div", { key: '06072cad9c132f1926b4070c0c3d714b76cc6636', class: "camera-selector-header" }, h("span", { key: '85a813b383f6cf1fd505239781d8a5ea60cbae58' }, "Seleccionar C\u00E1mara"), h("button", { key: 'c1588cea255bcf7151ca2e9d0583d149122784d7', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '62864eaea7aac898d27d8995fcede3788383918f', 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: '1288fb31a8f3290bf5a280390c52af5b1dce75b5', class: "device-info" }, h("small", { key: 'efdf5a337cd07fc14112d3bd883298cb586f67f5' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '6b18401230001a923e139f3f2a704ac44b8f52f4', class: "manual-capture-section" }, h("button", { key: '8269121d4822b8d7cb01b966d3b701cc297b8856', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (h("div", { key: '8920362bb45d0f024149440a914a2144e16783b0', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '644baeaf145477d31439e7f28a5c262064f4db95', class: "flip-animation" }, h("div", { key: '467c703d5ac600f12f7456e2c51bf0d1ec94a638', class: "id-card-icon" }), h("div", { key: '8f2cef3855202d959a6e7884f1d74d78e3ef66ac', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '651898dc58e10886d0b4696f2036af7bf68640ef', class: "success-animation" }, h("div", { key: '7b3e1626aa02131af0f860dc5ea8c8f0e4d6655b', class: "check-icon" }), h("div", { key: '571dcad0d407c275dd4ceaa04d2de4bbed7a9181', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: 'f2efae53af3ca6a38735f7fb06708101a811e1fe', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '6f9268bfcc01b305c2d415b2bbbd3197b228d487', class: "status-spinner" })), h("div", { key: '17f0f0c8c4debb57408d1bed95dd8a6c180affb5', class: "status-content" }, h("div", { key: '6af3696c54d05bea334c24fe7713e658ef1c9d3e', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '99d8fe46dd0c4e3f779c8d3a7fc6314b551f8424', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '4b25e47a42a9f20713167c0f89f8adb07090a6e5', class: "performance-monitor" }, h("div", { key: 'b323c4cb466bfe50d105ea165d361b4240e97cbf', class: "performance-expanded" }, h("div", { key: '001d7d5cc4024162d24a9f46e2bc4b978ad0166b', class: "metrics-row" }, h("div", { key: 'd3f8281b92075b48f4fff22a6cbb020e98055f74', class: "metric-compact" }, h("span", { key: 'c140d8d5596ed3a3f176daf3e7a74bc12ef9fa56', class: "metric-label" }, "FPS"), h("span", { key: 'f88d7a211bc516757ef6842c5a9acb4bb9c3ff9b', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '6f4b8aac4f1f1f086d0038b42ddd12b4bdff15ea', class: "metric-compact" }, h("span", { key: '8a222b05e739012a67f98f73bbba0ba042420b08', class: "metric-label" }, "MEM"), h("span", { key: '1a2f785d57ba926900eca868b890c677ce0adafc', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'e2dc13638b3cffa1245ed6b1485d85982215a23d', class: "metrics-row" }, h("div", { key: '3356b3dc77e2c2adc33f0483b46958495ea963a6', class: "metric-compact" }, h("span", { key: '9d6a01153605f15dd13a46fc6de7e43bcec3c4d7', class: "metric-label" }, "INF"), h("span", { key: '6db223f93179706ab9f9d19807247f69dc2555bf', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '920f5f8a7d98d4c30644584ee4177b7f0b9d4ce6', class: "metric-compact" }, h("span", { key: 'e27e9a6410f9e9ccb789500e23a0a6140d5cc56e', class: "metric-label" }, "FRAME"), h("span", { key: 'e2229a1d7c05c6503a6aaf85951dfbdb93d8922f', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '33bc285a3ece3bc0887249fc158f63a34fbd84fd', class: "metrics-row" }, h("div", { key: '9904884ea06b18561e6996fc996c8522f01138b8', class: "metric-compact" }, h("span", { key: 'df4bc26fe4af5e1203cbc1c82d104ac1c64a0d31', class: "metric-label" }, "DET"), h("span", { key: '760931f01f32761f80bedb579652018cedeb6edb', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '9f1e5b50979e54fdb58ade2df5e1a990b8ddd1a3', class: "metric-compact" }, h("span", { key: '698e460b3b7b13747cdc27743348c580eab337ad', class: "metric-label" }, "RATE"), h("span", { key: '466b3b790ce6d3c680bb7629592b67e57be3cee5', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '2c093210e7b929967b690c60cfc0943274db2409', class: "watermark" }, h("img", { key: '9e30dc6b9cd294e5dc3059ea98c61a45a50c4255', 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" })))));
904
1001
  }
905
1002
  // Utility methods
906
1003
  updateDetectionBoxes(boxes) {
@@ -1016,9 +1113,24 @@ export class JaakStamps {
1016
1113
  }
1017
1114
  }
1018
1115
  async takeManualScreenshot() {
1019
- 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;
1020
1130
  return;
1131
+ }
1021
1132
  // When using manual capture, use mask coordinates for cropping
1133
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
1022
1134
  await this.takeScreenshotWithMaskCoordinates();
1023
1135
  }
1024
1136
  async takeScreenshotWithMaskCoordinates() {
@@ -1094,6 +1206,8 @@ export class JaakStamps {
1094
1206
  this.completeProcess(true);
1095
1207
  this.returnCanvasToPool(captureCanvas);
1096
1208
  this.returnCanvasToPool(croppedCanvas);
1209
+ // Clear processing button when passport is detected
1210
+ this.processingButton = null;
1097
1211
  return;
1098
1212
  }
1099
1213
  }
@@ -1114,6 +1228,8 @@ export class JaakStamps {
1114
1228
  });
1115
1229
  this.triggerRerender();
1116
1230
  this.startBackDocumentTimer();
1231
+ // Clear processing button after animation completes
1232
+ this.processingButton = null;
1117
1233
  }, 3000);
1118
1234
  }
1119
1235
  else if (captureState.step === 'back') {
@@ -1124,6 +1240,8 @@ export class JaakStamps {
1124
1240
  }
1125
1241
  });
1126
1242
  this.completeProcess(false);
1243
+ // Clear processing button after back capture completes
1244
+ this.processingButton = null;
1127
1245
  }
1128
1246
  // Return canvases to pool after use
1129
1247
  this.returnCanvasToPool(captureCanvas);
@@ -1217,6 +1335,9 @@ export class JaakStamps {
1217
1335
  this.stopPerformanceMonitoring();
1218
1336
  // Liberar recursos de ONNX
1219
1337
  this.releaseOnnxResources();
1338
+ // Resetear máscara a blanco y limpiar detecciones
1339
+ this.resetMaskToWhite();
1340
+ this.detectionBoxes = [];
1220
1341
  this.hasAutoSwitchedToManual = true;
1221
1342
  this.performanceDegradedMode = true;
1222
1343
  this.showManualCaptureButton = true;
@@ -1227,6 +1348,75 @@ export class JaakStamps {
1227
1348
  }, 5000);
1228
1349
  }
1229
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
+ }
1230
1420
  switchToManualModeWithError(errorMessage) {
1231
1421
  // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1232
1422
  if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
@@ -1234,6 +1424,9 @@ export class JaakStamps {
1234
1424
  }
1235
1425
  // Liberar recursos de ONNX inmediatamente
1236
1426
  this.releaseOnnxResources();
1427
+ // Resetear máscara a blanco y limpiar detecciones
1428
+ this.resetMaskToWhite();
1429
+ this.detectionBoxes = [];
1237
1430
  // Cambiar a modo manual
1238
1431
  this.hasAutoSwitchedToManual = true;
1239
1432
  this.performanceDegradedMode = true;
@@ -1484,6 +1677,24 @@ export class JaakStamps {
1484
1677
  // Performance is good, use base frame skip
1485
1678
  return this.BASE_FRAME_SKIP;
1486
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
+ }
1487
1698
  // Canvas pool management for screenshots
1488
1699
  getPooledCanvas(width, height) {
1489
1700
  // Try to find a canvas with matching dimensions
@@ -1746,7 +1957,8 @@ export class JaakStamps {
1746
1957
  "showManualCaptureButton": {},
1747
1958
  "performanceDegradedMode": {},
1748
1959
  "showPerformanceMessage": {},
1749
- "captureStateVersion": {}
1960
+ "captureStateVersion": {},
1961
+ "processingButton": {}
1750
1962
  };
1751
1963
  }
1752
1964
  static get events() {