@jaak.ai/stamps 2.1.0-dev.6 → 2.1.0-dev.8

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 (28) hide show
  1. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  2. package/dist/cjs/jaak-stamps.cjs.entry.js +623 -31
  3. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  4. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/collection/components/my-component/my-component.css +36 -11
  7. package/dist/collection/components/my-component/my-component.js +484 -24
  8. package/dist/collection/components/my-component/my-component.js.map +1 -1
  9. package/dist/collection/services/DetectionService.js +176 -9
  10. package/dist/collection/services/DetectionService.js.map +1 -1
  11. package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
  12. package/dist/components/jaak-stamps.js +627 -32
  13. package/dist/components/jaak-stamps.js.map +1 -1
  14. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  15. package/dist/esm/jaak-stamps.entry.js +623 -31
  16. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  17. package/dist/esm/loader.js +1 -1
  18. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  19. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  20. package/dist/jaak-stamps-webcomponent/p-8e25497e.entry.js +2 -0
  21. package/dist/jaak-stamps-webcomponent/p-8e25497e.entry.js.map +1 -0
  22. package/dist/types/components/my-component/my-component.d.ts +32 -0
  23. package/dist/types/components.d.ts +3 -1
  24. package/dist/types/services/DetectionService.d.ts +9 -1
  25. package/dist/types/services/interfaces/IDetectionService.d.ts +6 -0
  26. package/package.json +1 -1
  27. package/dist/jaak-stamps-webcomponent/p-d05dc382.entry.js +0 -2
  28. package/dist/jaak-stamps-webcomponent/p-d05dc382.entry.js.map +0 -1
@@ -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() {
@@ -405,7 +411,13 @@ export class JaakStamps {
405
411
  }
406
412
  try {
407
413
  this.exitSession();
408
- return { success: true };
414
+ // Aggressive cleanup when stopping capture
415
+ const cleanupResult = await this.aggressiveResourceCleanup();
416
+ return {
417
+ success: true,
418
+ resourcesFreed: cleanupResult.freed,
419
+ cleanupErrors: cleanupResult.errors
420
+ };
409
421
  }
410
422
  catch (error) {
411
423
  return { success: false, error: error.message };
@@ -425,8 +437,14 @@ export class JaakStamps {
425
437
  }
426
438
  }
427
439
  async skipBackCapture() {
440
+ console.log('🟡 Botón clicked: Saltar reverso');
441
+ // Set processing state immediately
442
+ this.processingButton = 'skip-back';
443
+ // Add small delay to show spinner
444
+ await new Promise(resolve => setTimeout(resolve, 100));
428
445
  const readyCheck = this.isComponentReady();
429
446
  if (!readyCheck.ready) {
447
+ this.processingButton = null;
430
448
  return { success: false, error: readyCheck.message };
431
449
  }
432
450
  try {
@@ -443,6 +461,10 @@ export class JaakStamps {
443
461
  catch (error) {
444
462
  return { success: false, error: error.message };
445
463
  }
464
+ finally {
465
+ // Always clear processing state when done
466
+ this.processingButton = null;
467
+ }
446
468
  }
447
469
  async getStatus() {
448
470
  const readyCheck = this.isComponentReady();
@@ -620,9 +642,76 @@ export class JaakStamps {
620
642
  // Always allow getting capture delay, even during initialization
621
643
  return this.captureDelay;
622
644
  }
645
+ async getResourceReport() {
646
+ // Public method to get resource usage report
647
+ return this.getResourceReportInternal();
648
+ }
649
+ async forceResourceCleanup() {
650
+ // Public method to force aggressive resource cleanup
651
+ const readyCheck = this.isComponentReady();
652
+ if (!readyCheck.ready) {
653
+ return { success: false, error: readyCheck.message };
654
+ }
655
+ try {
656
+ const cleanupResult = await this.aggressiveResourceCleanup();
657
+ return {
658
+ success: true,
659
+ resourcesFreed: cleanupResult.freed,
660
+ errors: cleanupResult.errors,
661
+ timestamp: new Date().toISOString()
662
+ };
663
+ }
664
+ catch (error) {
665
+ return { success: false, error: error.message };
666
+ }
667
+ }
668
+ // Memory and device capability detection
669
+ checkDeviceCapabilities() {
670
+ const deviceMemory = navigator.deviceMemory;
671
+ const hardwareConcurrency = navigator.hardwareConcurrency || 1;
672
+ const isLowEndDevice = hardwareConcurrency <= 2;
673
+ const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
674
+ const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
675
+ // Si tenemos info de memoria del dispositivo
676
+ if (deviceMemory && deviceMemory < 4) {
677
+ return {
678
+ canUseML: false,
679
+ reason: `Memoria del dispositivo (${deviceMemory}GB) insuficiente para detección automática. Modo manual activado para mejor rendimiento.`,
680
+ memoryMB: deviceMemory * 1024
681
+ };
682
+ }
683
+ // Dispositivos móviles de gama baja
684
+ if (isMobile && isLowEndDevice) {
685
+ return {
686
+ canUseML: false,
687
+ reason: 'Dispositivo móvil optimizado detectado. Modo manual activado para mejor rendimiento y duración de batería.',
688
+ memoryMB: null
689
+ };
690
+ }
691
+ // Safari tiene limitaciones conocidas con WASM
692
+ if (isSafari) {
693
+ return {
694
+ canUseML: false,
695
+ reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
696
+ memoryMB: null
697
+ };
698
+ }
699
+ return {
700
+ canUseML: true,
701
+ reason: 'Dispositivo compatible con detección automática.',
702
+ memoryMB: deviceMemory ? deviceMemory * 1024 : null
703
+ };
704
+ }
623
705
  // DETECTION METHODS
624
706
  async startDetection() {
625
707
  try {
708
+ // Verificar capacidades del dispositivo antes de cargar modelos
709
+ const capabilities = this.checkDeviceCapabilities();
710
+ // Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
711
+ if (!capabilities.canUseML && this.useDocumentDetector) {
712
+ this.switchToManualModeWithWarning(capabilities.reason);
713
+ return;
714
+ }
626
715
  // Paso 1: Verificar y cargar modelos si es necesario
627
716
  if (!this.detectionService.isModelLoaded()) {
628
717
  const loadStartTime = performance.now();
@@ -632,16 +721,32 @@ export class JaakStamps {
632
721
  await this.detectionService.loadModel();
633
722
  }
634
723
  catch (modelError) {
635
- this.switchToManualModeWithError('Error al cargar modelo de detección');
636
- throw modelError;
724
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
725
+ if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
726
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
727
+ this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
728
+ return;
729
+ }
730
+ else {
731
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
732
+ throw modelError;
733
+ }
637
734
  }
638
735
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
639
736
  try {
640
737
  await this.detectionService.loadClassificationModel();
641
738
  }
642
739
  catch (classificationError) {
643
- this.switchToManualModeWithError('Error al cargar modelo de clasificación');
644
- throw classificationError;
740
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
741
+ if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
742
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
743
+ this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
744
+ return;
745
+ }
746
+ else {
747
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
748
+ throw classificationError;
749
+ }
645
750
  }
646
751
  const loadEndTime = performance.now();
647
752
  const loadTime = loadEndTime - loadStartTime;
@@ -652,11 +757,27 @@ export class JaakStamps {
652
757
  }
653
758
  // Paso 2: Detectar y configurar dispositivos
654
759
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
655
- await this.cameraService.enumerateDevices();
656
- await this.updateCameraInfoWithAutofocus();
760
+ try {
761
+ await this.cameraService.enumerateDevices();
762
+ }
763
+ catch (enumerateError) {
764
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
765
+ }
766
+ try {
767
+ await this.updateCameraInfoWithAutofocus();
768
+ }
769
+ catch (cameraInfoError) {
770
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
771
+ }
657
772
  // Paso 3: Configurar cámara seleccionada
658
773
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
659
- const stream = await this.cameraService.setupCamera();
774
+ let stream;
775
+ try {
776
+ stream = await this.cameraService.setupCamera();
777
+ }
778
+ catch (setupError) {
779
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
780
+ }
660
781
  // Paso 4: Inicializar video y mostrar estado de análisis inicial
661
782
  if (this.useDocumentDetector) {
662
783
  this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
@@ -664,7 +785,12 @@ export class JaakStamps {
664
785
  else {
665
786
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
666
787
  }
667
- await this.initializeVideoStream(stream);
788
+ try {
789
+ await this.initializeVideoStream(stream);
790
+ }
791
+ catch (videoError) {
792
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
793
+ }
668
794
  // Paso 5: Calibrar máscara
669
795
  if (this.detectionContainer) {
670
796
  const container = this.detectionContainer.parentElement;
@@ -874,11 +1000,182 @@ export class JaakStamps {
874
1000
  }
875
1001
  // Clear canvas pool
876
1002
  this.canvasPool = [];
1003
+ // Clean DOM references and memory leaks
1004
+ this.cleanupDOMReferences();
877
1005
  this.detectionBoxes = [];
878
1006
  this.alignmentStartTime = undefined;
879
1007
  this.hasDocumentDetected = false;
880
1008
  this.serviceContainer?.cleanup();
881
1009
  }
1010
+ // AGGRESSIVE RESOURCE CLEANUP METHODS
1011
+ // Clean DOM references and memory leaks
1012
+ cleanupDOMReferences() {
1013
+ // Remove event listeners that could maintain references
1014
+ if (this.videoRef) {
1015
+ // Remove common event listeners (if any were added)
1016
+ this.videoRef.srcObject = null;
1017
+ this.videoRef = undefined;
1018
+ }
1019
+ // Clean detection container
1020
+ if (this.detectionContainer) {
1021
+ this.detectionContainer = undefined;
1022
+ }
1023
+ // Clean references to images in the DOM and revoke blob URLs
1024
+ const images = this.el.shadowRoot?.querySelectorAll('img');
1025
+ images?.forEach(img => {
1026
+ if (img.src.startsWith('blob:')) {
1027
+ URL.revokeObjectURL(img.src);
1028
+ }
1029
+ img.src = '';
1030
+ });
1031
+ }
1032
+ // Clear browser cache and storage
1033
+ async clearBrowserCache() {
1034
+ try {
1035
+ // 1. Clear Service Worker cache if exists
1036
+ if ('serviceWorker' in navigator) {
1037
+ const registrations = await navigator.serviceWorker.getRegistrations();
1038
+ await Promise.all(registrations.map(registration => registration.unregister()));
1039
+ }
1040
+ // 2. Clear Cache API specific to the component
1041
+ const cacheNames = await caches.keys();
1042
+ const componentCaches = cacheNames.filter(name => name.includes('jaak') || name.includes('stamps'));
1043
+ await Promise.all(componentCaches.map(cacheName => caches.delete(cacheName)));
1044
+ // 3. Force garbage collection if available (only in dev)
1045
+ if (this.debug && window.gc) {
1046
+ window.gc();
1047
+ }
1048
+ }
1049
+ catch (error) {
1050
+ console.warn('Error clearing browser cache:', error);
1051
+ }
1052
+ }
1053
+ // Clear local storage specific to the component
1054
+ async clearLocalStorage() {
1055
+ try {
1056
+ // Clear localStorage specific to the component
1057
+ const keysToRemove = [];
1058
+ for (let i = 0; i < localStorage.length; i++) {
1059
+ const key = localStorage.key(i);
1060
+ if (key && (key.includes('jaak') || key.includes('onnx') || key.includes('stamps'))) {
1061
+ keysToRemove.push(key);
1062
+ }
1063
+ }
1064
+ keysToRemove.forEach(key => localStorage.removeItem(key));
1065
+ // Clear sessionStorage
1066
+ const sessionKeysToRemove = [];
1067
+ for (let i = 0; i < sessionStorage.length; i++) {
1068
+ const key = sessionStorage.key(i);
1069
+ if (key && (key.includes('jaak') || key.includes('onnx'))) {
1070
+ sessionKeysToRemove.push(key);
1071
+ }
1072
+ }
1073
+ sessionKeysToRemove.forEach(key => sessionStorage.removeItem(key));
1074
+ }
1075
+ catch (error) {
1076
+ console.warn('Error clearing local storage:', error);
1077
+ }
1078
+ }
1079
+ // Aggressive resource cleanup combining all strategies
1080
+ async aggressiveResourceCleanup() {
1081
+ const freed = [];
1082
+ const errors = [];
1083
+ try {
1084
+ // 1. Release ONNX resources
1085
+ this.releaseOnnxResources();
1086
+ freed.push('ONNX models');
1087
+ // 2. Clear model cache in detection service
1088
+ if (this.detectionService) {
1089
+ await this.detectionService.clearModelCache();
1090
+ freed.push('Model cache');
1091
+ }
1092
+ // 3. Clear browser cache
1093
+ await this.clearBrowserCache();
1094
+ freed.push('Browser cache');
1095
+ // 4. Clear local storage
1096
+ await this.clearLocalStorage();
1097
+ freed.push('Local storage');
1098
+ // 5. Clean DOM references
1099
+ this.cleanupDOMReferences();
1100
+ freed.push('DOM references');
1101
+ // 6. Optimize canvas pools
1102
+ if (this.detectionService) {
1103
+ this.detectionService.optimizeCanvasPool(0);
1104
+ freed.push('Canvas pools');
1105
+ }
1106
+ // 7. Clear component canvas pool
1107
+ this.canvasPool = [];
1108
+ freed.push('Component canvas pool');
1109
+ // 8. Force garbage collection (if available)
1110
+ if (this.debug && window.gc) {
1111
+ window.gc();
1112
+ freed.push('Garbage collection');
1113
+ }
1114
+ }
1115
+ catch (error) {
1116
+ errors.push(error.message);
1117
+ }
1118
+ return { freed, errors };
1119
+ }
1120
+ // Progressive resource release after front capture
1121
+ async onFrontCaptureComplete() {
1122
+ try {
1123
+ // Release classification model if not needed for back
1124
+ if (this.detectionService && !this.useDocumentClassification) {
1125
+ await this.detectionService.releaseMobileNetResources();
1126
+ }
1127
+ // Optimize canvas pool to minimum needed for back capture
1128
+ if (this.detectionService) {
1129
+ this.detectionService.optimizeCanvasPool(1);
1130
+ }
1131
+ // Reduce component canvas pool
1132
+ while (this.canvasPool.length > 1) {
1133
+ const canvas = this.canvasPool.pop();
1134
+ if (canvas) {
1135
+ const ctx = canvas.getContext('2d');
1136
+ if (ctx) {
1137
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1138
+ }
1139
+ }
1140
+ }
1141
+ if (this.debug) {
1142
+ console.log('🧹 Progressive cleanup after front capture completed');
1143
+ }
1144
+ }
1145
+ catch (error) {
1146
+ console.warn('Error in progressive cleanup:', error);
1147
+ }
1148
+ }
1149
+ // Resource reporting for debugging
1150
+ getResourceReportInternal() {
1151
+ const report = {
1152
+ timestamp: new Date().toISOString(),
1153
+ component: {
1154
+ hasVideoStream: !!this.videoStream,
1155
+ hasAnimationFrame: !!this.animationId,
1156
+ canvasPoolSize: this.canvasPool.length,
1157
+ detectionBoxesCount: this.detectionBoxes.length,
1158
+ performanceMonitorActive: !!this.performanceUpdateInterval
1159
+ }
1160
+ };
1161
+ // Add detection service stats if available
1162
+ if (this.detectionService) {
1163
+ report.detectionService = {
1164
+ isModelLoaded: this.detectionService.isModelLoaded(),
1165
+ canvasPoolStats: this.detectionService.getPoolStats()
1166
+ };
1167
+ }
1168
+ // Add memory info if available
1169
+ if ('memory' in performance) {
1170
+ const memInfo = performance.memory;
1171
+ report.memory = {
1172
+ usedJSHeapSize: Math.round(memInfo.usedJSHeapSize / 1048576), // MB
1173
+ totalJSHeapSize: Math.round(memInfo.totalJSHeapSize / 1048576), // MB
1174
+ jsHeapSizeLimit: Math.round(memInfo.jsHeapSizeLimit / 1048576) // MB
1175
+ };
1176
+ }
1177
+ return report;
1178
+ }
882
1179
  render() {
883
1180
  const captureState = this.stateManager?.getCaptureState() || {
884
1181
  isVideoActive: false,
@@ -889,7 +1186,7 @@ export class JaakStamps {
889
1186
  isCapturing: false
890
1187
  };
891
1188
  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: {
1189
+ return (h("div", { key: 'ca44b9eacbde9f688f2c41aa21ad35a2b882002b', class: "detector-container" }, h("div", { key: '86c7b5d31ed69276b62fa43e01c92b0c0b9f298d', class: "video-container" }, h("video", { key: 'bb310544c17ae734d8b42ed2617748c2f69743ff', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '078a79a0c6386ca6c040e629acbf4f52f12d8e29', 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
1190
  position: 'absolute',
894
1191
  left: `${box.x}px`,
895
1192
  top: `${box.y}px`,
@@ -898,9 +1195,9 @@ export class JaakStamps {
898
1195
  border: '2px solid #32406C',
899
1196
  pointerEvents: 'none',
900
1197
  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
1198
+ } })))), this.isMaskReady && (h("div", { key: '990015ff5b2ded89405dd2b891c027aad8788f3e', class: "overlay-mask" }, h("div", { key: 'dd9145140987addb1b6e4cc2119b8e9f89003726', class: "card-outline" }, h("div", { key: 'e8f4ec692a8b80c7a98075e4f708b79a272db67b', class: "side side-top" }), h("div", { key: '2577253b40425ecdcb3dd51b2f4a43c15c83ca79', class: "side side-right" }), h("div", { key: '30f4454020c00cf4fb92308af5ecf1843ec24005', class: "side side-bottom" }), h("div", { key: '3459f634987b8396106fff23fa1c99b295ee7d0a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '84f4d70947e211a9c6b296531d53420223d2ca34', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '7c28e86e8de6c33bf7d7baab3083c5f6a0ab060f', class: "back-capture-section" }, h("div", { key: '27bdcf9cfe9790c4bbad484423f6f4c353ccf0b3', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (h("button", { key: 'ed0320b1df412bf53cbe4e4119f6d769926eb2c5', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '0bba706c8ebda59c33faca8e97b7536146462d21', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'b072c46b25d16a04c5e0a8f3975d8aa8d24576d5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: 'efd0827d0f19c9072d49d1b3c0046ef6c95a842c', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
902
1199
  ? `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" })))));
1200
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'bd0303e8ce85211e4c68fc3cd79545db7c74288b', class: "camera-controls" }, h("button", { key: 'e4327d260b988b67e0de3654c72f24455cd3bbbd', 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: '35da16baf4c62466c2a66650209a1daf842c0db2', class: "camera-selector-dropdown" }, h("div", { key: '7035dd0e22f614cf239a015c6d59531b68fa74a9', class: "camera-selector-header" }, h("span", { key: 'ae026f31931f52ea0daf72391a9de4ff9e4b88f1' }, "Seleccionar C\u00E1mara"), h("button", { key: '40f2ba0770bafea71fedbf849938010e743be776', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '6f26927094a724a35ebb07ef701caf14f296a837', 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: '082236981c369f8fd487f036439b16e6560514be', class: "device-info" }, h("small", { key: 'ccbbfa0bca96395f574bda71103338ae9226f709' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '6baa487409503777e90453b39fd30649941cbbe4', class: "manual-capture-section" }, h("button", { key: '893f4715ca3315613f779603f178459955f686f0', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '7c30b680fe84f4c8227beba45d8416f1edb8348f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: 'd8ef052cf67d7bda4b3534febf96efa2cdcc6bf7', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '3dae7634e770dda2655b1042ec8fb997b3cb2220', class: "flip-animation" }, h("div", { key: 'a977fc5e889369121bacc06cf5213dfee39d858d', class: "id-card-icon" }), h("div", { key: '4f052cdc8c7b493cddce90e108e976325c9bab8e', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '5117cd6e63e1e0763c1503f6f60f3c7ef60dd665', class: "success-animation" }, h("div", { key: 'd11128c45c7118915cc0dd9c25845bb55b4b3eb2', class: "check-icon" }), h("div", { key: '693e197bf8a4c91ac7ddedb457d15aa3aa46a5d5', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '89caaa9d78eb522e0fadfd1c0e844e05d29b8286', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '3f41d40b05b71e13e7e1d0c0e980b48308cba9d6', class: "status-spinner" })), h("div", { key: '8674480928f4f4a77099c0600ed84f0e27903379', class: "status-content" }, h("div", { key: 'e88f4044ab8115a6a5cbb78cc4030975eef9149a', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'ad581e42c30c304b837711549148c11b801dee5e', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '1b371c1b47f3ba699885db00fef52eb448f9e245', class: "performance-monitor" }, h("div", { key: '8b9ac60449a15e9fb966c329b2e6c9285c0228c0', class: "performance-expanded" }, h("div", { key: '760c11a3ed28e248a144d53a92fd8adee6822ff2', class: "metrics-row" }, h("div", { key: '858ab8b106f1d12210e4e15c423ab3bab11bdb60', class: "metric-compact" }, h("span", { key: '65fe2479c3822d99452e7e5faa93c25a80765024', class: "metric-label" }, "FPS"), h("span", { key: '9faf7763d1284a6f3c1e15fb0f3c99f1d45bb4dc', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'c95ed8fa8101f42bfba5cc8ca7debbad028caf42', class: "metric-compact" }, h("span", { key: '3555b3778cb71e959d132d6aa563f58f757915d8', class: "metric-label" }, "MEM"), h("span", { key: 'd72b0491147a6f80dcf33f5d72dd8dae5898f44a', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'cd4dc81f28f9e59a36a0295dde7bfee89f50d3e2', class: "metrics-row" }, h("div", { key: 'c43d8624c5da256ce3e97f67b022bdd98fb04e07', class: "metric-compact" }, h("span", { key: 'adb402d83809a90580b81544f31b5331bdff59dd', class: "metric-label" }, "INF"), h("span", { key: 'e48c9814c08acbe321460a29e42cb49b0eab38ff', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '71517f9279e9a6aa0d5b02e2169db868ca3fb676', class: "metric-compact" }, h("span", { key: '3ddbcc56b137aea702a40f900178e8e16efd146e', class: "metric-label" }, "FRAME"), h("span", { key: 'd3a70afb511d3f72b485b3a1cb922876faa62b11', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: 'a3b62cdbe830b1024ab96f7fb726825df24fecdc', class: "metrics-row" }, h("div", { key: '95471e5cdde748d1ce6bdee3540740dd3aa66865', class: "metric-compact" }, h("span", { key: 'c93e01c0bdc67621c3a666357d2477b7a5275d52', class: "metric-label" }, "DET"), h("span", { key: 'ef33c86a83101b0989b14c7f1ab0ce0b331b397f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'e14d56128bf96337e4c3859f8e4003e001e8e953', class: "metric-compact" }, h("span", { key: '8aa828632f0a977a27ed377f103bb8d5e7e0ed1a', class: "metric-label" }, "RATE"), h("span", { key: '59035c473b5a747a27f774e2554322a084b6d19f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4d0c2de00954416faac30ab622fb21f36c932aca', class: "watermark" }, h("img", { key: 'd8f775428ebb3ba86a943ca73b074db2180d0c29', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
904
1201
  }
905
1202
  // Utility methods
906
1203
  updateDetectionBoxes(boxes) {
@@ -1016,9 +1313,24 @@ export class JaakStamps {
1016
1313
  }
1017
1314
  }
1018
1315
  async takeManualScreenshot() {
1019
- if (!this.videoRef)
1316
+ console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
1317
+ // Set processing state immediately
1318
+ const captureState = this.stateManager?.getCaptureState();
1319
+ if (captureState?.step === 'front') {
1320
+ this.processingButton = 'capture-front';
1321
+ }
1322
+ else if (captureState?.step === 'back') {
1323
+ this.processingButton = 'capture-back';
1324
+ }
1325
+ console.log('🔵 processingButton set to:', this.processingButton);
1326
+ // Add small delay to show spinner
1327
+ await new Promise(resolve => setTimeout(resolve, 100));
1328
+ if (!this.videoRef) {
1329
+ this.processingButton = null;
1020
1330
  return;
1331
+ }
1021
1332
  // When using manual capture, use mask coordinates for cropping
1333
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
1022
1334
  await this.takeScreenshotWithMaskCoordinates();
1023
1335
  }
1024
1336
  async takeScreenshotWithMaskCoordinates() {
@@ -1094,6 +1406,8 @@ export class JaakStamps {
1094
1406
  this.completeProcess(true);
1095
1407
  this.returnCanvasToPool(captureCanvas);
1096
1408
  this.returnCanvasToPool(croppedCanvas);
1409
+ // Clear processing button when passport is detected
1410
+ this.processingButton = null;
1097
1411
  return;
1098
1412
  }
1099
1413
  }
@@ -1107,6 +1421,8 @@ export class JaakStamps {
1107
1421
  showFlipAnimation: true
1108
1422
  });
1109
1423
  this.triggerRerender();
1424
+ // Progressive resource cleanup after front capture
1425
+ await this.onFrontCaptureComplete();
1110
1426
  setTimeout(() => {
1111
1427
  this.stateManager.updateCaptureState({
1112
1428
  showFlipAnimation: false,
@@ -1114,6 +1430,8 @@ export class JaakStamps {
1114
1430
  });
1115
1431
  this.triggerRerender();
1116
1432
  this.startBackDocumentTimer();
1433
+ // Clear processing button after animation completes
1434
+ this.processingButton = null;
1117
1435
  }, 3000);
1118
1436
  }
1119
1437
  else if (captureState.step === 'back') {
@@ -1124,6 +1442,8 @@ export class JaakStamps {
1124
1442
  }
1125
1443
  });
1126
1444
  this.completeProcess(false);
1445
+ // Clear processing button after back capture completes
1446
+ this.processingButton = null;
1127
1447
  }
1128
1448
  // Return canvases to pool after use
1129
1449
  this.returnCanvasToPool(captureCanvas);
@@ -1178,6 +1498,8 @@ export class JaakStamps {
1178
1498
  isDetectionPaused: true,
1179
1499
  showFlipAnimation: true
1180
1500
  });
1501
+ // Progressive resource cleanup after front capture
1502
+ await this.onFrontCaptureComplete();
1181
1503
  setTimeout(() => {
1182
1504
  this.stateManager.updateCaptureState({
1183
1505
  showFlipAnimation: false,
@@ -1217,6 +1539,9 @@ export class JaakStamps {
1217
1539
  this.stopPerformanceMonitoring();
1218
1540
  // Liberar recursos de ONNX
1219
1541
  this.releaseOnnxResources();
1542
+ // Resetear máscara a blanco y limpiar detecciones
1543
+ this.resetMaskToWhite();
1544
+ this.detectionBoxes = [];
1220
1545
  this.hasAutoSwitchedToManual = true;
1221
1546
  this.performanceDegradedMode = true;
1222
1547
  this.showManualCaptureButton = true;
@@ -1227,6 +1552,75 @@ export class JaakStamps {
1227
1552
  }, 5000);
1228
1553
  }
1229
1554
  // Método para cambiar a modo manual por errores de ONNX
1555
+ switchToManualModeWithWarning(warningMessage) {
1556
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1557
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
1558
+ return;
1559
+ }
1560
+ // No necesitamos liberar recursos ONNX porque no se han cargado aún
1561
+ // Resetear máscara a blanco y limpiar detecciones
1562
+ this.resetMaskToWhite();
1563
+ this.detectionBoxes = [];
1564
+ // Cambiar a modo manual
1565
+ this.hasAutoSwitchedToManual = true;
1566
+ this.performanceDegradedMode = true;
1567
+ this.showManualCaptureButton = true;
1568
+ this.useDocumentDetector = false; // Desactivar detector automático
1569
+ // Actualizar el estado con mensaje informativo (no error)
1570
+ this.updateStatus('Modo manual activado', warningMessage, 'active');
1571
+ // Continuar con la configuración de cámara
1572
+ this.continueWithCameraSetup();
1573
+ }
1574
+ async continueWithCameraSetup() {
1575
+ try {
1576
+ // Paso 2: Detectar y configurar dispositivos
1577
+ this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1578
+ try {
1579
+ await this.cameraService.enumerateDevices();
1580
+ }
1581
+ catch (enumerateError) {
1582
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
1583
+ }
1584
+ try {
1585
+ await this.updateCameraInfoWithAutofocus();
1586
+ }
1587
+ catch (cameraInfoError) {
1588
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
1589
+ }
1590
+ // Paso 3: Configurar cámara seleccionada
1591
+ this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1592
+ let stream;
1593
+ try {
1594
+ stream = await this.cameraService.setupCamera();
1595
+ }
1596
+ catch (setupError) {
1597
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
1598
+ }
1599
+ // Paso 4: Inicializar video
1600
+ this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
1601
+ try {
1602
+ await this.initializeVideoStream(stream);
1603
+ }
1604
+ catch (videoError) {
1605
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
1606
+ }
1607
+ // Paso 5: Calibrar máscara
1608
+ if (this.detectionContainer) {
1609
+ const container = this.detectionContainer.parentElement;
1610
+ const rect = container.getBoundingClientRect();
1611
+ this.updateMaskDimensions(rect);
1612
+ }
1613
+ // Finalizar
1614
+ this.startTime = Date.now();
1615
+ this.stateManager.updateCaptureState({ isLoading: false });
1616
+ // Mostrar botón manual (ya debe estar habilitado)
1617
+ this.showManualCaptureButton = true;
1618
+ }
1619
+ catch (error) {
1620
+ this.updateStatus('Error de cámara', error.message, 'error');
1621
+ throw error;
1622
+ }
1623
+ }
1230
1624
  switchToManualModeWithError(errorMessage) {
1231
1625
  // Solo cambiar si el detector está habilitado y no se ha cambiado ya
1232
1626
  if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
@@ -1234,6 +1628,9 @@ export class JaakStamps {
1234
1628
  }
1235
1629
  // Liberar recursos de ONNX inmediatamente
1236
1630
  this.releaseOnnxResources();
1631
+ // Resetear máscara a blanco y limpiar detecciones
1632
+ this.resetMaskToWhite();
1633
+ this.detectionBoxes = [];
1237
1634
  // Cambiar a modo manual
1238
1635
  this.hasAutoSwitchedToManual = true;
1239
1636
  this.performanceDegradedMode = true;
@@ -1280,6 +1677,16 @@ export class JaakStamps {
1280
1677
  setTimeout(() => {
1281
1678
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
1282
1679
  }, 3000);
1680
+ // Aggressive cleanup after process completion with delay
1681
+ setTimeout(async () => {
1682
+ const cleanupResult = await this.aggressiveResourceCleanup();
1683
+ if (this.debug) {
1684
+ console.log('🧹 Post-completion cleanup:', cleanupResult.freed);
1685
+ if (cleanupResult.errors.length > 0) {
1686
+ console.warn('⚠️ Cleanup errors:', cleanupResult.errors);
1687
+ }
1688
+ }
1689
+ }, 1000); // Wait 1 second after completion
1283
1690
  }
1284
1691
  stopDetection() {
1285
1692
  if (this.animationId) {
@@ -1484,6 +1891,24 @@ export class JaakStamps {
1484
1891
  // Performance is good, use base frame skip
1485
1892
  return this.BASE_FRAME_SKIP;
1486
1893
  }
1894
+ // Método para resetear la máscara a color blanco
1895
+ resetMaskToWhite() {
1896
+ const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
1897
+ const corners = this.el.shadowRoot?.querySelectorAll('.corner');
1898
+ const topSide = this.el.shadowRoot?.querySelector('.side-top');
1899
+ const rightSide = this.el.shadowRoot?.querySelector('.side-right');
1900
+ const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
1901
+ const leftSide = this.el.shadowRoot?.querySelector('.side-left');
1902
+ // Remover todas las clases de alineación y estado perfecto
1903
+ cardOutline?.classList.remove('perfect-match');
1904
+ corners?.forEach(corner => corner.classList.remove('perfect-match'));
1905
+ topSide?.classList.remove('aligned');
1906
+ rightSide?.classList.remove('aligned');
1907
+ bottomSide?.classList.remove('aligned');
1908
+ leftSide?.classList.remove('aligned');
1909
+ // Resetear estado de alineación
1910
+ this.sideAlignment = { top: false, right: false, bottom: false, left: false };
1911
+ }
1487
1912
  // Canvas pool management for screenshots
1488
1913
  getPooledCanvas(width, height) {
1489
1914
  // Try to find a canvas with matching dimensions
@@ -1746,7 +2171,8 @@ export class JaakStamps {
1746
2171
  "showManualCaptureButton": {},
1747
2172
  "performanceDegradedMode": {},
1748
2173
  "showPerformanceMessage": {},
1749
- "captureStateVersion": {}
2174
+ "captureStateVersion": {},
2175
+ "processingButton": {}
1750
2176
  };
1751
2177
  }
1752
2178
  static get events() {
@@ -1842,7 +2268,7 @@ export class JaakStamps {
1842
2268
  },
1843
2269
  "stopCapture": {
1844
2270
  "complexType": {
1845
- "signature": "() => Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>",
2271
+ "signature": "() => Promise<{ success: boolean; resourcesFreed: string[]; cleanupErrors: string[]; error?: undefined; } | { success: boolean; error: any; resourcesFreed?: undefined; cleanupErrors?: undefined; }>",
1846
2272
  "parameters": [],
1847
2273
  "references": {
1848
2274
  "Promise": {
@@ -1850,7 +2276,7 @@ export class JaakStamps {
1850
2276
  "id": "global::Promise"
1851
2277
  }
1852
2278
  },
1853
- "return": "Promise<{ success: boolean; error?: undefined; } | { success: boolean; error: any; }>"
2279
+ "return": "Promise<{ success: boolean; resourcesFreed: string[]; cleanupErrors: string[]; error?: undefined; } | { success: boolean; error: any; resourcesFreed?: undefined; cleanupErrors?: undefined; }>"
1854
2280
  },
1855
2281
  "docs": {
1856
2282
  "text": "",
@@ -2010,6 +2436,40 @@ export class JaakStamps {
2010
2436
  "text": "",
2011
2437
  "tags": []
2012
2438
  }
2439
+ },
2440
+ "getResourceReport": {
2441
+ "complexType": {
2442
+ "signature": "() => Promise<any>",
2443
+ "parameters": [],
2444
+ "references": {
2445
+ "Promise": {
2446
+ "location": "global",
2447
+ "id": "global::Promise"
2448
+ }
2449
+ },
2450
+ "return": "Promise<any>"
2451
+ },
2452
+ "docs": {
2453
+ "text": "",
2454
+ "tags": []
2455
+ }
2456
+ },
2457
+ "forceResourceCleanup": {
2458
+ "complexType": {
2459
+ "signature": "() => Promise<{ success: boolean; resourcesFreed: string[]; errors: string[]; timestamp: string; error?: undefined; } | { success: boolean; error: any; resourcesFreed?: undefined; errors?: undefined; timestamp?: undefined; }>",
2460
+ "parameters": [],
2461
+ "references": {
2462
+ "Promise": {
2463
+ "location": "global",
2464
+ "id": "global::Promise"
2465
+ }
2466
+ },
2467
+ "return": "Promise<{ success: boolean; resourcesFreed: string[]; errors: string[]; timestamp: string; error?: undefined; } | { success: boolean; error: any; resourcesFreed?: undefined; errors?: undefined; timestamp?: undefined; }>"
2468
+ },
2469
+ "docs": {
2470
+ "text": "",
2471
+ "tags": []
2472
+ }
2013
2473
  }
2014
2474
  };
2015
2475
  }