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

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,12 +18,24 @@ export class JaakStamps {
18
18
  isMaskReady = false;
19
19
  shouldMirrorVideo = true;
20
20
  showCameraSelector = false;
21
+ isSwitchingCamera = false;
21
22
  currentStatus = {
22
23
  message: 'Inicializando componente...',
23
24
  description: 'Configurando servicios y cargando recursos',
24
25
  type: 'initializing',
25
26
  isInitialized: false
26
27
  };
28
+ performanceData = {
29
+ fps: 0,
30
+ inferenceTime: 0,
31
+ memoryUsage: 0,
32
+ onnxLoadTime: 0,
33
+ frameProcessingTime: 0,
34
+ totalDetections: 0,
35
+ successfulDetections: 0,
36
+ detectionRate: 0
37
+ };
38
+ isPerformanceMonitorMinimized = false;
27
39
  // Services
28
40
  serviceContainer;
29
41
  logger;
@@ -40,6 +52,22 @@ export class JaakStamps {
40
52
  lastDetectedBox;
41
53
  startTime;
42
54
  hasScreenshotTaken = false;
55
+ alignmentStartTime;
56
+ alignmentTimer;
57
+ // Performance monitoring
58
+ performanceMetrics = {
59
+ fps: 0,
60
+ inferenceTime: 0,
61
+ memoryUsage: 0,
62
+ cpuUsage: 0,
63
+ onnxLoadTime: 0,
64
+ frameProcessingTime: 0,
65
+ totalDetections: 0,
66
+ successfulDetections: 0,
67
+ detectionRate: 0,
68
+ lastUpdateTime: 0
69
+ };
70
+ performanceUpdateInterval;
43
71
  // Performance optimization
44
72
  frameSkipCounter = 0;
45
73
  FRAME_SKIP = 2;
@@ -54,6 +82,9 @@ export class JaakStamps {
54
82
  await this.setupEventListeners();
55
83
  this.updateStatus('Inicializando cámara...', 'Detectando dispositivos disponibles', 'initializing');
56
84
  await this.initializeComponent();
85
+ if (this.debug) {
86
+ this.initializePerformanceMonitor();
87
+ }
57
88
  }
58
89
  async initializeServices() {
59
90
  const config = {
@@ -294,17 +325,24 @@ export class JaakStamps {
294
325
  return { success: true, message: 'Model already loaded' };
295
326
  }
296
327
  try {
328
+ const loadStartTime = performance.now();
297
329
  this.updateStatus('Descargando modelo de detección...', 'Obteniendo red neuronal para reconocimiento de documentos', 'loading');
298
330
  this.stateManager.updateCaptureState({ isLoading: true });
299
331
  await this.detectionService.loadModel();
300
332
  this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de tipos de documento', 'loading');
301
333
  await this.detectionService.loadClassificationModel();
334
+ const loadEndTime = performance.now();
335
+ const loadTime = loadEndTime - loadStartTime;
336
+ // Record ONNX load time
337
+ if (this.debug) {
338
+ this.recordOnnxPerformance(loadTime, 0);
339
+ }
302
340
  this.updateStatus('Optimizando modelos...', 'Configurando parámetros de rendimiento', 'loading');
303
341
  await new Promise(resolve => setTimeout(resolve, 300));
304
342
  this.updateStatus('Modelos precargados', '', 'ready');
305
343
  this.stateManager.updateCaptureState({ isLoading: false });
306
344
  this.emitReadyEvent();
307
- this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE');
345
+ this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
308
346
  return { success: true, message: 'Models preloaded successfully' };
309
347
  }
310
348
  catch (error) {
@@ -340,11 +378,19 @@ export class JaakStamps {
340
378
  try {
341
379
  // Paso 1: Verificar y cargar modelos si es necesario
342
380
  if (!this.detectionService.isModelLoaded()) {
381
+ const loadStartTime = performance.now();
343
382
  this.updateStatus('Cargando modelo de detección...', 'Descargando red neuronal para reconocimiento', 'loading');
344
383
  this.stateManager.updateCaptureState({ isLoading: true });
345
384
  await this.detectionService.loadModel();
346
385
  this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de documentos', 'loading');
347
386
  await this.detectionService.loadClassificationModel();
387
+ const loadEndTime = performance.now();
388
+ const loadTime = loadEndTime - loadStartTime;
389
+ // Record ONNX load time
390
+ if (this.debug) {
391
+ this.recordOnnxPerformance(loadTime, 0);
392
+ }
393
+ this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
348
394
  }
349
395
  // Paso 2: Detectar y configurar dispositivos
350
396
  this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura disponibles', 'loading');
@@ -396,6 +442,7 @@ export class JaakStamps {
396
442
  }
397
443
  async detectFrame() {
398
444
  try {
445
+ const frameStartTime = performance.now();
399
446
  const captureState = this.stateManager.getCaptureState();
400
447
  if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
401
448
  return;
@@ -423,8 +470,15 @@ export class JaakStamps {
423
470
  return;
424
471
  }
425
472
  this.lastInferenceTime = currentTime;
473
+ // Measure preprocessing and inference time
474
+ const inferenceStartTime = performance.now();
426
475
  const inputTensor = this.detectionService.preprocess(this.videoRef);
427
476
  const detections = await this.detectionService.runInference(inputTensor);
477
+ const inferenceTime = performance.now() - inferenceStartTime;
478
+ // Record ONNX performance metrics
479
+ if (this.debug) {
480
+ this.recordOnnxPerformance(0, inferenceTime);
481
+ }
428
482
  // Update best score logic
429
483
  if (this.startTime && Date.now() - this.startTime < 5000) {
430
484
  detections.forEach(detection => {
@@ -450,6 +504,12 @@ export class JaakStamps {
450
504
  this.detectionBoxes = [];
451
505
  }
452
506
  this.updateMaskColor(detections);
507
+ // Record frame processing metrics
508
+ if (this.debug) {
509
+ const frameEndTime = performance.now();
510
+ const totalFrameTime = frameEndTime - frameStartTime;
511
+ this.recordFrameProcessing(totalFrameTime, detections.length);
512
+ }
453
513
  // Continue detection loop
454
514
  if (captureState.step !== 'completed') {
455
515
  if (this.consecutiveFailures > this.MAX_FAILURES) {
@@ -480,7 +540,16 @@ export class JaakStamps {
480
540
  if (this.videoStream) {
481
541
  this.videoStream.getTracks().forEach(track => track.stop());
482
542
  }
543
+ if (this.alignmentTimer) {
544
+ clearTimeout(this.alignmentTimer);
545
+ this.alignmentTimer = undefined;
546
+ }
547
+ if (this.performanceUpdateInterval) {
548
+ clearInterval(this.performanceUpdateInterval);
549
+ this.performanceUpdateInterval = undefined;
550
+ }
483
551
  this.detectionBoxes = [];
552
+ this.alignmentStartTime = undefined;
484
553
  this.serviceContainer?.cleanup();
485
554
  }
486
555
  render() {
@@ -499,7 +568,7 @@ export class JaakStamps {
499
568
  deviceType: 'desktop',
500
569
  preferredFacing: null
501
570
  };
502
- 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: {
571
+ 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: {
503
572
  position: 'absolute',
504
573
  left: `${box.x}px`,
505
574
  top: `${box.y}px`,
@@ -508,10 +577,7 @@ export class JaakStamps {
508
577
  border: '2px solid #32406C',
509
578
  pointerEvents: 'none',
510
579
  boxSizing: 'border-box'
511
- } })))), 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: () => {
512
- this.cameraService.switchCamera(camera.id);
513
- this.toggleCameraSelector();
514
- }, 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" })))));
580
+ } })))), 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" })))));
515
581
  }
516
582
  // Utility methods
517
583
  updateDetectionBoxes(boxes) {
@@ -584,19 +650,37 @@ export class JaakStamps {
584
650
  cardOutline?.classList.add('perfect-match');
585
651
  corners?.forEach(corner => corner.classList.add('perfect-match'));
586
652
  if (!this.hasScreenshotTaken) {
587
- this.lastDetectedBox = bestBox;
588
- this.takeScreenshot().catch(error => {
589
- this.logger.error('Error al tomar captura de pantalla:', error);
590
- });
591
- this.hasScreenshotTaken = true;
592
- setTimeout(() => {
593
- this.hasScreenshotTaken = false;
594
- }, 2000);
653
+ const currentTime = Date.now();
654
+ // Initialize alignment start time if not set
655
+ if (!this.alignmentStartTime) {
656
+ this.alignmentStartTime = currentTime;
657
+ }
658
+ // Check if document has been aligned for 1 second
659
+ const alignmentDuration = currentTime - this.alignmentStartTime;
660
+ if (alignmentDuration >= 1000) {
661
+ this.lastDetectedBox = bestBox;
662
+ this.takeScreenshot().catch(error => {
663
+ this.logger.error('Error al tomar captura de pantalla:', error);
664
+ });
665
+ this.hasScreenshotTaken = true;
666
+ this.alignmentStartTime = undefined;
667
+ setTimeout(() => {
668
+ this.hasScreenshotTaken = false;
669
+ }, 2000);
670
+ }
595
671
  }
596
672
  }
597
673
  else {
598
674
  cardOutline?.classList.remove('perfect-match');
599
675
  corners?.forEach(corner => corner.classList.remove('perfect-match'));
676
+ // Reset alignment timer when document moves out of position
677
+ if (this.alignmentStartTime) {
678
+ this.alignmentStartTime = undefined;
679
+ }
680
+ if (this.alignmentTimer) {
681
+ clearTimeout(this.alignmentTimer);
682
+ this.alignmentTimer = undefined;
683
+ }
600
684
  }
601
685
  }
602
686
  async takeScreenshot() {
@@ -713,8 +797,52 @@ export class JaakStamps {
713
797
  this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
714
798
  }
715
799
  toggleCameraSelector() {
800
+ if (this.isSwitchingCamera)
801
+ return; // Don't toggle if switching camera
716
802
  this.showCameraSelector = !this.showCameraSelector;
717
803
  }
804
+ async handleCameraSwitch(cameraId) {
805
+ if (this.isSwitchingCamera)
806
+ return; // Prevent multiple simultaneous switches
807
+ try {
808
+ // Close the selector immediately when user selects a camera
809
+ this.showCameraSelector = false;
810
+ this.isSwitchingCamera = true;
811
+ this.logger.state('INICIANDO_CAMBIO_CAMARA', {
812
+ from: this.cameraService.getSelectedCameraId(),
813
+ to: cameraId
814
+ });
815
+ // Stop current video stream
816
+ if (this.videoStream) {
817
+ this.videoStream.getTracks().forEach(track => track.stop());
818
+ }
819
+ // Switch camera in service
820
+ await this.cameraService.switchCamera(cameraId);
821
+ // Setup new camera stream
822
+ const newStream = await this.cameraService.setupCamera();
823
+ // Update video element
824
+ await this.initializeVideoStream(newStream);
825
+ this.logger.state('CAMBIO_CAMARA_EXITOSO', {
826
+ newCameraId: cameraId,
827
+ isRearCamera: this.cameraService.isRearCamera(newStream)
828
+ });
829
+ }
830
+ catch (error) {
831
+ this.logger.error('Error al cambiar cámara:', error);
832
+ // Try to restore previous camera if switch failed
833
+ try {
834
+ const fallbackStream = await this.cameraService.setupCamera();
835
+ await this.initializeVideoStream(fallbackStream);
836
+ }
837
+ catch (fallbackError) {
838
+ this.logger.error('Error al restaurar cámara anterior:', fallbackError);
839
+ this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
840
+ }
841
+ }
842
+ finally {
843
+ this.isSwitchingCamera = false;
844
+ }
845
+ }
718
846
  resetDetection() {
719
847
  this.stateManager.reset();
720
848
  this.hasScreenshotTaken = false;
@@ -723,6 +851,11 @@ export class JaakStamps {
723
851
  this.consecutiveFailures = 0;
724
852
  this.lastInferenceTime = 0;
725
853
  this.detectionBoxes = [];
854
+ this.alignmentStartTime = undefined;
855
+ if (this.alignmentTimer) {
856
+ clearTimeout(this.alignmentTimer);
857
+ this.alignmentTimer = undefined;
858
+ }
726
859
  const captureState = this.stateManager.getCaptureState();
727
860
  if (captureState.isVideoActive && this.detectionService.isModelLoaded()) {
728
861
  this.updateStatus('Captura reiniciada', 'Buscando documento en el marco de captura', 'active');
@@ -742,6 +875,60 @@ export class JaakStamps {
742
875
  this.detectionBoxes = [];
743
876
  this.cleanup();
744
877
  }
878
+ // PERFORMANCE MONITORING METHODS
879
+ initializePerformanceMonitor() {
880
+ this.performanceMetrics.lastUpdateTime = performance.now();
881
+ // Update performance metrics every 500ms
882
+ this.performanceUpdateInterval = window.setInterval(() => {
883
+ this.updatePerformanceMetrics();
884
+ }, 500);
885
+ this.logger.debug('Monitor de performance inicializado');
886
+ }
887
+ updatePerformanceMetrics() {
888
+ const currentTime = performance.now();
889
+ const deltaTime = currentTime - this.performanceMetrics.lastUpdateTime;
890
+ // Calculate FPS
891
+ if (deltaTime > 0) {
892
+ this.performanceMetrics.fps = Math.round(1000 / (deltaTime / this.frameSkipCounter || 1));
893
+ }
894
+ // Get memory usage if available
895
+ if ('memory' in performance) {
896
+ const memInfo = performance.memory;
897
+ this.performanceMetrics.memoryUsage = Math.round(memInfo.usedJSHeapSize / 1048576); // MB
898
+ }
899
+ // Calculate detection success rate
900
+ if (this.performanceMetrics.totalDetections > 0) {
901
+ this.performanceMetrics.successfulDetections = this.performanceMetrics.successfulDetections;
902
+ const detectionRate = (this.performanceMetrics.successfulDetections / this.performanceMetrics.totalDetections) * 100;
903
+ this.performanceMetrics.detectionRate = Math.round(detectionRate);
904
+ }
905
+ // Update state for rendering
906
+ this.performanceData = {
907
+ fps: this.performanceMetrics.fps,
908
+ inferenceTime: this.performanceMetrics.inferenceTime,
909
+ memoryUsage: this.performanceMetrics.memoryUsage,
910
+ onnxLoadTime: this.performanceMetrics.onnxLoadTime,
911
+ frameProcessingTime: this.performanceMetrics.frameProcessingTime,
912
+ totalDetections: this.performanceMetrics.totalDetections,
913
+ successfulDetections: this.performanceMetrics.successfulDetections,
914
+ detectionRate: this.performanceMetrics.detectionRate
915
+ };
916
+ this.performanceMetrics.lastUpdateTime = currentTime;
917
+ }
918
+ recordOnnxPerformance(loadTime, inferenceTime) {
919
+ this.performanceMetrics.onnxLoadTime = Math.round(loadTime);
920
+ this.performanceMetrics.inferenceTime = Math.round(inferenceTime);
921
+ }
922
+ recordFrameProcessing(processingTime, detectionsFound) {
923
+ this.performanceMetrics.frameProcessingTime = Math.round(processingTime);
924
+ this.performanceMetrics.totalDetections++;
925
+ if (detectionsFound > 0) {
926
+ this.performanceMetrics.successfulDetections++;
927
+ }
928
+ }
929
+ togglePerformanceMonitor() {
930
+ this.isPerformanceMonitorMinimized = !this.isPerformanceMonitorMinimized;
931
+ }
745
932
  static get is() { return "jaak-stamps"; }
746
933
  static get encapsulation() { return "shadow"; }
747
934
  static get originalStyleUrls() {
@@ -885,7 +1072,10 @@ export class JaakStamps {
885
1072
  "isMaskReady": {},
886
1073
  "shouldMirrorVideo": {},
887
1074
  "showCameraSelector": {},
888
- "currentStatus": {}
1075
+ "isSwitchingCamera": {},
1076
+ "currentStatus": {},
1077
+ "performanceData": {},
1078
+ "isPerformanceMonitorMinimized": {}
889
1079
  };
890
1080
  }
891
1081
  static get events() {