@jaak.ai/stamps 2.0.0-dev.31 → 2.0.0-dev.32

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.
@@ -818,11 +818,17 @@ class DetectionService {
818
818
  }
819
819
  // Tolerancia para considerar que un lado está alineado (en píxeles)
820
820
  const tolerance = alignmentTolerance;
821
+ // Verificar alineación de esquinas más estricta
822
+ // Una esquina está alineada solo si AMBOS bordes que la forman están alineados
823
+ const topAligned = Math.abs(docTop - maskTop) <= tolerance;
824
+ const rightAligned = Math.abs(docRight - maskRight) <= tolerance;
825
+ const bottomAligned = Math.abs(docBottom - maskBottom) <= tolerance;
826
+ const leftAligned = Math.abs(docLeft - maskLeft) <= tolerance;
821
827
  return {
822
- top: Math.abs(docTop - maskTop) <= tolerance,
823
- right: Math.abs(docRight - maskRight) <= tolerance,
824
- bottom: Math.abs(docBottom - maskBottom) <= tolerance,
825
- left: Math.abs(docLeft - maskLeft) <= tolerance
828
+ top: topAligned && leftAligned, // Esquina superior izquierda: borde top Y left alineados
829
+ right: topAligned && rightAligned, // Esquina superior derecha: borde top Y right alineados
830
+ bottom: bottomAligned && leftAligned, // Esquina inferior izquierda: borde bottom Y left alineados
831
+ left: bottomAligned && rightAligned // Esquina inferior derecha: borde bottom Y right alineados
826
832
  };
827
833
  }
828
834
  isCardInFrame(box) {
@@ -974,7 +980,7 @@ class ServiceContainer {
974
980
  }
975
981
  }
976
982
 
977
- 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;box-shadow:0 0 0 9999px rgba(0, 0, 0, 0.7);opacity:0.8}.card-outline.perfect-match{box-shadow:0 0 0 9999px rgba(0, 0, 0, 0.7)}.side{position:absolute;background:#999;transition:background-color 0.3s ease}.side.aligned{background:#28a745}.side-top{top:0;left:0;width:100%;height:3px}.side-right{top:0;right:0;width:3px;height:100%}.side-bottom{bottom:0;left:0;width:100%;height:3px}.side-left{top:0;left:0;width:3px;height:100%}.corner{position:absolute;width:20px;height:20px;border:2px solid #999;z-index:12}.corner-tl{top:-10px;left:-10px;border-right:none;border-bottom:none}.corner-tr{top:-10px;right:-10px;border-left:none;border-bottom:none}.corner-bl{bottom:-10px;left:-10px;border-right:none;border-top:none}.corner-br{bottom:-10px;right:-10px;border-left:none;border-top:none}.corner.perfect-match{border-color:#28a745}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;z-index:20}.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 9999px rgba(0, 0, 0, 0.7), 0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 9999px rgba(0, 0, 0, 0.7), 0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 9999px rgba(0, 0, 0, 0.7), 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-button{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;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:translateX(-50%) translateY(0)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto}.camera-selector-button{height:40px;padding:0 16px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.6);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-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.85);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)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;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:10px 16px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:space-between;align-items:center;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:8px;font-weight:400}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9}.device-info{padding:8px 16px;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:10px 14px}.camera-option{padding:8px 14px}.device-info{padding:6px 14px}}.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;right:16px;background:rgba(26, 26, 26, 0.95);backdrop-filter:blur(8px);display:flex;align-items:center;gap:12px;padding:12px 16px;border-radius:12px;z-index:40;transition:all 0.3s ease;min-height:48px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.component-status.status-initializing{background:rgba(26, 26, 26, 0.95)}.component-status.status-loading{background:rgba(26, 26, 26, 0.95)}.component-status.status-ready{background:rgba(26, 26, 26, 0.95)}.component-status.status-error{background:rgba(26, 26, 26, 0.95)}.status-spinner{width:24px;height:24px;border:2px solid rgba(255, 255, 255, 0.2);border-top:2px solid #ffffff;border-radius:50%;animation:statusSpin 1.2s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:2px}.status-message{color:#e6edf3;font-size:14px;font-weight:600;opacity:0.95;letter-spacing:0.3px;margin:0}.status-description{color:#8b949e;font-size:12px;line-height:1.3;opacity:0.9;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;right:12px;padding:10px 14px;gap:10px;min-height:44px}.status-spinner{width:20px;height:20px}.status-message{font-size:13px}.status-description{font-size:11px}}.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)}}";
983
+ 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%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;z-index:20}.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-button{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;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:translateX(-50%) translateY(0)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto}.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)}}.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:space-between;align-items:center;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:8px;font-weight:400}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9}.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}}.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:slideInFromLeft 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}}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}";
978
984
 
979
985
  const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
980
986
  constructor() {
@@ -1001,12 +1007,24 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1001
1007
  isMaskReady = false;
1002
1008
  shouldMirrorVideo = true;
1003
1009
  showCameraSelector = false;
1010
+ isSwitchingCamera = false;
1004
1011
  currentStatus = {
1005
1012
  message: 'Inicializando componente...',
1006
1013
  description: 'Configurando servicios y cargando recursos',
1007
1014
  type: 'initializing',
1008
1015
  isInitialized: false
1009
1016
  };
1017
+ performanceData = {
1018
+ fps: 0,
1019
+ inferenceTime: 0,
1020
+ memoryUsage: 0,
1021
+ onnxLoadTime: 0,
1022
+ frameProcessingTime: 0,
1023
+ totalDetections: 0,
1024
+ successfulDetections: 0,
1025
+ detectionRate: 0
1026
+ };
1027
+ isPerformanceMonitorMinimized = false;
1010
1028
  // Services
1011
1029
  serviceContainer;
1012
1030
  logger;
@@ -1023,6 +1041,22 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1023
1041
  lastDetectedBox;
1024
1042
  startTime;
1025
1043
  hasScreenshotTaken = false;
1044
+ alignmentStartTime;
1045
+ alignmentTimer;
1046
+ // Performance monitoring
1047
+ performanceMetrics = {
1048
+ fps: 0,
1049
+ inferenceTime: 0,
1050
+ memoryUsage: 0,
1051
+ cpuUsage: 0,
1052
+ onnxLoadTime: 0,
1053
+ frameProcessingTime: 0,
1054
+ totalDetections: 0,
1055
+ successfulDetections: 0,
1056
+ detectionRate: 0,
1057
+ lastUpdateTime: 0
1058
+ };
1059
+ performanceUpdateInterval;
1026
1060
  // Performance optimization
1027
1061
  frameSkipCounter = 0;
1028
1062
  FRAME_SKIP = 2;
@@ -1037,6 +1071,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1037
1071
  await this.setupEventListeners();
1038
1072
  this.updateStatus('Inicializando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1039
1073
  await this.initializeComponent();
1074
+ if (this.debug) {
1075
+ this.initializePerformanceMonitor();
1076
+ }
1040
1077
  }
1041
1078
  async initializeServices() {
1042
1079
  const config = {
@@ -1271,17 +1308,24 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1271
1308
  return { success: true, message: 'Model already loaded' };
1272
1309
  }
1273
1310
  try {
1311
+ const loadStartTime = performance.now();
1274
1312
  this.updateStatus('Descargando modelo de detección...', 'Obteniendo red neuronal para reconocimiento de documentos', 'loading');
1275
1313
  this.stateManager.updateCaptureState({ isLoading: true });
1276
1314
  await this.detectionService.loadModel();
1277
1315
  this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de tipos de documento', 'loading');
1278
1316
  await this.detectionService.loadClassificationModel();
1317
+ const loadEndTime = performance.now();
1318
+ const loadTime = loadEndTime - loadStartTime;
1319
+ // Record ONNX load time
1320
+ if (this.debug) {
1321
+ this.recordOnnxPerformance(loadTime, 0);
1322
+ }
1279
1323
  this.updateStatus('Optimizando modelos...', 'Configurando parámetros de rendimiento', 'loading');
1280
1324
  await new Promise(resolve => setTimeout(resolve, 300));
1281
1325
  this.updateStatus('Modelos precargados', '', 'ready');
1282
1326
  this.stateManager.updateCaptureState({ isLoading: false });
1283
1327
  this.emitReadyEvent();
1284
- this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE');
1328
+ this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
1285
1329
  return { success: true, message: 'Models preloaded successfully' };
1286
1330
  }
1287
1331
  catch (error) {
@@ -1317,11 +1361,19 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1317
1361
  try {
1318
1362
  // Paso 1: Verificar y cargar modelos si es necesario
1319
1363
  if (!this.detectionService.isModelLoaded()) {
1364
+ const loadStartTime = performance.now();
1320
1365
  this.updateStatus('Cargando modelo de detección...', 'Descargando red neuronal para reconocimiento', 'loading');
1321
1366
  this.stateManager.updateCaptureState({ isLoading: true });
1322
1367
  await this.detectionService.loadModel();
1323
1368
  this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de documentos', 'loading');
1324
1369
  await this.detectionService.loadClassificationModel();
1370
+ const loadEndTime = performance.now();
1371
+ const loadTime = loadEndTime - loadStartTime;
1372
+ // Record ONNX load time
1373
+ if (this.debug) {
1374
+ this.recordOnnxPerformance(loadTime, 0);
1375
+ }
1376
+ this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
1325
1377
  }
1326
1378
  // Paso 2: Detectar y configurar dispositivos
1327
1379
  this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura disponibles', 'loading');
@@ -1373,6 +1425,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1373
1425
  }
1374
1426
  async detectFrame() {
1375
1427
  try {
1428
+ const frameStartTime = performance.now();
1376
1429
  const captureState = this.stateManager.getCaptureState();
1377
1430
  if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
1378
1431
  return;
@@ -1400,8 +1453,15 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1400
1453
  return;
1401
1454
  }
1402
1455
  this.lastInferenceTime = currentTime;
1456
+ // Measure preprocessing and inference time
1457
+ const inferenceStartTime = performance.now();
1403
1458
  const inputTensor = this.detectionService.preprocess(this.videoRef);
1404
1459
  const detections = await this.detectionService.runInference(inputTensor);
1460
+ const inferenceTime = performance.now() - inferenceStartTime;
1461
+ // Record ONNX performance metrics
1462
+ if (this.debug) {
1463
+ this.recordOnnxPerformance(0, inferenceTime);
1464
+ }
1405
1465
  // Update best score logic
1406
1466
  if (this.startTime && Date.now() - this.startTime < 5000) {
1407
1467
  detections.forEach(detection => {
@@ -1427,6 +1487,12 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1427
1487
  this.detectionBoxes = [];
1428
1488
  }
1429
1489
  this.updateMaskColor(detections);
1490
+ // Record frame processing metrics
1491
+ if (this.debug) {
1492
+ const frameEndTime = performance.now();
1493
+ const totalFrameTime = frameEndTime - frameStartTime;
1494
+ this.recordFrameProcessing(totalFrameTime, detections.length);
1495
+ }
1430
1496
  // Continue detection loop
1431
1497
  if (captureState.step !== 'completed') {
1432
1498
  if (this.consecutiveFailures > this.MAX_FAILURES) {
@@ -1457,7 +1523,16 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1457
1523
  if (this.videoStream) {
1458
1524
  this.videoStream.getTracks().forEach(track => track.stop());
1459
1525
  }
1526
+ if (this.alignmentTimer) {
1527
+ clearTimeout(this.alignmentTimer);
1528
+ this.alignmentTimer = undefined;
1529
+ }
1530
+ if (this.performanceUpdateInterval) {
1531
+ clearInterval(this.performanceUpdateInterval);
1532
+ this.performanceUpdateInterval = undefined;
1533
+ }
1460
1534
  this.detectionBoxes = [];
1535
+ this.alignmentStartTime = undefined;
1461
1536
  this.serviceContainer?.cleanup();
1462
1537
  }
1463
1538
  render() {
@@ -1472,7 +1547,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1472
1547
  availableCameras: [],
1473
1548
  selectedCameraId: null,
1474
1549
  deviceType: 'desktop'};
1475
- return (h("div", { key: '3e5555392fdc2abb3f5063110ac8896501385934', class: "detector-container" }, h("div", { key: '7b4f44477ec8eb1b89088182b32ae5bdccec3077', class: "video-container" }, h("video", { key: '2dedc3a7489bd6016d9b04df970de191e543aed8', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'ac6eb89a2bea40ecd043f4709e92acbe7c3ddaba', 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: {
1550
+ return (h("div", { key: '6d83055c7dcfc6c6f77e99d8439866aaca31f323', class: "detector-container" }, h("div", { key: '89d95da0fbee2bf03a455da911103d13f2d55a64', class: "video-container" }, h("video", { key: 'a06b3df2a0c2de21c4c3113ca5357a0518168214', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'a11ae04dd825208d069a02368708811a718a5b68', 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: {
1476
1551
  position: 'absolute',
1477
1552
  left: `${box.x}px`,
1478
1553
  top: `${box.y}px`,
@@ -1481,10 +1556,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1481
1556
  border: '2px solid #32406C',
1482
1557
  pointerEvents: 'none',
1483
1558
  boxSizing: 'border-box'
1484
- } })))), this.isMaskReady && (h("div", { key: '6b7c6b605eb0d856b7ca8596ec1a50dc900efbdb', class: "overlay-mask" }, h("div", { key: '31575da3b5c97bb7ed58a181ba6b4b0bb52c7554', class: "card-outline" }, h("div", { key: '624f1ca7887a8ed23b511cdaef41879dc11479b8', class: "side side-top" }), h("div", { key: '277299b2b7b9e20f3323752655e2eb28f1a5963c', class: "side side-right" }), h("div", { key: 'a911171b451dc48796b97d0a6fa2590cb8705364', class: "side side-bottom" }), h("div", { key: 'a24c041a52ad23368dd7f44378c14a556e10b120', class: "side side-left" }), h("div", { key: '5bd74218e3f93b55e7fd94b573e0f5666c587fcd', class: "corner corner-tl" }), h("div", { key: 'ea5f10f14adc38fb642749d0505ae11a23b9ad93', class: "corner corner-tr" }), h("div", { key: 'ce51a50c570fffe352cea0266a967c18a0c56deb', class: "corner corner-bl" }), h("div", { key: '36c2dc3833f5e776803466de9feb123716d31728', class: "corner corner-br" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '8b9227c89cc922dda92b6e176413d78c43188ebe', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: 'f612542ab94482a4f499d1cea303f033d58b20db', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: '82c988e8238a20e5a6290c0f747d392a2bd50068', class: "camera-controls" }, h("button", { key: '5e761bec2fe0f8c466be65ade262d24ec43e564a', class: "camera-selector-button", onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara" }, "C\u00E1maras"))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '450476b15866b2facb24d36da2dbc775aee2ad4c', class: "camera-selector-dropdown" }, h("div", { key: '5c1642572b67215e322d0826ee7e28f2f12b21fe', class: "camera-selector-header" }, h("span", { key: 'e98b5cb4375a09776c544b803d59ff3109569d3e' }, "Seleccionar C\u00E1mara"), h("button", { key: '317190dc57cbaa60c4d299cc940bb8a6379f514d', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '94ecfe4c4e6b11dae84e9e71282dffee14e1f85b', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => {
1485
- this.cameraService.switchCamera(camera.id);
1486
- this.toggleCameraSelector();
1487
- }, type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'afd0e161367e9dcf9105bef6fa3510a8fa25d221', class: "device-info" }, h("small", { key: 'fd57d4bfaba32b77d441d163491655468b0263d2' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: 'dcb69cad03369a87e2834e1cd04cbab5dc052866', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '899ef764d10497b023140b296c3fc72a7e762607', class: "flip-animation" }, h("div", { key: 'ec7449b92e28dcbd40d956e435c3a0ebc65d16ff', class: "id-card-icon" }), h("div", { key: 'f086a238ecc3b3fa91fc1010380676e9b3bc7429', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '4d6f0030db7a14c403b4b6fd00714aa04fd5abed', class: "success-animation" }, h("div", { key: 'c08babe375bbc94c9403ef264739016bbfac13ae', class: "check-icon" }), h("div", { key: '24393d75b67dd27da6520dd2b7a223dc9dfa73e4', class: "success-text" }, "\u00A1Proceso completado!"))), this.currentStatus.type !== 'active' && (h("div", { key: '20820f96090471728b9ed654e0b92f0a503e2052', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '0709abb333b47085e4c48ff162e58226e2204978', class: "status-spinner" })), h("div", { key: '5eb17314e376a5e0db32a6f00023d2f502c55fe4', class: "status-content" }, h("div", { key: 'fd98ef9b6889de7ca5cce0befd05285d7247795c', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '3aa93673546a06137f407aafd2933bf09d6814b0', class: "status-description" }, this.currentStatus.description))))), h("div", { key: '2247b9245bc8ecf265cb03c6c518583ae49a3e6d', class: "watermark" }, h("img", { key: '7a50ec51fe6c08a6d7907f5d4bda85b3d9a5b48b', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1559
+ } })))), this.isMaskReady && (h("div", { key: '58c0a0bdd8088835c677c9b455e01ffd8a28bcc0', class: "overlay-mask" }, h("div", { key: '52f87e8837f82a4ab6fd75bd22f8c178ff5670c4', class: "card-outline" }, h("div", { key: '0f503ec7ab610342e01d2b976fff0a5ff1b01845', class: "side side-top" }), h("div", { key: '305c980c5d638270c348ce2fc7bf762515557ea1', class: "side side-right" }), h("div", { key: '201531d41aa8fab1782bc3045bda313bfac14929', class: "side side-bottom" }), h("div", { key: 'acaa082133217e2b4aa807571d6f7f8e5f225530', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '17246f4264074dfae7fdef963120a2d3756b0f64', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: '9f21b022b3601f2b8f9898f72683e33b86f433a6', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: '3c9d8e3b2135dd14414463a6ada636b425385583', class: "camera-controls" }, h("button", { key: 'b287e1dab5a98f2eb6e05e924b1af9bb6616f947', 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: 'd8dca3049cbdf827d03cd432ac183a1651f77ab6', class: "camera-selector-dropdown" }, h("div", { key: '3a4200b5624f5d96de4ae8acb821a47d31a34c93', class: "camera-selector-header" }, h("span", { key: '14f8e8618d9404cdfd3f9cc150e54b510507c977' }, "Seleccionar C\u00E1mara"), h("button", { key: '98367e182d227093742e0fc9118f81db32352bdd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '172471ef3c0d55cb619a061eafe37205c3a0bee4', 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}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'e08ced0c02b5662be31c60b2ba35c83fac46bb80', class: "device-info" }, h("small", { key: '8489f10c13a0a528abc878cc66d71dc769890b90' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '89f6c5a9bc1efb9bd8af190ae0821dabe992a2be', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'ead4d0cf0751097220572a4946bf4dcb3d7f4f33', class: "flip-animation" }, h("div", { key: '357e981fd9b2f616e9b1b90c8454555dceac6fbd', class: "id-card-icon" }), h("div", { key: '4abfecbb5ee0165b720d6ad8398984b25ecec7a1', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'cdeedd36903c8467e7ebb406e3414ecd92abf47c', class: "success-animation" }, h("div", { key: '8731f318d490ff67fa7f68edf264acb762632604', class: "check-icon" }), h("div", { key: '2e21ce0921fedfaa0b90c56ec44d289426ea7708', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '6cf678c79f8e57fde12202a932fe855905ae770f', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'f9e888616736c26624bd3de7c2f1eee297d0a2f5', class: "status-spinner" })), h("div", { key: 'b849c5187ed8e66b4cf62113d2f93c006cf9f836', class: "status-content" }, h("div", { key: '64a2dac4cb5e4c17fc2aa161c8faa389c64a0458', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '64e85a98129f791d6784a98129f2411607360fff', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '1d41d3139d7d486bdf9225f584d6de250faeddf7', class: "performance-monitor" }, h("div", { key: '75bf3d45d869c4b0365f994444292de2245d391c', class: "performance-expanded" }, h("div", { key: '762c977e7125202103aba84c568c0fefee43d6b2', class: "metrics-row" }, h("div", { key: '51b03d882f84e835664af11d409fc01a5363c0ce', class: "metric-compact" }, h("span", { key: '21767c803c2fda9fe8653881cb8dfdcaa992fe3b', class: "metric-label" }, "FPS"), h("span", { key: '5467ba22adb75668a0acadd4ffcbaa25117fd8ae', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'a6c020d500a72f330a7cbb01cfdddc1fff63f1bb', class: "metric-compact" }, h("span", { key: '5034e33c12ad454368e4fa32567c307bce6ffe0e', class: "metric-label" }, "MEM"), h("span", { key: '19f691984f9e6aac29067fc983561b701b9080de', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'c2a11b8dc156b90a4ba18020e5a62ff87c29f624', class: "metrics-row" }, h("div", { key: '7af9c2c1286bb32d6c166d3487c8ea4a02f14490', class: "metric-compact" }, h("span", { key: 'b34e54ac6b4e387ee8155176f999dd2b721a8617', class: "metric-label" }, "INF"), h("span", { key: 'ada2f913ac6c97661a054f9bf596f4ac52eb918e', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'cc2d819a7e75af4e03d2521756c23110785076ad', class: "metric-compact" }, h("span", { key: 'b2f7e528be88eafcc66d801e8f547b4d274e0cf8', class: "metric-label" }, "FRAME"), h("span", { key: '1bcddf221b960018fb5505553e9f8cfd3717c349', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: 'cb80b6198f621fab68b13c4992578ef2e548ae99', class: "metrics-row" }, h("div", { key: 'b752b65906c92eff86cad2d0ffdc1420f03a8932', class: "metric-compact" }, h("span", { key: '96af746b42a101c6bfe4c197b652837ecc66fc06', class: "metric-label" }, "DET"), h("span", { key: 'f2038ae4943ebd9ae2ec1d8b907ae23f04c8fd90', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'c2e6b9380928b30b1a879a6abc43eb7053da9ac1', class: "metric-compact" }, h("span", { key: '096268d19a7ac7471c5dafd5adc3103d30433a55', class: "metric-label" }, "RATE"), h("span", { key: '7f9e9e49c68da719fe43391518514af3cdab004d', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'c6546f8ec722c56fa369da6d5d732f29d3afc65f', class: "watermark" }, h("img", { key: 'd195a395051a4c436dec33d7835c565e748cf60a', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1488
1560
  }
1489
1561
  // Utility methods
1490
1562
  updateDetectionBoxes(boxes) {
@@ -1557,19 +1629,37 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1557
1629
  cardOutline?.classList.add('perfect-match');
1558
1630
  corners?.forEach(corner => corner.classList.add('perfect-match'));
1559
1631
  if (!this.hasScreenshotTaken) {
1560
- this.lastDetectedBox = bestBox;
1561
- this.takeScreenshot().catch(error => {
1562
- this.logger.error('Error al tomar captura de pantalla:', error);
1563
- });
1564
- this.hasScreenshotTaken = true;
1565
- setTimeout(() => {
1566
- this.hasScreenshotTaken = false;
1567
- }, 2000);
1632
+ const currentTime = Date.now();
1633
+ // Initialize alignment start time if not set
1634
+ if (!this.alignmentStartTime) {
1635
+ this.alignmentStartTime = currentTime;
1636
+ }
1637
+ // Check if document has been aligned for 1 second
1638
+ const alignmentDuration = currentTime - this.alignmentStartTime;
1639
+ if (alignmentDuration >= 1000) {
1640
+ this.lastDetectedBox = bestBox;
1641
+ this.takeScreenshot().catch(error => {
1642
+ this.logger.error('Error al tomar captura de pantalla:', error);
1643
+ });
1644
+ this.hasScreenshotTaken = true;
1645
+ this.alignmentStartTime = undefined;
1646
+ setTimeout(() => {
1647
+ this.hasScreenshotTaken = false;
1648
+ }, 2000);
1649
+ }
1568
1650
  }
1569
1651
  }
1570
1652
  else {
1571
1653
  cardOutline?.classList.remove('perfect-match');
1572
1654
  corners?.forEach(corner => corner.classList.remove('perfect-match'));
1655
+ // Reset alignment timer when document moves out of position
1656
+ if (this.alignmentStartTime) {
1657
+ this.alignmentStartTime = undefined;
1658
+ }
1659
+ if (this.alignmentTimer) {
1660
+ clearTimeout(this.alignmentTimer);
1661
+ this.alignmentTimer = undefined;
1662
+ }
1573
1663
  }
1574
1664
  }
1575
1665
  async takeScreenshot() {
@@ -1686,8 +1776,52 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1686
1776
  this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
1687
1777
  }
1688
1778
  toggleCameraSelector() {
1779
+ if (this.isSwitchingCamera)
1780
+ return; // Don't toggle if switching camera
1689
1781
  this.showCameraSelector = !this.showCameraSelector;
1690
1782
  }
1783
+ async handleCameraSwitch(cameraId) {
1784
+ if (this.isSwitchingCamera)
1785
+ return; // Prevent multiple simultaneous switches
1786
+ try {
1787
+ // Close the selector immediately when user selects a camera
1788
+ this.showCameraSelector = false;
1789
+ this.isSwitchingCamera = true;
1790
+ this.logger.state('INICIANDO_CAMBIO_CAMARA', {
1791
+ from: this.cameraService.getSelectedCameraId(),
1792
+ to: cameraId
1793
+ });
1794
+ // Stop current video stream
1795
+ if (this.videoStream) {
1796
+ this.videoStream.getTracks().forEach(track => track.stop());
1797
+ }
1798
+ // Switch camera in service
1799
+ await this.cameraService.switchCamera(cameraId);
1800
+ // Setup new camera stream
1801
+ const newStream = await this.cameraService.setupCamera();
1802
+ // Update video element
1803
+ await this.initializeVideoStream(newStream);
1804
+ this.logger.state('CAMBIO_CAMARA_EXITOSO', {
1805
+ newCameraId: cameraId,
1806
+ isRearCamera: this.cameraService.isRearCamera(newStream)
1807
+ });
1808
+ }
1809
+ catch (error) {
1810
+ this.logger.error('Error al cambiar cámara:', error);
1811
+ // Try to restore previous camera if switch failed
1812
+ try {
1813
+ const fallbackStream = await this.cameraService.setupCamera();
1814
+ await this.initializeVideoStream(fallbackStream);
1815
+ }
1816
+ catch (fallbackError) {
1817
+ this.logger.error('Error al restaurar cámara anterior:', fallbackError);
1818
+ this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
1819
+ }
1820
+ }
1821
+ finally {
1822
+ this.isSwitchingCamera = false;
1823
+ }
1824
+ }
1691
1825
  resetDetection() {
1692
1826
  this.stateManager.reset();
1693
1827
  this.hasScreenshotTaken = false;
@@ -1696,6 +1830,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1696
1830
  this.consecutiveFailures = 0;
1697
1831
  this.lastInferenceTime = 0;
1698
1832
  this.detectionBoxes = [];
1833
+ this.alignmentStartTime = undefined;
1834
+ if (this.alignmentTimer) {
1835
+ clearTimeout(this.alignmentTimer);
1836
+ this.alignmentTimer = undefined;
1837
+ }
1699
1838
  const captureState = this.stateManager.getCaptureState();
1700
1839
  if (captureState.isVideoActive && this.detectionService.isModelLoaded()) {
1701
1840
  this.updateStatus('Captura reiniciada', 'Buscando documento en el marco de captura', 'active');
@@ -1715,6 +1854,60 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1715
1854
  this.detectionBoxes = [];
1716
1855
  this.cleanup();
1717
1856
  }
1857
+ // PERFORMANCE MONITORING METHODS
1858
+ initializePerformanceMonitor() {
1859
+ this.performanceMetrics.lastUpdateTime = performance.now();
1860
+ // Update performance metrics every 500ms
1861
+ this.performanceUpdateInterval = window.setInterval(() => {
1862
+ this.updatePerformanceMetrics();
1863
+ }, 500);
1864
+ this.logger.debug('Monitor de performance inicializado');
1865
+ }
1866
+ updatePerformanceMetrics() {
1867
+ const currentTime = performance.now();
1868
+ const deltaTime = currentTime - this.performanceMetrics.lastUpdateTime;
1869
+ // Calculate FPS
1870
+ if (deltaTime > 0) {
1871
+ this.performanceMetrics.fps = Math.round(1000 / (deltaTime / this.frameSkipCounter || 1));
1872
+ }
1873
+ // Get memory usage if available
1874
+ if ('memory' in performance) {
1875
+ const memInfo = performance.memory;
1876
+ this.performanceMetrics.memoryUsage = Math.round(memInfo.usedJSHeapSize / 1048576); // MB
1877
+ }
1878
+ // Calculate detection success rate
1879
+ if (this.performanceMetrics.totalDetections > 0) {
1880
+ this.performanceMetrics.successfulDetections = this.performanceMetrics.successfulDetections;
1881
+ const detectionRate = (this.performanceMetrics.successfulDetections / this.performanceMetrics.totalDetections) * 100;
1882
+ this.performanceMetrics.detectionRate = Math.round(detectionRate);
1883
+ }
1884
+ // Update state for rendering
1885
+ this.performanceData = {
1886
+ fps: this.performanceMetrics.fps,
1887
+ inferenceTime: this.performanceMetrics.inferenceTime,
1888
+ memoryUsage: this.performanceMetrics.memoryUsage,
1889
+ onnxLoadTime: this.performanceMetrics.onnxLoadTime,
1890
+ frameProcessingTime: this.performanceMetrics.frameProcessingTime,
1891
+ totalDetections: this.performanceMetrics.totalDetections,
1892
+ successfulDetections: this.performanceMetrics.successfulDetections,
1893
+ detectionRate: this.performanceMetrics.detectionRate
1894
+ };
1895
+ this.performanceMetrics.lastUpdateTime = currentTime;
1896
+ }
1897
+ recordOnnxPerformance(loadTime, inferenceTime) {
1898
+ this.performanceMetrics.onnxLoadTime = Math.round(loadTime);
1899
+ this.performanceMetrics.inferenceTime = Math.round(inferenceTime);
1900
+ }
1901
+ recordFrameProcessing(processingTime, detectionsFound) {
1902
+ this.performanceMetrics.frameProcessingTime = Math.round(processingTime);
1903
+ this.performanceMetrics.totalDetections++;
1904
+ if (detectionsFound > 0) {
1905
+ this.performanceMetrics.successfulDetections++;
1906
+ }
1907
+ }
1908
+ togglePerformanceMonitor() {
1909
+ this.isPerformanceMonitorMinimized = !this.isPerformanceMonitorMinimized;
1910
+ }
1718
1911
  static get style() { return myComponentCss; }
1719
1912
  }, [1, "jaak-stamps", {
1720
1913
  "debug": [4],
@@ -1728,7 +1921,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1728
1921
  "isMaskReady": [32],
1729
1922
  "shouldMirrorVideo": [32],
1730
1923
  "showCameraSelector": [32],
1924
+ "isSwitchingCamera": [32],
1731
1925
  "currentStatus": [32],
1926
+ "performanceData": [32],
1927
+ "isPerformanceMonitorMinimized": [32],
1732
1928
  "getCapturedImages": [64],
1733
1929
  "isProcessCompleted": [64],
1734
1930
  "startCapture": [64],