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

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 (53) hide show
  1. package/dist/cjs/app-globals-V2Kpy_OQ.js +8 -0
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
  3. package/dist/cjs/{index-BfhtOB0D.js → index-Ga0t6BMe.js} +176 -119
  4. package/dist/cjs/index-Ga0t6BMe.js.map +1 -0
  5. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +5 -4
  6. package/dist/cjs/jaak-stamps-webcomponent.cjs.js.map +1 -1
  7. package/dist/cjs/jaak-stamps.cjs.entry.js +542 -68
  8. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  9. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  10. package/dist/cjs/loader.cjs.js +4 -3
  11. package/dist/cjs/loader.cjs.js.map +1 -1
  12. package/dist/collection/collection-manifest.json +1 -1
  13. package/dist/collection/components/my-component/my-component.css +56 -12
  14. package/dist/collection/components/my-component/my-component.js +429 -67
  15. package/dist/collection/components/my-component/my-component.js.map +1 -1
  16. package/dist/collection/services/CameraService.js +84 -2
  17. package/dist/collection/services/CameraService.js.map +1 -1
  18. package/dist/collection/services/DetectionService.js +32 -4
  19. package/dist/collection/services/DetectionService.js.map +1 -1
  20. package/dist/components/index.js +165 -113
  21. package/dist/components/index.js.map +1 -1
  22. package/dist/components/jaak-stamps.js +546 -68
  23. package/dist/components/jaak-stamps.js.map +1 -1
  24. package/dist/esm/app-globals-DQuL1Twl.js +6 -0
  25. package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
  26. package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
  27. package/dist/esm/index-Drbzcuq-.js.map +1 -0
  28. package/dist/esm/jaak-stamps-webcomponent.js +5 -4
  29. package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
  30. package/dist/esm/jaak-stamps.entry.js +542 -68
  31. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  32. package/dist/esm/loader.js +4 -3
  33. package/dist/esm/loader.js.map +1 -1
  34. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  35. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
  36. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  37. package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
  38. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
  39. package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
  40. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
  41. package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
  42. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js +2 -0
  43. package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js.map +1 -0
  44. package/dist/types/components/my-component/my-component.d.ts +19 -0
  45. package/dist/types/services/CameraService.d.ts +1 -0
  46. package/dist/types/stencil-public-runtime.d.ts +12 -5
  47. package/package.json +3 -1
  48. package/dist/cjs/index-BfhtOB0D.js.map +0 -1
  49. package/dist/esm/index-BP1Q4KOg.js.map +0 -1
  50. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +0 -2
  51. package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +0 -1
  52. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
  53. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +0 -1
@@ -49,6 +49,10 @@ export class JaakStamps {
49
49
  };
50
50
  backDocumentTimerRemaining = 0;
51
51
  showManualCaptureButton = false;
52
+ performanceDegradedMode = false; // Auto-switched to manual mode due to performance
53
+ showPerformanceMessage = false; // Show performance message temporarily
54
+ captureStateVersion = 0; // Triggers re-render when StateManager changes
55
+ processingButton = null; // Track which button is processing
52
56
  // Services
53
57
  serviceContainer;
54
58
  eventBus;
@@ -67,12 +71,12 @@ export class JaakStamps {
67
71
  alignmentStartTime;
68
72
  alignmentTimer;
69
73
  backDocumentTimer;
74
+ orientationTimer;
70
75
  // Performance monitoring
71
76
  performanceMetrics = {
72
77
  fps: 0,
73
78
  inferenceTime: 0,
74
79
  memoryUsage: 0,
75
- cpuUsage: 0,
76
80
  onnxLoadTime: 0,
77
81
  frameProcessingTime: 0,
78
82
  totalDetections: 0,
@@ -91,18 +95,29 @@ export class JaakStamps {
91
95
  MIN_INFERENCE_INTERVAL = 50;
92
96
  performanceHistory = [];
93
97
  PERFORMANCE_HISTORY_SIZE = 10;
98
+ // Sistema simplificado de monitoreo de rendimiento
99
+ PERFORMANCE_THRESHOLD_MS = 2000; // Umbral único para todos los frames
100
+ SLOW_FRAMES_TO_TRIGGER = 2; // Cuántos frames lentos consecutivos antes de cambiar
101
+ slowFrameCount = 0; // Contador de frames consecutivos lentos
102
+ processedFramesCount = 0; // Contador total de frames procesados
103
+ hasAutoSwitchedToManual = false;
94
104
  // Canvas pool for optimized screenshot capture
95
105
  canvasPool = [];
96
106
  MAX_CANVAS_POOL_SIZE = 3;
97
107
  async componentDidLoad() {
98
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
99
- await this.initializeServices();
100
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
101
- await this.setupEventListeners();
102
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
103
- await this.initializeComponent();
104
- if (this.debug) {
105
- this.initializePerformanceMonitor();
108
+ try {
109
+ this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
110
+ await this.initializeServices();
111
+ this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
112
+ await this.setupEventListeners();
113
+ this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
114
+ await this.initializeComponent();
115
+ if (this.debug) {
116
+ this.initializePerformanceMonitor();
117
+ }
118
+ }
119
+ catch (error) {
120
+ this.updateStatus('Error de inicialización', error.message, 'error');
106
121
  }
107
122
  }
108
123
  async initializeServices() {
@@ -123,14 +138,8 @@ export class JaakStamps {
123
138
  this.detectionService = this.serviceContainer.getDetectionService();
124
139
  }
125
140
  async setupEventListeners() {
126
- this.eventBus.on('state-changed', (data) => {
127
- this.handleStateChange(data);
128
- });
129
- this.eventBus.on('camera-changed', () => {
130
- // Camera changed event
131
- });
132
- this.eventBus.on('error', () => {
133
- // Handle service errors
141
+ this.eventBus.on('state-changed', () => {
142
+ this.handleStateChange();
134
143
  });
135
144
  }
136
145
  async initializeComponent() {
@@ -166,7 +175,7 @@ export class JaakStamps {
166
175
  if (!window.ort) {
167
176
  this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
168
177
  const script = document.createElement('script');
169
- script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
178
+ script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
170
179
  document.head.appendChild(script);
171
180
  await new Promise((resolve) => {
172
181
  script.onload = () => {
@@ -237,13 +246,8 @@ export class JaakStamps {
237
246
  return false;
238
247
  }
239
248
  }
240
- handleStateChange(data) {
241
- // React to state changes from StateManager
242
- // This is where the component updates its internal state based on service state changes
243
- if (data.changes.isLoading !== undefined) {
244
- // Force re-render when loading state changes
245
- // Note: Stencil automatically re-renders when @State changes
246
- }
249
+ handleStateChange() {
250
+ // Handle state changes from StateManager if needed
247
251
  }
248
252
  initializeResizeObserver() {
249
253
  if ('ResizeObserver' in window && this.detectionContainer) {
@@ -252,6 +256,18 @@ export class JaakStamps {
252
256
  });
253
257
  resizeObserver.observe(this.detectionContainer.parentElement);
254
258
  }
259
+ // Handle mobile device orientation changes
260
+ window.addEventListener('orientationchange', () => {
261
+ // Clear any existing orientation timer
262
+ if (this.orientationTimer) {
263
+ clearTimeout(this.orientationTimer);
264
+ }
265
+ // Add delay to ensure video dimensions are updated after rotation
266
+ this.orientationTimer = window.setTimeout(() => {
267
+ this.handleResize();
268
+ this.orientationTimer = undefined;
269
+ }, 300);
270
+ });
255
271
  }
256
272
  handleResize() {
257
273
  if (this.detectionContainer) {
@@ -260,10 +276,17 @@ export class JaakStamps {
260
276
  this.updateMaskDimensions(rect);
261
277
  }
262
278
  }
279
+ triggerRerender() {
280
+ this.captureStateVersion = this.captureStateVersion + 1;
281
+ }
263
282
  getGuideText(step) {
264
283
  if (step === 'completed') {
265
284
  return 'Proceso completado';
266
285
  }
286
+ // Check if we should show performance message
287
+ if (this.showPerformanceMessage && step === 'front') {
288
+ return 'Modo manual activado por rendimiento. Use el botón para capturar.';
289
+ }
267
290
  if (step === 'front') {
268
291
  // Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
269
292
  if (this.showManualCaptureButton) {
@@ -421,8 +444,14 @@ export class JaakStamps {
421
444
  }
422
445
  }
423
446
  async skipBackCapture() {
447
+ console.log('🟡 Botón clicked: Saltar reverso');
448
+ // Set processing state immediately
449
+ this.processingButton = 'skip-back';
450
+ // Add small delay to show spinner
451
+ await new Promise(resolve => setTimeout(resolve, 100));
424
452
  const readyCheck = this.isComponentReady();
425
453
  if (!readyCheck.ready) {
454
+ this.processingButton = null;
426
455
  return { success: false, error: readyCheck.message };
427
456
  }
428
457
  try {
@@ -439,6 +468,10 @@ export class JaakStamps {
439
468
  catch (error) {
440
469
  return { success: false, error: error.message };
441
470
  }
471
+ finally {
472
+ // Always clear processing state when done
473
+ this.processingButton = null;
474
+ }
442
475
  }
443
476
  async getStatus() {
444
477
  const readyCheck = this.isComponentReady();
@@ -493,9 +526,21 @@ export class JaakStamps {
493
526
  if (this.stateManager) {
494
527
  this.stateManager.updateCaptureState({ isLoading: true });
495
528
  }
496
- await this.detectionService.loadModel();
529
+ try {
530
+ await this.detectionService.loadModel();
531
+ }
532
+ catch (modelError) {
533
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
534
+ throw modelError;
535
+ }
497
536
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
498
- await this.detectionService.loadClassificationModel();
537
+ try {
538
+ await this.detectionService.loadClassificationModel();
539
+ }
540
+ catch (classificationError) {
541
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
542
+ throw classificationError;
543
+ }
499
544
  const loadEndTime = performance.now();
500
545
  const loadTime = loadEndTime - loadStartTime;
501
546
  // Record ONNX load time
@@ -604,17 +649,63 @@ export class JaakStamps {
604
649
  // Always allow getting capture delay, even during initialization
605
650
  return this.captureDelay;
606
651
  }
652
+ // Memory and device capability detection
653
+ checkDeviceCapabilities() {
654
+ const deviceMemory = navigator.deviceMemory;
655
+ // Siempre intentar usar ML, independientemente del dispositivo
656
+ // Las restricciones anteriores no eran confiables
657
+ return {
658
+ canUseML: true,
659
+ reason: 'Dispositivo compatible con detección automática.',
660
+ memoryMB: deviceMemory ? deviceMemory * 1024 : null
661
+ };
662
+ }
607
663
  // DETECTION METHODS
608
664
  async startDetection() {
609
665
  try {
666
+ // Verificar capacidades del dispositivo antes de cargar modelos
667
+ const capabilities = this.checkDeviceCapabilities();
668
+ // Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
669
+ if (!capabilities.canUseML && this.useDocumentDetector) {
670
+ this.switchToManualModeWithWarning(capabilities.reason);
671
+ return;
672
+ }
610
673
  // Paso 1: Verificar y cargar modelos si es necesario
611
674
  if (!this.detectionService.isModelLoaded()) {
612
675
  const loadStartTime = performance.now();
613
676
  this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
614
677
  this.stateManager.updateCaptureState({ isLoading: true });
615
- await this.detectionService.loadModel();
678
+ try {
679
+ await this.detectionService.loadModel();
680
+ }
681
+ catch (modelError) {
682
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
683
+ if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
684
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
685
+ this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
686
+ return;
687
+ }
688
+ else {
689
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
690
+ throw modelError;
691
+ }
692
+ }
616
693
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
617
- await this.detectionService.loadClassificationModel();
694
+ try {
695
+ await this.detectionService.loadClassificationModel();
696
+ }
697
+ catch (classificationError) {
698
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
699
+ if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
700
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
701
+ this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
702
+ return;
703
+ }
704
+ else {
705
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
706
+ throw classificationError;
707
+ }
708
+ }
618
709
  const loadEndTime = performance.now();
619
710
  const loadTime = loadEndTime - loadStartTime;
620
711
  // Record ONNX load time
@@ -624,19 +715,40 @@ export class JaakStamps {
624
715
  }
625
716
  // Paso 2: Detectar y configurar dispositivos
626
717
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
627
- await this.cameraService.enumerateDevices();
628
- await this.updateCameraInfoWithAutofocus();
718
+ try {
719
+ await this.cameraService.enumerateDevices();
720
+ }
721
+ catch (enumerateError) {
722
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
723
+ }
724
+ try {
725
+ await this.updateCameraInfoWithAutofocus();
726
+ }
727
+ catch (cameraInfoError) {
728
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
729
+ }
629
730
  // Paso 3: Configurar cámara seleccionada
630
731
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
631
- const stream = await this.cameraService.setupCamera();
632
- // Paso 4: Inicializar video y ocultar estado inmediatamente
732
+ let stream;
733
+ try {
734
+ stream = await this.cameraService.setupCamera();
735
+ }
736
+ catch (setupError) {
737
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
738
+ }
739
+ // Paso 4: Inicializar video y mostrar estado de análisis inicial
633
740
  if (this.useDocumentDetector) {
634
- this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
741
+ this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
635
742
  }
636
743
  else {
637
744
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
638
745
  }
639
- await this.initializeVideoStream(stream);
746
+ try {
747
+ await this.initializeVideoStream(stream);
748
+ }
749
+ catch (videoError) {
750
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
751
+ }
640
752
  // Paso 5: Calibrar máscara
641
753
  if (this.detectionContainer) {
642
754
  const container = this.detectionContainer.parentElement;
@@ -667,6 +779,12 @@ export class JaakStamps {
667
779
  this.videoRef.onloadedmetadata = async () => {
668
780
  await this.videoRef.play();
669
781
  this.stateManager.updateCaptureState({ isVideoActive: true });
782
+ // Recalculate mask dimensions when new camera stream loads
783
+ if (this.detectionContainer) {
784
+ const container = this.detectionContainer.parentElement;
785
+ const rect = container.getBoundingClientRect();
786
+ this.updateMaskDimensions(rect);
787
+ }
670
788
  resolve();
671
789
  };
672
790
  });
@@ -681,8 +799,8 @@ export class JaakStamps {
681
799
  const captureState = this.stateManager.getCaptureState();
682
800
  if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
683
801
  return;
684
- // Show manual capture button when document detector is disabled
685
- if (!this.useDocumentDetector) {
802
+ // Show manual capture button when document detector is disabled or performance is degraded
803
+ if (!this.useDocumentDetector || this.performanceDegradedMode) {
686
804
  this.showManualCaptureButton = true;
687
805
  // Continue the loop for UI updates but skip detection logic
688
806
  if (captureState.step !== 'completed') {
@@ -690,7 +808,7 @@ export class JaakStamps {
690
808
  }
691
809
  return;
692
810
  }
693
- else {
811
+ else if (!this.performanceDegradedMode) {
694
812
  this.showManualCaptureButton = false;
695
813
  }
696
814
  if (captureState.isDetectionPaused) {
@@ -720,10 +838,27 @@ export class JaakStamps {
720
838
  this.lastInferenceTime = currentTime;
721
839
  // Measure preprocessing and inference time
722
840
  const inferenceStartTime = performance.now();
723
- const inputTensor = this.detectionService.preprocess(this.videoRef);
724
- // Standard detection without quality validation
725
- const detections = await this.detectionService.runInference(inputTensor);
726
- const inferenceTime = performance.now() - inferenceStartTime;
841
+ let inputTensor;
842
+ let detections;
843
+ let inferenceTime;
844
+ try {
845
+ inputTensor = this.detectionService.preprocess(this.videoRef);
846
+ }
847
+ catch (preprocessError) {
848
+ this.switchToManualModeWithError('Error al procesar imagen');
849
+ return;
850
+ }
851
+ try {
852
+ // Standard detection without timeout (timeout is handled by frame counting)
853
+ detections = await this.detectionService.runInference(inputTensor);
854
+ inferenceTime = performance.now() - inferenceStartTime;
855
+ }
856
+ catch (inferenceError) {
857
+ this.switchToManualModeWithError('Error en detección automática');
858
+ return;
859
+ }
860
+ // Increment frame count
861
+ this.processedFramesCount++;
727
862
  // Record ONNX performance metrics
728
863
  if (this.debug) {
729
864
  this.recordOnnxPerformance(0, inferenceTime);
@@ -762,9 +897,30 @@ export class JaakStamps {
762
897
  }
763
898
  this.updateMaskColor(detections);
764
899
  // Record frame processing metrics
900
+ const frameEndTime = performance.now();
901
+ const totalFrameTime = frameEndTime - frameStartTime;
902
+ // Sistema simplificado: Verificar rendimiento de cada frame (solo si no se ha cambiado a manual)
903
+ if (this.useDocumentDetector && !this.hasAutoSwitchedToManual) {
904
+ // Verificar si el frame es lento
905
+ if (totalFrameTime >= this.PERFORMANCE_THRESHOLD_MS) {
906
+ this.slowFrameCount++;
907
+ // Si tenemos suficientes frames lentos consecutivos, cambiar a manual
908
+ if (this.slowFrameCount >= this.SLOW_FRAMES_TO_TRIGGER) {
909
+ this.switchToManualMode();
910
+ }
911
+ }
912
+ else {
913
+ // Frame rápido, reiniciar contador
914
+ if (this.slowFrameCount > 0) {
915
+ this.slowFrameCount = 0;
916
+ }
917
+ }
918
+ // Cambiar mensaje de estado después de analizar los primeros 2 frames
919
+ if (this.processedFramesCount === 2) {
920
+ this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
921
+ }
922
+ }
765
923
  if (this.debug) {
766
- const frameEndTime = performance.now();
767
- const totalFrameTime = frameEndTime - frameStartTime;
768
924
  this.recordFrameProcessing(totalFrameTime, detections.length);
769
925
  }
770
926
  // Continue detection loop
@@ -800,6 +956,12 @@ export class JaakStamps {
800
956
  clearTimeout(this.alignmentTimer);
801
957
  this.alignmentTimer = undefined;
802
958
  }
959
+ if (this.orientationTimer) {
960
+ clearTimeout(this.orientationTimer);
961
+ this.orientationTimer = undefined;
962
+ }
963
+ // Liberar recursos de ONNX al limpiar el componente
964
+ this.releaseOnnxResources();
803
965
  if (this.performanceUpdateInterval) {
804
966
  clearInterval(this.performanceUpdateInterval);
805
967
  this.performanceUpdateInterval = undefined;
@@ -821,7 +983,7 @@ export class JaakStamps {
821
983
  isCapturing: false
822
984
  };
823
985
  const cameraInfo = this.cameraInfoWithAutofocus;
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: {
986
+ return (h("div", { key: 'f9cbca2fd6b8548a29ace7fcb972b53f402f61af', class: "detector-container" }, h("div", { key: '4f3cac734eb6bfd44a8de644eadca17e7893e11a', class: "video-container" }, h("video", { key: '5e15a38c989a72d5274281272d41ba064447ca6f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '38c8ad8b691db60e9b5a2a8b641fe6febbeacfca', 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: {
825
987
  position: 'absolute',
826
988
  left: `${box.x}px`,
827
989
  top: `${box.y}px`,
@@ -830,9 +992,9 @@ export class JaakStamps {
830
992
  border: '2px solid #32406C',
831
993
  pointerEvents: 'none',
832
994
  boxSizing: 'border-box'
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
995
+ } })))), this.isMaskReady && (h("div", { key: 'c56fde56b7a86a4e8a4cf558745291ea5cde12fd', class: "overlay-mask" }, h("div", { key: '04ed3f01f271dc84eed594cb4b60c2453645bd41', class: "card-outline" }, h("div", { key: '648bd423a5e886d4768115237de5896c7223b30b', class: "side side-top" }), h("div", { key: 'b8878059493ca61961b9b3c82ecc18b50b79ac85', class: "side side-right" }), h("div", { key: '09e5ad64ff33dbd5c112bf52b71e14409ab6aa65', class: "side side-bottom" }), h("div", { key: 'b8ba3d83c9bc1b4a0da859d7ed4b9a15eaddcc2a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '3279701a240f77ac96e68a34ad95c099340c9a6a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '2adf96d0a57e482372fd9586229dac2301b64fd4', class: "back-capture-section" }, h("div", { key: '305a147c9cfee854c60ac946a48a1d21b2792725', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (h("button", { key: '43270a0647f39307229c540dd5130fb58b1ff4fb', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: 'eb4666603774564eef95a120ac13faeb9e10c8b4', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'cac667d9548ae2314ad5a677c5ed90f25184f728', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '0af7aa4106ce189ae2d79bec78ede01abe25b1e7', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
834
996
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
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" })))));
997
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'de6e5c4fad4d4d996b894457c9af52bc71e533fb', class: "camera-controls" }, h("button", { key: 'cfddcf2420d5f5923a6b02ca446bbe1b8e9fc5e4', 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: '5fb73f958b9a5a6f9242032c4530b0bdab84f6c5', class: "camera-selector-dropdown" }, h("div", { key: '8ee2d26b1001e571e7b6c326e927926fa08287fd', class: "camera-selector-header" }, h("span", { key: '78350b0fb1bd933f7c8c59d8dab0b7122ffbe363' }, "Seleccionar C\u00E1mara"), h("button", { key: 'bb750e0ddd5a1ee5ff96bd57805a7f90b8821e9f', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd0f6343cf1b7367f3da33ec8cf64891109d923b8', 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: 'c9642fbfb0acea766d3f0542f4cc9f17f4af4d7b', class: "device-info" }, h("small", { key: 'b6e9507ff7addf011f339f6c22a88a96b40db68a' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '04ae9262409284782850b90945eb7038673e2097', class: "manual-capture-section" }, h("button", { key: '27c315e503ccba468b89ebd58d6b70f40ddd06ba', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '8f2dcd2df9a218b37eb0b8204a8724362cfeaa53', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '6fd6fe1d0213b01c8119a67fb8108f73227feee1', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'fba3816b48b99ca64510ecbefc0d41431569c9ae', class: "flip-animation" }, h("div", { key: 'beea1c8245b5d81f1008efc6d1b079a61c821e7f', class: "id-card-icon" }), h("div", { key: 'f0fbc34f619a653e2c0ade764ca99e7aa582d157', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'e4a0e6d31112e3a50024d1d0430b4ad997049fe7', class: "success-animation" }, h("div", { key: '5dd3f781f55630bc904c81207548ae35bc79b452', class: "check-icon" }), h("div", { key: 'a10ebc557582325091efb0abd12d62b27a43b98c', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '975865955dee1803b5215ecf049701c98b324855', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '84f6f67202e26ce80f7fc5d5f321e23b5bc55c57', class: "status-spinner" })), h("div", { key: 'a1ae56ed156e8feb279a1bf160f749c5d0b5ce45', class: "status-content" }, h("div", { key: 'd62932554cea257ce19763df26e0ba702e50eeca', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'c5506fa12dd0d37d26dc26c9f2381c843330451f', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '2097eda63185f336f7319014fae2ef6db1e41761', class: "performance-monitor" }, h("div", { key: 'd611c494c6b1a5930ef64aebdcba41e5d94c9ee6', class: "performance-expanded" }, h("div", { key: '800d46831e7b9bec260d9c5e67ea1a3eb9c2933e', class: "metrics-row" }, h("div", { key: 'dd0db1faa9050d98e71da4b5cd26a4a484c37c64', class: "metric-compact" }, h("span", { key: 'f0e6ea61a35ddb2ae5c2a6aff8c255a02c5c1219', class: "metric-label" }, "FPS"), h("span", { key: 'cbf4df4d142f6f01e45f566a8ede8fbbf29ad673', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '83861ad31fa46c8199a5583e4b3f8eefd44bacd6', class: "metric-compact" }, h("span", { key: '41b8de4cda47959dfc9df393e95fdadde534dd21', class: "metric-label" }, "MEM"), h("span", { key: 'b268f13cc78066e240ed158f1443113a94827aff', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '9f9a9d0690906b186492af23b900c5fa0f5cff89', class: "metrics-row" }, h("div", { key: '84fa6e453dc36e4a74e8b573447b17230766229e', class: "metric-compact" }, h("span", { key: 'f17d52899090c89f532d74c552dd8c5338fc2d83', class: "metric-label" }, "INF"), h("span", { key: '2beca0d7ce55ed574f6b01efc086c5846983e024', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '981e300cf076e81bfe946974f6cffd63619eacde', class: "metric-compact" }, h("span", { key: '3dfb0c996a69eb87b0f711322675cad69c471146', class: "metric-label" }, "FRAME"), h("span", { key: '3ae2ac6e90cda77f23e559f3197c3b761f9bdc40', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '2c51216838d87728d9d7764e5ba8a50fa02eb493', class: "metrics-row" }, h("div", { key: 'eda810ffb80ec38ab84583f59b26706f90a50fc9', class: "metric-compact" }, h("span", { key: '978ba9c0c67d6da6b29b3535af33a314bab9276a', class: "metric-label" }, "DET"), h("span", { key: '3e5e169fd4a3906283d8f918b5bbc7c5df6ccd0f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'b4b915c745e35c09470a4dc5edfc112d32cfb8b6', class: "metric-compact" }, h("span", { key: '3e2a7b88a4446813c955efb0dfa9e2958e8e1448', class: "metric-label" }, "RATE"), h("span", { key: 'f4f073f7ee622167a241c6a1c535ef580c13d9ab', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '76c556e92f6d7980539f1a30efe8751f665164fb', class: "watermark" }, h("img", { key: 'af23e115d6b4e92a72e55d6d98fde0e4b1d09e9b', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
836
998
  }
837
999
  // Utility methods
838
1000
  updateDetectionBoxes(boxes) {
@@ -948,9 +1110,24 @@ export class JaakStamps {
948
1110
  }
949
1111
  }
950
1112
  async takeManualScreenshot() {
951
- if (!this.videoRef)
1113
+ console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
1114
+ // Set processing state immediately
1115
+ const captureState = this.stateManager?.getCaptureState();
1116
+ if (captureState?.step === 'front') {
1117
+ this.processingButton = 'capture-front';
1118
+ }
1119
+ else if (captureState?.step === 'back') {
1120
+ this.processingButton = 'capture-back';
1121
+ }
1122
+ console.log('🔵 processingButton set to:', this.processingButton);
1123
+ // Add small delay to show spinner
1124
+ await new Promise(resolve => setTimeout(resolve, 100));
1125
+ if (!this.videoRef) {
1126
+ this.processingButton = null;
952
1127
  return;
1128
+ }
953
1129
  // When using manual capture, use mask coordinates for cropping
1130
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
954
1131
  await this.takeScreenshotWithMaskCoordinates();
955
1132
  }
956
1133
  async takeScreenshotWithMaskCoordinates() {
@@ -1014,18 +1191,25 @@ export class JaakStamps {
1014
1191
  if (captureState.step === 'front') {
1015
1192
  this.stateManager.setCapturedImages({
1016
1193
  front: {
1017
- fullFrame: captureCanvas.toDataURL('image/png'),
1018
- cropped: croppedCanvas.toDataURL('image/png')
1194
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
1195
+ cropped: croppedCanvas.toDataURL('image/jpeg')
1019
1196
  }
1020
1197
  });
1021
1198
  // Check if document classification is enabled (independent of detector)
1022
1199
  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;
1200
+ try {
1201
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
1202
+ if (classification && classification.class === 'passport') {
1203
+ this.completeProcess(true);
1204
+ this.returnCanvasToPool(captureCanvas);
1205
+ this.returnCanvasToPool(croppedCanvas);
1206
+ // Clear processing button when passport is detected
1207
+ this.processingButton = null;
1208
+ return;
1209
+ }
1210
+ }
1211
+ catch (classifyError) {
1212
+ // No cambiar a manual aquí, solo registrar el error y continuar
1029
1213
  }
1030
1214
  }
1031
1215
  this.stateManager.updateCaptureState({
@@ -1033,22 +1217,28 @@ export class JaakStamps {
1033
1217
  isDetectionPaused: true,
1034
1218
  showFlipAnimation: true
1035
1219
  });
1220
+ this.triggerRerender();
1036
1221
  setTimeout(() => {
1037
1222
  this.stateManager.updateCaptureState({
1038
1223
  showFlipAnimation: false,
1039
1224
  isDetectionPaused: false
1040
1225
  });
1226
+ this.triggerRerender();
1041
1227
  this.startBackDocumentTimer();
1228
+ // Clear processing button after animation completes
1229
+ this.processingButton = null;
1042
1230
  }, 3000);
1043
1231
  }
1044
1232
  else if (captureState.step === 'back') {
1045
1233
  this.stateManager.setCapturedImages({
1046
1234
  back: {
1047
- fullFrame: captureCanvas.toDataURL('image/png'),
1048
- cropped: croppedCanvas.toDataURL('image/png')
1235
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
1236
+ cropped: croppedCanvas.toDataURL('image/jpeg')
1049
1237
  }
1050
1238
  });
1051
1239
  this.completeProcess(false);
1240
+ // Clear processing button after back capture completes
1241
+ this.processingButton = null;
1052
1242
  }
1053
1243
  // Return canvases to pool after use
1054
1244
  this.returnCanvasToPool(captureCanvas);
@@ -1081,16 +1271,21 @@ export class JaakStamps {
1081
1271
  if (captureState.step === 'front') {
1082
1272
  this.stateManager.setCapturedImages({
1083
1273
  front: {
1084
- fullFrame: captureCanvas.toDataURL('image/png'),
1085
- cropped: croppedCanvas.toDataURL('image/png')
1274
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
1275
+ cropped: croppedCanvas.toDataURL('image/jpeg')
1086
1276
  }
1087
1277
  });
1088
1278
  // Check if document classification is enabled
1089
1279
  if (this.useDocumentClassification) {
1090
- const classification = await this.detectionService.classifyDocument(croppedCanvas);
1091
- if (classification && classification.class === 'passport') {
1092
- this.completeProcess(true);
1093
- return;
1280
+ try {
1281
+ const classification = await this.detectionService.classifyDocument(croppedCanvas);
1282
+ if (classification && classification.class === 'passport') {
1283
+ this.completeProcess(true);
1284
+ return;
1285
+ }
1286
+ }
1287
+ catch (classifyError) {
1288
+ // No cambiar a manual aquí, solo registrar el error y continuar
1094
1289
  }
1095
1290
  }
1096
1291
  this.stateManager.updateCaptureState({
@@ -1109,8 +1304,8 @@ export class JaakStamps {
1109
1304
  else if (captureState.step === 'back') {
1110
1305
  this.stateManager.setCapturedImages({
1111
1306
  back: {
1112
- fullFrame: captureCanvas.toDataURL('image/png'),
1113
- cropped: croppedCanvas.toDataURL('image/png')
1307
+ fullFrame: captureCanvas.toDataURL('image/jpeg'),
1308
+ cropped: croppedCanvas.toDataURL('image/jpeg')
1114
1309
  }
1115
1310
  });
1116
1311
  this.completeProcess(false);
@@ -1127,6 +1322,138 @@ export class JaakStamps {
1127
1322
  cardOutline?.classList.remove('capturing');
1128
1323
  }, 600);
1129
1324
  }
1325
+ // Método simplificado para cambio a modo manual
1326
+ switchToManualMode() {
1327
+ // Only switch if detector is enabled and not already switched
1328
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
1329
+ return;
1330
+ }
1331
+ // Detener todo el monitoreo de performance
1332
+ this.stopPerformanceMonitoring();
1333
+ // Liberar recursos de ONNX
1334
+ this.releaseOnnxResources();
1335
+ // Resetear máscara a blanco y limpiar detecciones
1336
+ this.resetMaskToWhite();
1337
+ this.detectionBoxes = [];
1338
+ this.hasAutoSwitchedToManual = true;
1339
+ this.performanceDegradedMode = true;
1340
+ this.showManualCaptureButton = true;
1341
+ this.showPerformanceMessage = true;
1342
+ // Hide performance message after 5 seconds
1343
+ setTimeout(() => {
1344
+ this.showPerformanceMessage = false;
1345
+ }, 5000);
1346
+ }
1347
+ // Método para cambiar a modo manual por errores de ONNX
1348
+ switchToManualModeWithWarning(warningMessage) {
1349
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1350
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
1351
+ return;
1352
+ }
1353
+ // No necesitamos liberar recursos ONNX porque no se han cargado aún
1354
+ // Resetear máscara a blanco y limpiar detecciones
1355
+ this.resetMaskToWhite();
1356
+ this.detectionBoxes = [];
1357
+ // Cambiar a modo manual
1358
+ this.hasAutoSwitchedToManual = true;
1359
+ this.performanceDegradedMode = true;
1360
+ this.showManualCaptureButton = true;
1361
+ this.useDocumentDetector = false; // Desactivar detector automático
1362
+ // Actualizar el estado con mensaje informativo (no error)
1363
+ this.updateStatus('Modo manual activado', warningMessage, 'active');
1364
+ // Continuar con la configuración de cámara
1365
+ this.continueWithCameraSetup();
1366
+ }
1367
+ async continueWithCameraSetup() {
1368
+ try {
1369
+ // Paso 2: Detectar y configurar dispositivos
1370
+ this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1371
+ try {
1372
+ await this.cameraService.enumerateDevices();
1373
+ }
1374
+ catch (enumerateError) {
1375
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
1376
+ }
1377
+ try {
1378
+ await this.updateCameraInfoWithAutofocus();
1379
+ }
1380
+ catch (cameraInfoError) {
1381
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
1382
+ }
1383
+ // Paso 3: Configurar cámara seleccionada
1384
+ this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1385
+ let stream;
1386
+ try {
1387
+ stream = await this.cameraService.setupCamera();
1388
+ }
1389
+ catch (setupError) {
1390
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
1391
+ }
1392
+ // Paso 4: Inicializar video
1393
+ this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
1394
+ try {
1395
+ await this.initializeVideoStream(stream);
1396
+ }
1397
+ catch (videoError) {
1398
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
1399
+ }
1400
+ // Paso 5: Calibrar máscara
1401
+ if (this.detectionContainer) {
1402
+ const container = this.detectionContainer.parentElement;
1403
+ const rect = container.getBoundingClientRect();
1404
+ this.updateMaskDimensions(rect);
1405
+ }
1406
+ // Finalizar
1407
+ this.startTime = Date.now();
1408
+ this.stateManager.updateCaptureState({ isLoading: false });
1409
+ // Mostrar botón manual (ya debe estar habilitado)
1410
+ this.showManualCaptureButton = true;
1411
+ }
1412
+ catch (error) {
1413
+ this.updateStatus('Error de cámara', error.message, 'error');
1414
+ throw error;
1415
+ }
1416
+ }
1417
+ switchToManualModeWithError(errorMessage) {
1418
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1419
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
1420
+ return;
1421
+ }
1422
+ // Liberar recursos de ONNX inmediatamente
1423
+ this.releaseOnnxResources();
1424
+ // Resetear máscara a blanco y limpiar detecciones
1425
+ this.resetMaskToWhite();
1426
+ this.detectionBoxes = [];
1427
+ // Cambiar a modo manual
1428
+ this.hasAutoSwitchedToManual = true;
1429
+ this.performanceDegradedMode = true;
1430
+ this.showManualCaptureButton = true;
1431
+ this.useDocumentDetector = false; // Desactivar detector automático
1432
+ // Actualizar el estado con mensaje de error
1433
+ this.updateStatus('Modo manual activado', errorMessage, 'error');
1434
+ // Continuar con la configuración de cámara para mostrar el video
1435
+ this.continueWithCameraSetup().catch(() => {
1436
+ // Si falla la configuración de cámara, mantener el mensaje de error pero cambiar el estado
1437
+ setTimeout(() => {
1438
+ this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
1439
+ }, 5000);
1440
+ });
1441
+ }
1442
+ stopPerformanceMonitoring() {
1443
+ // Nota: No necesitamos limpiar más variables porque ya no se usarán
1444
+ // El sistema automáticamente saltará la lógica de performance una vez que hasAutoSwitchedToManual = true
1445
+ }
1446
+ // Método para liberar recursos de ONNX cuando se cambia a modo manual
1447
+ releaseOnnxResources() {
1448
+ try {
1449
+ // Liberar recursos del servicio de detección
1450
+ if (this.detectionService) {
1451
+ this.detectionService.cleanup();
1452
+ }
1453
+ }
1454
+ catch (error) {
1455
+ }
1456
+ }
1130
1457
  completeProcess(skippedBack = false) {
1131
1458
  this.stateManager.updateCaptureState({
1132
1459
  step: 'completed',
@@ -1197,6 +1524,12 @@ export class JaakStamps {
1197
1524
  try {
1198
1525
  // Close the selector immediately when user selects a camera
1199
1526
  this.showCameraSelector = false;
1527
+ // Check if user selected the same camera that's already active
1528
+ const currentCameraId = this.cameraService.getSelectedCameraId();
1529
+ if (currentCameraId === cameraId) {
1530
+ // Same camera selected, just close the selector without switching
1531
+ return;
1532
+ }
1200
1533
  this.isSwitchingCamera = true;
1201
1534
  // Stop current video stream
1202
1535
  if (this.videoStream) {
@@ -1240,6 +1573,13 @@ export class JaakStamps {
1240
1573
  this.detectionBoxes = [];
1241
1574
  this.alignmentStartTime = undefined;
1242
1575
  this.hasDocumentDetected = false;
1576
+ // Reset sistema simplificado
1577
+ this.processedFramesCount = 0;
1578
+ this.slowFrameCount = 0;
1579
+ // Reset performance degradation state
1580
+ this.hasAutoSwitchedToManual = false;
1581
+ this.performanceDegradedMode = false;
1582
+ this.showPerformanceMessage = false;
1243
1583
  if (this.alignmentTimer) {
1244
1584
  clearTimeout(this.alignmentTimer);
1245
1585
  this.alignmentTimer = undefined;
@@ -1343,6 +1683,24 @@ export class JaakStamps {
1343
1683
  // Performance is good, use base frame skip
1344
1684
  return this.BASE_FRAME_SKIP;
1345
1685
  }
1686
+ // Método para resetear la máscara a color blanco
1687
+ resetMaskToWhite() {
1688
+ const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
1689
+ const corners = this.el.shadowRoot?.querySelectorAll('.corner');
1690
+ const topSide = this.el.shadowRoot?.querySelector('.side-top');
1691
+ const rightSide = this.el.shadowRoot?.querySelector('.side-right');
1692
+ const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
1693
+ const leftSide = this.el.shadowRoot?.querySelector('.side-left');
1694
+ // Remover todas las clases de alineación y estado perfecto
1695
+ cardOutline?.classList.remove('perfect-match');
1696
+ corners?.forEach(corner => corner.classList.remove('perfect-match'));
1697
+ topSide?.classList.remove('aligned');
1698
+ rightSide?.classList.remove('aligned');
1699
+ bottomSide?.classList.remove('aligned');
1700
+ leftSide?.classList.remove('aligned');
1701
+ // Resetear estado de alineación
1702
+ this.sideAlignment = { top: false, right: false, bottom: false, left: false };
1703
+ }
1346
1704
  // Canvas pool management for screenshots
1347
1705
  getPooledCanvas(width, height) {
1348
1706
  // Try to find a canvas with matching dimensions
@@ -1602,7 +1960,11 @@ export class JaakStamps {
1602
1960
  "currentStatus": {},
1603
1961
  "performanceData": {},
1604
1962
  "backDocumentTimerRemaining": {},
1605
- "showManualCaptureButton": {}
1963
+ "showManualCaptureButton": {},
1964
+ "performanceDegradedMode": {},
1965
+ "showPerformanceMessage": {},
1966
+ "captureStateVersion": {},
1967
+ "processingButton": {}
1606
1968
  };
1607
1969
  }
1608
1970
  static get events() {