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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,7 +18,7 @@ var patchBrowser = () => {
18
18
 
19
19
  patchBrowser().then(async (options) => {
20
20
  await index.globalScripts();
21
- return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
21
+ return index.bootstrapLazy([["jaak-stamps.cjs",[[1,"jaak-stamps",{"debug":[4],"alignmentTolerance":[2,"alignment-tolerance"],"maskSize":[2,"mask-size"],"cropMargin":[2,"crop-margin"],"useDocumentClassification":[4,"use-document-classification"],"useDocumentDetector":[4,"use-document-detector"],"preferredCamera":[1,"preferred-camera"],"captureDelay":[2,"capture-delay"],"enableBackDocumentTimer":[4,"enable-back-document-timer"],"backDocumentTimerDuration":[2,"back-document-timer-duration"],"detectionBoxes":[32],"sideAlignment":[32],"isMaskReady":[32],"shouldMirrorVideo":[32],"showCameraSelector":[32],"isSwitchingCamera":[32],"hasDocumentDetected":[32],"cameraInfoWithAutofocus":[32],"currentStatus":[32],"performanceData":[32],"backDocumentTimerRemaining":[32],"showManualCaptureButton":[32],"performanceDegradedMode":[32],"showPerformanceMessage":[32],"captureStateVersion":[32],"processingButton":[32],"getCapturedImages":[64],"isProcessCompleted":[64],"startCapture":[64],"stopCapture":[64],"resetCapture":[64],"skipBackCapture":[64],"getStatus":[64],"preloadModel":[64],"getCameraInfo":[64],"setPreferredCamera":[64],"setCaptureDelay":[64],"getCaptureDelay":[64]}]]]], options);
22
22
  });
23
23
 
24
24
  exports.setNonce = index.setNonce;
@@ -672,7 +672,12 @@ class DetectionService {
672
672
  this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
673
673
  }
674
674
  catch (error) {
675
- if (error.message.includes('failed to allocate a buffer')) {
675
+ // Múltiples tipos de errores de memoria que pueden ocurrir
676
+ const isMemoryError = error.message.includes('failed to allocate a buffer') ||
677
+ error.message.includes('Out of memory') ||
678
+ error.message.includes('RangeError: Out of memory') ||
679
+ error.message.includes('no available backend found');
680
+ if (isMemoryError) {
676
681
  const fallbackOptions = {
677
682
  executionProviders: ['wasm'],
678
683
  graphOptimizationLevel: 'disabled',
@@ -682,8 +687,17 @@ class DetectionService {
682
687
  executionMode: 'sequential',
683
688
  interOpNumThreads: 1,
684
689
  intraOpNumThreads: 1,
690
+ // Configuraciones adicionales para dispositivos con poca memoria
691
+ enableProfiling: false,
692
+ sessionLogSeverityLevel: 4,
693
+ sessionLogVerbosityLevel: 0,
685
694
  };
686
- this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
695
+ try {
696
+ this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
697
+ }
698
+ catch (fallbackError) {
699
+ throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
700
+ }
687
701
  }
688
702
  else {
689
703
  throw error;
@@ -723,7 +737,12 @@ class DetectionService {
723
737
  this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
724
738
  }
725
739
  catch (error) {
726
- if (error.message.includes('failed to allocate a buffer')) {
740
+ // Múltiples tipos de errores de memoria que pueden ocurrir
741
+ const isMemoryError = error.message.includes('failed to allocate a buffer') ||
742
+ error.message.includes('Out of memory') ||
743
+ error.message.includes('RangeError: Out of memory') ||
744
+ error.message.includes('no available backend found');
745
+ if (isMemoryError) {
727
746
  const fallbackOptions = {
728
747
  executionProviders: ['wasm'],
729
748
  graphOptimizationLevel: 'disabled',
@@ -733,8 +752,17 @@ class DetectionService {
733
752
  executionMode: 'sequential',
734
753
  interOpNumThreads: 1,
735
754
  intraOpNumThreads: 1,
755
+ // Configuraciones adicionales para dispositivos con poca memoria
756
+ enableProfiling: false,
757
+ sessionLogSeverityLevel: 4,
758
+ sessionLogVerbosityLevel: 0,
736
759
  };
737
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
760
+ try {
761
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
762
+ }
763
+ catch (fallbackError) {
764
+ throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
765
+ }
738
766
  }
739
767
  else {
740
768
  throw error;
@@ -1091,7 +1119,7 @@ class ServiceContainer {
1091
1119
  }
1092
1120
  }
1093
1121
 
1094
- const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}}";
1122
+ const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}";
1095
1123
 
1096
1124
  const JaakStamps = class {
1097
1125
  constructor(hostRef) {
@@ -1150,6 +1178,7 @@ const JaakStamps = class {
1150
1178
  performanceDegradedMode = false; // Auto-switched to manual mode due to performance
1151
1179
  showPerformanceMessage = false; // Show performance message temporarily
1152
1180
  captureStateVersion = 0; // Triggers re-render when StateManager changes
1181
+ processingButton = null; // Track which button is processing
1153
1182
  // Services
1154
1183
  serviceContainer;
1155
1184
  eventBus;
@@ -1201,14 +1230,19 @@ const JaakStamps = class {
1201
1230
  canvasPool = [];
1202
1231
  MAX_CANVAS_POOL_SIZE = 3;
1203
1232
  async componentDidLoad() {
1204
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1205
- await this.initializeServices();
1206
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1207
- await this.setupEventListeners();
1208
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1209
- await this.initializeComponent();
1210
- if (this.debug) {
1211
- this.initializePerformanceMonitor();
1233
+ try {
1234
+ this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1235
+ await this.initializeServices();
1236
+ this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1237
+ await this.setupEventListeners();
1238
+ this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1239
+ await this.initializeComponent();
1240
+ if (this.debug) {
1241
+ this.initializePerformanceMonitor();
1242
+ }
1243
+ }
1244
+ catch (error) {
1245
+ this.updateStatus('Error de inicialización', error.message, 'error');
1212
1246
  }
1213
1247
  }
1214
1248
  async initializeServices() {
@@ -1523,8 +1557,14 @@ const JaakStamps = class {
1523
1557
  }
1524
1558
  }
1525
1559
  async skipBackCapture() {
1560
+ console.log('🟡 Botón clicked: Saltar reverso');
1561
+ // Set processing state immediately
1562
+ this.processingButton = 'skip-back';
1563
+ // Add small delay to show spinner
1564
+ await new Promise(resolve => setTimeout(resolve, 100));
1526
1565
  const readyCheck = this.isComponentReady();
1527
1566
  if (!readyCheck.ready) {
1567
+ this.processingButton = null;
1528
1568
  return { success: false, error: readyCheck.message };
1529
1569
  }
1530
1570
  try {
@@ -1541,6 +1581,10 @@ const JaakStamps = class {
1541
1581
  catch (error) {
1542
1582
  return { success: false, error: error.message };
1543
1583
  }
1584
+ finally {
1585
+ // Always clear processing state when done
1586
+ this.processingButton = null;
1587
+ }
1544
1588
  }
1545
1589
  async getStatus() {
1546
1590
  const readyCheck = this.isComponentReady();
@@ -1718,9 +1762,53 @@ const JaakStamps = class {
1718
1762
  // Always allow getting capture delay, even during initialization
1719
1763
  return this.captureDelay;
1720
1764
  }
1765
+ // Memory and device capability detection
1766
+ checkDeviceCapabilities() {
1767
+ const deviceMemory = navigator.deviceMemory;
1768
+ const hardwareConcurrency = navigator.hardwareConcurrency || 1;
1769
+ const isLowEndDevice = hardwareConcurrency <= 2;
1770
+ const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1771
+ const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
1772
+ // Si tenemos info de memoria del dispositivo
1773
+ if (deviceMemory && deviceMemory < 4) {
1774
+ return {
1775
+ canUseML: false,
1776
+ reason: `Memoria del dispositivo (${deviceMemory}GB) insuficiente para detección automática. Modo manual activado para mejor rendimiento.`,
1777
+ memoryMB: deviceMemory * 1024
1778
+ };
1779
+ }
1780
+ // Dispositivos móviles de gama baja
1781
+ if (isMobile && isLowEndDevice) {
1782
+ return {
1783
+ canUseML: false,
1784
+ reason: 'Dispositivo móvil optimizado detectado. Modo manual activado para mejor rendimiento y duración de batería.',
1785
+ memoryMB: null
1786
+ };
1787
+ }
1788
+ // Safari tiene limitaciones conocidas con WASM
1789
+ if (isSafari) {
1790
+ return {
1791
+ canUseML: false,
1792
+ reason: 'Safari detectado. Modo manual activado por compatibilidad optimizada con este navegador.',
1793
+ memoryMB: null
1794
+ };
1795
+ }
1796
+ return {
1797
+ canUseML: true,
1798
+ reason: 'Dispositivo compatible con detección automática.',
1799
+ memoryMB: deviceMemory ? deviceMemory * 1024 : null
1800
+ };
1801
+ }
1721
1802
  // DETECTION METHODS
1722
1803
  async startDetection() {
1723
1804
  try {
1805
+ // Verificar capacidades del dispositivo antes de cargar modelos
1806
+ const capabilities = this.checkDeviceCapabilities();
1807
+ // Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
1808
+ if (!capabilities.canUseML && this.useDocumentDetector) {
1809
+ this.switchToManualModeWithWarning(capabilities.reason);
1810
+ return;
1811
+ }
1724
1812
  // Paso 1: Verificar y cargar modelos si es necesario
1725
1813
  if (!this.detectionService.isModelLoaded()) {
1726
1814
  const loadStartTime = performance.now();
@@ -1730,16 +1818,32 @@ const JaakStamps = class {
1730
1818
  await this.detectionService.loadModel();
1731
1819
  }
1732
1820
  catch (modelError) {
1733
- this.switchToManualModeWithError('Error al cargar modelo de detección');
1734
- throw modelError;
1821
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
1822
+ if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
1823
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
1824
+ this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
1825
+ return;
1826
+ }
1827
+ else {
1828
+ this.switchToManualModeWithError('Error al cargar modelo de detección');
1829
+ throw modelError;
1830
+ }
1735
1831
  }
1736
1832
  this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1737
1833
  try {
1738
1834
  await this.detectionService.loadClassificationModel();
1739
1835
  }
1740
1836
  catch (classificationError) {
1741
- this.switchToManualModeWithError('Error al cargar modelo de clasificación');
1742
- throw classificationError;
1837
+ // Si es un error de memoria, cambiar a modo manual con mensaje específico
1838
+ if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
1839
+ const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
1840
+ this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
1841
+ return;
1842
+ }
1843
+ else {
1844
+ this.switchToManualModeWithError('Error al cargar modelo de clasificación');
1845
+ throw classificationError;
1846
+ }
1743
1847
  }
1744
1848
  const loadEndTime = performance.now();
1745
1849
  const loadTime = loadEndTime - loadStartTime;
@@ -1750,11 +1854,27 @@ const JaakStamps = class {
1750
1854
  }
1751
1855
  // Paso 2: Detectar y configurar dispositivos
1752
1856
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1753
- await this.cameraService.enumerateDevices();
1754
- await this.updateCameraInfoWithAutofocus();
1857
+ try {
1858
+ await this.cameraService.enumerateDevices();
1859
+ }
1860
+ catch (enumerateError) {
1861
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
1862
+ }
1863
+ try {
1864
+ await this.updateCameraInfoWithAutofocus();
1865
+ }
1866
+ catch (cameraInfoError) {
1867
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
1868
+ }
1755
1869
  // Paso 3: Configurar cámara seleccionada
1756
1870
  this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1757
- const stream = await this.cameraService.setupCamera();
1871
+ let stream;
1872
+ try {
1873
+ stream = await this.cameraService.setupCamera();
1874
+ }
1875
+ catch (setupError) {
1876
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
1877
+ }
1758
1878
  // Paso 4: Inicializar video y mostrar estado de análisis inicial
1759
1879
  if (this.useDocumentDetector) {
1760
1880
  this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
@@ -1762,7 +1882,12 @@ const JaakStamps = class {
1762
1882
  else {
1763
1883
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
1764
1884
  }
1765
- await this.initializeVideoStream(stream);
1885
+ try {
1886
+ await this.initializeVideoStream(stream);
1887
+ }
1888
+ catch (videoError) {
1889
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
1890
+ }
1766
1891
  // Paso 5: Calibrar máscara
1767
1892
  if (this.detectionContainer) {
1768
1893
  const container = this.detectionContainer.parentElement;
@@ -1986,7 +2111,7 @@ const JaakStamps = class {
1986
2111
  isCapturing: false
1987
2112
  };
1988
2113
  const cameraInfo = this.cameraInfoWithAutofocus;
1989
- return (index.h("div", { key: '42958507f3463e8506bdd240b3f478f6e3bf1746', class: "detector-container" }, index.h("div", { key: '775a9c1df2075236c0b0bfe4a39eace38d099b09', class: "video-container" }, index.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' } }), index.h("div", { key: 'e7fee77cba28b3e4149319ce7998b06af5c4075c', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
2114
+ return (index.h("div", { key: '51cc6d0f873691852f938940b131732252b970c1', class: "detector-container" }, index.h("div", { key: '009480499c0a2833bf9f6bd28003142ca3dd3750', class: "video-container" }, index.h("video", { key: '88a99a1a90c8d817e40549a255324b2da342797f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '7ede15ccd6f67b3081d907777de7b0d1a1b4cfd6', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
1990
2115
  position: 'absolute',
1991
2116
  left: `${box.x}px`,
1992
2117
  top: `${box.y}px`,
@@ -1995,9 +2120,9 @@ const JaakStamps = class {
1995
2120
  border: '2px solid #32406C',
1996
2121
  pointerEvents: 'none',
1997
2122
  boxSizing: 'border-box'
1998
- } })))), this.isMaskReady && (index.h("div", { key: '3d7ab5706479922c4321953a18c0026f4be2ba07', class: "overlay-mask" }, index.h("div", { key: '410d98aa7ed52604760e12a1d8c8dc5a94a4e0d8', class: "card-outline" }, index.h("div", { key: 'b51b8b7df3ef5c5d3a9bfa43d704d06aef14b2cf', class: "side side-top" }), index.h("div", { key: 'e2c8f24094d2a9356248234c8a7a2d8aca07f3e6', class: "side side-right" }), index.h("div", { key: 'e4f92a79ac4c8af8d1e51e03d743b4814ffd2c00', class: "side side-bottom" }), index.h("div", { key: '400c294159a44f6211eb84a4e457b1406f541f44', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '69c4ed1c4d5bd21bdaf0795a49cf3dd9a59502f4', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '92164199e91c9cbf40a94a87b0457e76c566e252', class: "back-capture-section" }, index.h("div", { key: '22ad40233923be0909529927779f09a6917895ee', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: '3c65c3bd5687d709a5f4d1c9fe2293cfd320f269', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Reverso')), index.h("button", { key: 'ad6cb7eb1ce70af2fb3d5a8d031801a850605baa', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
2123
+ } })))), this.isMaskReady && (index.h("div", { key: '63beb4330ab171a5e63237b1d83dcace61422018', class: "overlay-mask" }, index.h("div", { key: '93cfac6898de6ece6c7dd8f02ba2b79ce6591755', class: "card-outline" }, index.h("div", { key: '3087d3c51f42ea8d720cea4170dcb68e8bb385e1', class: "side side-top" }), index.h("div", { key: '992e311b895da161d3b5c49dde346ae378139641', class: "side side-right" }), index.h("div", { key: '5fb3d7b704f08177b2b9c6e4fbaf685f251a3e53', class: "side side-bottom" }), index.h("div", { key: '76f6e2cb856632c58809ccdabdf3f7d9a588c9da', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '142dfc187e07100278e16b19018e4b5fb96d964a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'bba078ce4c9585631b6a8154f878464b1f779c8b', class: "back-capture-section" }, index.h("div", { key: 'ec94c9ba2cb373a539f0e6cc3eb356294cebf3ea', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: '30561e1fc88090c6bb4d0cd7909a33a9525b0ac2', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '4b3e3356507c7f654705425e201d0303d727db72', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: '1abfada41312658eab4966d8fcdb711a6c4762e6', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: '3cab3ce751cb38174b6d061170a66ac654a90a38', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1999
2124
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
2000
- : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: '9b87c7e4f5ab646ec35e5f81833a6c9e9ac5d5c8', class: "camera-controls" }, index.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 ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: 'b4f5c63464085c135dda068c929b147cfd84238f', class: "camera-selector-dropdown" }, index.h("div", { key: '06072cad9c132f1926b4070c0c3d714b76cc6636', class: "camera-selector-header" }, index.h("span", { key: '85a813b383f6cf1fd505239781d8a5ea60cbae58' }, "Seleccionar C\u00E1mara"), index.h("button", { key: 'c1588cea255bcf7151ca2e9d0583d149122784d7', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '62864eaea7aac898d27d8995fcede3788383918f', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '1288fb31a8f3290bf5a280390c52af5b1dce75b5', class: "device-info" }, index.h("small", { key: 'efdf5a337cd07fc14112d3bd883298cb586f67f5' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '6b18401230001a923e139f3f2a704ac44b8f52f4', class: "manual-capture-section" }, index.h("button", { key: '8269121d4822b8d7cb01b966d3b701cc297b8856', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: captureState.isCapturing }, captureState.isCapturing ? 'Capturando...' : 'Capturar Frente'))))), captureState.isCapturing && (index.h("div", { key: '8920362bb45d0f024149440a914a2144e16783b0', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '644baeaf145477d31439e7f28a5c262064f4db95', class: "flip-animation" }, index.h("div", { key: '467c703d5ac600f12f7456e2c51bf0d1ec94a638', class: "id-card-icon" }), index.h("div", { key: '8f2cef3855202d959a6e7884f1d74d78e3ef66ac', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '651898dc58e10886d0b4696f2036af7bf68640ef', class: "success-animation" }, index.h("div", { key: '7b3e1626aa02131af0f860dc5ea8c8f0e4d6655b', class: "check-icon" }), index.h("div", { key: '571dcad0d407c275dd4ceaa04d2de4bbed7a9181', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: 'f2efae53af3ca6a38735f7fb06708101a811e1fe', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '6f9268bfcc01b305c2d415b2bbbd3197b228d487', class: "status-spinner" })), index.h("div", { key: '17f0f0c8c4debb57408d1bed95dd8a6c180affb5', class: "status-content" }, index.h("div", { key: '6af3696c54d05bea334c24fe7713e658ef1c9d3e', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '99d8fe46dd0c4e3f779c8d3a7fc6314b551f8424', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '4b25e47a42a9f20713167c0f89f8adb07090a6e5', class: "performance-monitor" }, index.h("div", { key: 'b323c4cb466bfe50d105ea165d361b4240e97cbf', class: "performance-expanded" }, index.h("div", { key: '001d7d5cc4024162d24a9f46e2bc4b978ad0166b', class: "metrics-row" }, index.h("div", { key: 'd3f8281b92075b48f4fff22a6cbb020e98055f74', class: "metric-compact" }, index.h("span", { key: 'c140d8d5596ed3a3f176daf3e7a74bc12ef9fa56', class: "metric-label" }, "FPS"), index.h("span", { key: 'f88d7a211bc516757ef6842c5a9acb4bb9c3ff9b', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '6f4b8aac4f1f1f086d0038b42ddd12b4bdff15ea', class: "metric-compact" }, index.h("span", { key: '8a222b05e739012a67f98f73bbba0ba042420b08', class: "metric-label" }, "MEM"), index.h("span", { key: '1a2f785d57ba926900eca868b890c677ce0adafc', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'e2dc13638b3cffa1245ed6b1485d85982215a23d', class: "metrics-row" }, index.h("div", { key: '3356b3dc77e2c2adc33f0483b46958495ea963a6', class: "metric-compact" }, index.h("span", { key: '9d6a01153605f15dd13a46fc6de7e43bcec3c4d7', class: "metric-label" }, "INF"), index.h("span", { key: '6db223f93179706ab9f9d19807247f69dc2555bf', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '920f5f8a7d98d4c30644584ee4177b7f0b9d4ce6', class: "metric-compact" }, index.h("span", { key: 'e27e9a6410f9e9ccb789500e23a0a6140d5cc56e', class: "metric-label" }, "FRAME"), index.h("span", { key: 'e2229a1d7c05c6503a6aaf85951dfbdb93d8922f', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '33bc285a3ece3bc0887249fc158f63a34fbd84fd', class: "metrics-row" }, index.h("div", { key: '9904884ea06b18561e6996fc996c8522f01138b8', class: "metric-compact" }, index.h("span", { key: 'df4bc26fe4af5e1203cbc1c82d104ac1c64a0d31', class: "metric-label" }, "DET"), index.h("span", { key: '760931f01f32761f80bedb579652018cedeb6edb', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '9f1e5b50979e54fdb58ade2df5e1a990b8ddd1a3', class: "metric-compact" }, index.h("span", { key: '698e460b3b7b13747cdc27743348c580eab337ad', class: "metric-label" }, "RATE"), index.h("span", { key: '466b3b790ce6d3c680bb7629592b67e57be3cee5', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '2c093210e7b929967b690c60cfc0943274db2409', class: "watermark" }, index.h("img", { key: '9e30dc6b9cd294e5dc3059ea98c61a45a50c4255', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2125
+ : 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'b773fe579cc039fd90ff5e61976752bcf631d16e', class: "camera-controls" }, index.h("button", { key: '8535463b13cc02e0030d14c1d57390e560572b4f', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: 'c4b159cf28c24ba2ec0a658bf6f1296c6aba47e3', class: "camera-selector-dropdown" }, index.h("div", { key: '033273241b77cedc414dee00b70926c3a659910a', class: "camera-selector-header" }, index.h("span", { key: '83fb4c7ae2387b85134a3ca0c5a156beaf73e481' }, "Seleccionar C\u00E1mara"), index.h("button", { key: 'f65b6e994603e616af8d69df198c3d8471125695', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'fd90d4c4db1e910c5d05b9ce19a252967c71ad1f', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: '49faa944b3c0c052e18f48e9dda2758cd2c0ae6b', class: "device-info" }, index.h("small", { key: '15e95207aef60a78b2912a6c00a1b61ab760b72d' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: 'a67a3795837adcd307fa3666c338376cecae16d0', class: "manual-capture-section" }, index.h("button", { key: '7f21ccd9b622d97bfce80962038de8be7cd6e33d', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: 'a4b5e865f373cc0e7787098eedd2d4df413d310f', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: '0b02e81963136dfda62a8980bc5a1f5662509fb8', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '5eb16e12a7ce16720ca7bdd11aefd902ba772e24', class: "flip-animation" }, index.h("div", { key: '4c6f3ed33c5080e9e18d17c4594cf394250c3517', class: "id-card-icon" }), index.h("div", { key: 'd848d7a98540988a8f491bcf8f6f6ad9c5dccfd4', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '94863e6d0c404188d548db428af918274d882652', class: "success-animation" }, index.h("div", { key: 'a90b777e7b7eb533fabec2c15f79a4bd18778f0b', class: "check-icon" }), index.h("div", { key: '2152e5a3756f58f996c520b58cb1b6cca1763e94', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: 'ae18e06bf291429a74d1b6c8a0c4d37d473f7b30', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: 'b6b03ed285868d7d3350a2a73e59cb1d65a47401', class: "status-spinner" })), index.h("div", { key: 'dc85b49dd1cfcddf7300fb3f327d2e1de8ab9bff', class: "status-content" }, index.h("div", { key: '82461ed0c6d70a0c95ae6b7144ff1d8b8371c6fc', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: '24aa77ec92ca5a48d1bed4cf98ae591bdbff74f0', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '3b56a0b305ca07407c09a73624a4040cf6625b56', class: "performance-monitor" }, index.h("div", { key: '52f975da1502f6b08a0b2c828bfd0be8d10e36f8', class: "performance-expanded" }, index.h("div", { key: 'ea2f444255add0415c7f88cdea2733e1244b17bd', class: "metrics-row" }, index.h("div", { key: 'e3c2370f1a900594940cb321d5b1661690018025', class: "metric-compact" }, index.h("span", { key: '25c2f402c9b69f21736c494420e7a396673e9893', class: "metric-label" }, "FPS"), index.h("span", { key: '505de593f7927544cd874332127e84c72131da72', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '5cb1ff4bc1ac33988404e59dac3d01d5a55893b9', class: "metric-compact" }, index.h("span", { key: 'ce701538abbfe797b288520ab4c277bf7a1d6a05', class: "metric-label" }, "MEM"), index.h("span", { key: 'efc383e993ad72c20cbfc630e20b2bf9a617a02d', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'b6b6b832740c44d4f78cddf99ba1d139dc3d4739', class: "metrics-row" }, index.h("div", { key: '3a8903a05aed8adb1626f93bee6ba258fbea9a7d', class: "metric-compact" }, index.h("span", { key: 'aabc56057156217b3be771ab3b1e92590fa886c5', class: "metric-label" }, "INF"), index.h("span", { key: '1080df34860cc891cda94c5aacc754cbde18a138', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: 'ac152093d4c0cc25862aac638fc4693dd0c51a69', class: "metric-compact" }, index.h("span", { key: 'b23712dbc0b9b8e9cb6e2c659e5af3844ec0ebe7', class: "metric-label" }, "FRAME"), index.h("span", { key: '461f88519b339fbad6444f009312f72efbc6e450', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '8f99afe2c94f37fd3a6feb082cc9aa96f54fc5e1', class: "metrics-row" }, index.h("div", { key: 'da7ac0fd842f38bbdcac01550764f9fe1bd666a2', class: "metric-compact" }, index.h("span", { key: 'a5dcd9c8753ba42038f869efa50ae9602cdf6256', class: "metric-label" }, "DET"), index.h("span", { key: 'e98b91ea98a41637baea1fcfda0bb590cf5d24a0', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: '3ac54161a712d305a098eae0a3b5d21a731c6019', class: "metric-compact" }, index.h("span", { key: '3c8cc465433be3b490887457a6c3967dbbda6a7d', class: "metric-label" }, "RATE"), index.h("span", { key: 'f2e083118fe107c7dc2a675e3f661466cea6d479', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '10b6c32b0460e194d680c63b30a078c51a26e9c9', class: "watermark" }, index.h("img", { key: 'f5954a0ba3f90a642e0d5c6c14d5b549cd193018', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2001
2126
  }
2002
2127
  // Utility methods
2003
2128
  updateDetectionBoxes(boxes) {
@@ -2113,9 +2238,24 @@ const JaakStamps = class {
2113
2238
  }
2114
2239
  }
2115
2240
  async takeManualScreenshot() {
2116
- if (!this.videoRef)
2241
+ console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
2242
+ // Set processing state immediately
2243
+ const captureState = this.stateManager?.getCaptureState();
2244
+ if (captureState?.step === 'front') {
2245
+ this.processingButton = 'capture-front';
2246
+ }
2247
+ else if (captureState?.step === 'back') {
2248
+ this.processingButton = 'capture-back';
2249
+ }
2250
+ console.log('🔵 processingButton set to:', this.processingButton);
2251
+ // Add small delay to show spinner
2252
+ await new Promise(resolve => setTimeout(resolve, 100));
2253
+ if (!this.videoRef) {
2254
+ this.processingButton = null;
2117
2255
  return;
2256
+ }
2118
2257
  // When using manual capture, use mask coordinates for cropping
2258
+ // Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
2119
2259
  await this.takeScreenshotWithMaskCoordinates();
2120
2260
  }
2121
2261
  async takeScreenshotWithMaskCoordinates() {
@@ -2191,6 +2331,8 @@ const JaakStamps = class {
2191
2331
  this.completeProcess(true);
2192
2332
  this.returnCanvasToPool(captureCanvas);
2193
2333
  this.returnCanvasToPool(croppedCanvas);
2334
+ // Clear processing button when passport is detected
2335
+ this.processingButton = null;
2194
2336
  return;
2195
2337
  }
2196
2338
  }
@@ -2211,6 +2353,8 @@ const JaakStamps = class {
2211
2353
  });
2212
2354
  this.triggerRerender();
2213
2355
  this.startBackDocumentTimer();
2356
+ // Clear processing button after animation completes
2357
+ this.processingButton = null;
2214
2358
  }, 3000);
2215
2359
  }
2216
2360
  else if (captureState.step === 'back') {
@@ -2221,6 +2365,8 @@ const JaakStamps = class {
2221
2365
  }
2222
2366
  });
2223
2367
  this.completeProcess(false);
2368
+ // Clear processing button after back capture completes
2369
+ this.processingButton = null;
2224
2370
  }
2225
2371
  // Return canvases to pool after use
2226
2372
  this.returnCanvasToPool(captureCanvas);
@@ -2314,6 +2460,9 @@ const JaakStamps = class {
2314
2460
  this.stopPerformanceMonitoring();
2315
2461
  // Liberar recursos de ONNX
2316
2462
  this.releaseOnnxResources();
2463
+ // Resetear máscara a blanco y limpiar detecciones
2464
+ this.resetMaskToWhite();
2465
+ this.detectionBoxes = [];
2317
2466
  this.hasAutoSwitchedToManual = true;
2318
2467
  this.performanceDegradedMode = true;
2319
2468
  this.showManualCaptureButton = true;
@@ -2324,6 +2473,75 @@ const JaakStamps = class {
2324
2473
  }, 5000);
2325
2474
  }
2326
2475
  // Método para cambiar a modo manual por errores de ONNX
2476
+ switchToManualModeWithWarning(warningMessage) {
2477
+ // Solo cambiar si el detector está habilitado y no se ha cambiado ya
2478
+ if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
2479
+ return;
2480
+ }
2481
+ // No necesitamos liberar recursos ONNX porque no se han cargado aún
2482
+ // Resetear máscara a blanco y limpiar detecciones
2483
+ this.resetMaskToWhite();
2484
+ this.detectionBoxes = [];
2485
+ // Cambiar a modo manual
2486
+ this.hasAutoSwitchedToManual = true;
2487
+ this.performanceDegradedMode = true;
2488
+ this.showManualCaptureButton = true;
2489
+ this.useDocumentDetector = false; // Desactivar detector automático
2490
+ // Actualizar el estado con mensaje informativo (no error)
2491
+ this.updateStatus('Modo manual activado', warningMessage, 'active');
2492
+ // Continuar con la configuración de cámara
2493
+ this.continueWithCameraSetup();
2494
+ }
2495
+ async continueWithCameraSetup() {
2496
+ try {
2497
+ // Paso 2: Detectar y configurar dispositivos
2498
+ this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
2499
+ try {
2500
+ await this.cameraService.enumerateDevices();
2501
+ }
2502
+ catch (enumerateError) {
2503
+ throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
2504
+ }
2505
+ try {
2506
+ await this.updateCameraInfoWithAutofocus();
2507
+ }
2508
+ catch (cameraInfoError) {
2509
+ throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
2510
+ }
2511
+ // Paso 3: Configurar cámara seleccionada
2512
+ this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
2513
+ let stream;
2514
+ try {
2515
+ stream = await this.cameraService.setupCamera();
2516
+ }
2517
+ catch (setupError) {
2518
+ throw new Error(`Error al configurar cámara: ${setupError.message}`);
2519
+ }
2520
+ // Paso 4: Inicializar video
2521
+ this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
2522
+ try {
2523
+ await this.initializeVideoStream(stream);
2524
+ }
2525
+ catch (videoError) {
2526
+ throw new Error(`Error al inicializar video: ${videoError.message}`);
2527
+ }
2528
+ // Paso 5: Calibrar máscara
2529
+ if (this.detectionContainer) {
2530
+ const container = this.detectionContainer.parentElement;
2531
+ const rect = container.getBoundingClientRect();
2532
+ this.updateMaskDimensions(rect);
2533
+ }
2534
+ // Finalizar
2535
+ this.startTime = Date.now();
2536
+ this.stateManager.updateCaptureState({ isLoading: false });
2537
+ // Mostrar botón manual (ya debe estar habilitado)
2538
+ this.showManualCaptureButton = true;
2539
+ }
2540
+ catch (error) {
2541
+ this.updateStatus('Error de cámara', error.message, 'error');
2542
+ throw error;
2543
+ }
2544
+ }
2327
2545
  switchToManualModeWithError(errorMessage) {
2328
2546
  // Solo cambiar si el detector está habilitado y no se ha cambiado ya
2329
2547
  if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
@@ -2331,6 +2549,9 @@ const JaakStamps = class {
2331
2549
  }
2332
2550
  // Liberar recursos de ONNX inmediatamente
2333
2551
  this.releaseOnnxResources();
2552
+ // Resetear máscara a blanco y limpiar detecciones
2553
+ this.resetMaskToWhite();
2554
+ this.detectionBoxes = [];
2334
2555
  // Cambiar a modo manual
2335
2556
  this.hasAutoSwitchedToManual = true;
2336
2557
  this.performanceDegradedMode = true;
@@ -2581,6 +2802,24 @@ const JaakStamps = class {
2581
2802
  // Performance is good, use base frame skip
2582
2803
  return this.BASE_FRAME_SKIP;
2583
2804
  }
2805
+ // Método para resetear la máscara a color blanco
2806
+ resetMaskToWhite() {
2807
+ const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
2808
+ const corners = this.el.shadowRoot?.querySelectorAll('.corner');
2809
+ const topSide = this.el.shadowRoot?.querySelector('.side-top');
2810
+ const rightSide = this.el.shadowRoot?.querySelector('.side-right');
2811
+ const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
2812
+ const leftSide = this.el.shadowRoot?.querySelector('.side-left');
2813
+ // Remover todas las clases de alineación y estado perfecto
2814
+ cardOutline?.classList.remove('perfect-match');
2815
+ corners?.forEach(corner => corner.classList.remove('perfect-match'));
2816
+ topSide?.classList.remove('aligned');
2817
+ rightSide?.classList.remove('aligned');
2818
+ bottomSide?.classList.remove('aligned');
2819
+ leftSide?.classList.remove('aligned');
2820
+ // Resetear estado de alineación
2821
+ this.sideAlignment = { top: false, right: false, bottom: false, left: false };
2822
+ }
2584
2823
  // Canvas pool management for screenshots
2585
2824
  getPooledCanvas(width, height) {
2586
2825
  // Try to find a canvas with matching dimensions