@jaak.ai/stamps 2.0.0-dev.27 → 2.0.0-dev.29

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.
@@ -76,34 +76,65 @@ const JaakStamps = class {
76
76
  CONFIDENCE_THRESHOLD = 0.6;
77
77
  // ISO/IEC 7810 ID-1 standard dimensions (85.60mm x 53.98mm)
78
78
  ID1_ASPECT_RATIO = 85.60 / 53.98; // 1.5863320574...
79
- debugLog(...args) {
80
- if (this.debug) {
81
- console.log(...args);
79
+ logger = {
80
+ info: (...args) => {
81
+ if (this.debug) {
82
+ console.log(`[JAAK-STAMPS] [INFO] [${new Date().toLocaleTimeString()}]`, ...args);
83
+ }
84
+ },
85
+ warn: (...args) => {
86
+ if (this.debug) {
87
+ console.warn(`[JAAK-STAMPS] [WARN] [${new Date().toLocaleTimeString()}]`, ...args);
88
+ }
89
+ },
90
+ error: (...args) => {
91
+ if (this.debug) {
92
+ console.error(`[JAAK-STAMPS] [ERROR] [${new Date().toLocaleTimeString()}]`, ...args);
93
+ }
94
+ },
95
+ debug: (...args) => {
96
+ if (this.debug) {
97
+ console.debug(`[JAAK-STAMPS] [DEBUG] [${new Date().toLocaleTimeString()}]`, ...args);
98
+ }
99
+ },
100
+ state: (state, data) => {
101
+ if (this.debug) {
102
+ console.log(`[JAAK-STAMPS] [STATE] [${new Date().toLocaleTimeString()}] ${state}`, data || '');
103
+ }
104
+ },
105
+ performance: (operation, duration) => {
106
+ if (this.debug) {
107
+ console.log(`[JAAK-STAMPS] [PERF] [${new Date().toLocaleTimeString()}] ${operation}: ${duration}ms`);
108
+ }
82
109
  }
83
- }
110
+ };
84
111
  validateMaskSize() {
85
112
  if (this.maskSize < 50 || this.maskSize > 100) {
86
- console.warn(`maskSize debe estar entre 50 y 100. Valor actual: ${this.maskSize}. Usando valor por defecto: 90`);
113
+ this.logger.warn(`Propiedad maskSize inválida. Valor: ${this.maskSize}, esperado: 50-100. Usando valor por defecto: 90`);
87
114
  this.maskSize = 90;
88
115
  }
89
116
  }
90
117
  validateCropMargin() {
91
118
  if (this.cropMargin < 0 || this.cropMargin > 100) {
92
- console.warn(`cropMargin debe estar entre 0 y 100. Valor actual: ${this.cropMargin}. Usando valor por defecto: 0`);
119
+ this.logger.warn(`Propiedad cropMargin inválida. Valor: ${this.cropMargin}, esperado: 0-100. Usando valor por defecto: 0`);
93
120
  this.cropMargin = 0;
94
121
  }
95
122
  }
96
123
  validatePreferredCamera() {
97
124
  const validOptions = ['auto', 'front', 'back'];
98
125
  if (!validOptions.includes(this.preferredCamera)) {
99
- console.warn(`preferredCamera debe ser uno de: ${validOptions.join(', ')}. Valor actual: ${this.preferredCamera}. Usando valor por defecto: 'auto'`);
126
+ this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
100
127
  this.preferredCamera = 'auto';
101
128
  }
102
129
  }
103
130
  emitReadyEvent() {
104
131
  const isDocumentReady = !!window.ort && this.isModelPreloaded;
105
132
  this.isReady.emit(isDocumentReady);
106
- this.debugLog('🟢 isReady event emitted:', isDocumentReady);
133
+ this.logger.state('COMPONENTE_LISTO', {
134
+ ortLibraryLoaded: !!window.ort,
135
+ modelPreloaded: this.isModelPreloaded,
136
+ isReady: isDocumentReady
137
+ });
107
138
  }
108
139
  isRearCamera(stream) {
109
140
  const videoTrack = stream.getVideoTracks()[0];
@@ -126,7 +157,11 @@ const JaakStamps = class {
126
157
  else {
127
158
  this.deviceType = 'desktop';
128
159
  }
129
- this.debugLog('📱 Device type detected:', this.deviceType);
160
+ this.logger.state('DISPOSITIVO_DETECTADO', {
161
+ deviceType: this.deviceType,
162
+ userAgent: navigator.userAgent,
163
+ screenDimensions: { width: window.innerWidth, height: window.innerHeight }
164
+ });
130
165
  // Enumerate available cameras
131
166
  await this.enumerateAndDetectCameras();
132
167
  // Load user preference
@@ -137,7 +172,7 @@ const JaakStamps = class {
137
172
  // First, check if we have permission to enumerate devices
138
173
  const permissionStatus = await this.checkCameraPermission();
139
174
  if (permissionStatus === 'denied') {
140
- this.debugLog(' Camera permission denied');
175
+ this.logger.error('Permiso de cámara denegado por el usuario');
141
176
  this.statusMessage = "Permiso de cámara denegado";
142
177
  this.statusColor = "#ff6b6b";
143
178
  return;
@@ -151,7 +186,7 @@ const JaakStamps = class {
151
186
  const devices = await navigator.mediaDevices.enumerateDevices();
152
187
  this.availableCameras = devices.filter(device => device.kind === 'videoinput');
153
188
  this.isMultipleCamerasAvailable = this.availableCameras.length > 1;
154
- this.debugLog('📹 Available cameras:', {
189
+ this.logger.state('CAMARAS_DETECTADAS', {
155
190
  count: this.availableCameras.length,
156
191
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
157
192
  cameras: this.availableCameras.map(cam => ({
@@ -163,7 +198,7 @@ const JaakStamps = class {
163
198
  this.setInitialCameraPreference();
164
199
  }
165
200
  catch (error) {
166
- this.debugLog('Error enumerating cameras:', error);
201
+ this.logger.error('Error al enumerar cámaras disponibles:', error);
167
202
  this.handleCameraPermissionError(error);
168
203
  }
169
204
  }
@@ -176,7 +211,7 @@ const JaakStamps = class {
176
211
  return permission.state;
177
212
  }
178
213
  catch (error) {
179
- this.debugLog('⚠️ Could not check camera permission:', error);
214
+ this.logger.warn('No se pudo verificar permisos de cámara:', error);
180
215
  return 'prompt';
181
216
  }
182
217
  }
@@ -219,11 +254,11 @@ const JaakStamps = class {
219
254
  !camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
220
255
  if (frontCamera) {
221
256
  this.selectedCameraId = frontCamera.deviceId;
222
- this.debugLog('👤 User selected front camera:', frontCamera.label);
257
+ this.logger.state('CAMARA_FRONTAL_SELECCIONADA', { label: frontCamera.label, deviceId: frontCamera.deviceId });
223
258
  }
224
259
  else {
225
260
  this.selectedCameraId = this.availableCameras[0].deviceId;
226
- this.debugLog('⚠️ Front camera not found, using first available:', this.availableCameras[0].label);
261
+ this.logger.warn('Cámara frontal no encontrada, usando primera disponible:', this.availableCameras[0].label);
227
262
  }
228
263
  }
229
264
  else if (this.preferredCamera === 'back') {
@@ -234,11 +269,11 @@ const JaakStamps = class {
234
269
  camera.label.toLowerCase().includes('environment'));
235
270
  if (backCamera) {
236
271
  this.selectedCameraId = backCamera.deviceId;
237
- this.debugLog('📷 User selected back camera:', backCamera.label);
272
+ this.logger.state('CAMARA_TRASERA_SELECCIONADA', { label: backCamera.label, deviceId: backCamera.deviceId });
238
273
  }
239
274
  else {
240
275
  this.selectedCameraId = this.availableCameras[0].deviceId;
241
- this.debugLog('⚠️ Back camera not found, using first available:', this.availableCameras[0].label);
276
+ this.logger.warn('Cámara trasera no encontrada, usando primera disponible:', this.availableCameras[0].label);
242
277
  }
243
278
  }
244
279
  else {
@@ -251,17 +286,17 @@ const JaakStamps = class {
251
286
  camera.label.toLowerCase().includes('environment'));
252
287
  if (rearCamera) {
253
288
  this.selectedCameraId = rearCamera.deviceId;
254
- this.debugLog('📱 Auto-selected rear camera for mobile:', rearCamera.label);
289
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear', label: rearCamera.label, deviceId: rearCamera.deviceId });
255
290
  }
256
291
  else {
257
292
  this.selectedCameraId = this.availableCameras[0].deviceId;
258
- this.debugLog('📱 Rear camera not found, using first available:', this.availableCameras[0].label);
293
+ this.logger.warn('Cámara trasera no encontrada en mobile, usando primera disponible:', this.availableCameras[0].label);
259
294
  }
260
295
  }
261
296
  else {
262
297
  // For desktop, use first available camera (usually the only one)
263
298
  this.selectedCameraId = this.availableCameras[0].deviceId;
264
- this.debugLog('💻 Auto-selected desktop camera:', this.availableCameras[0].label);
299
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', { label: this.availableCameras[0].label, deviceId: this.availableCameras[0].deviceId });
265
300
  }
266
301
  }
267
302
  }
@@ -275,12 +310,12 @@ const JaakStamps = class {
275
310
  if (isStillAvailable) {
276
311
  this.selectedCameraId = preference.cameraId;
277
312
  this.preferredCameraFacing = preference.facing;
278
- this.debugLog('💾 Loaded camera preference:', preference);
313
+ this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
279
314
  }
280
315
  }
281
316
  }
282
317
  catch (error) {
283
- this.debugLog('⚠️ Error loading camera preference:', error);
318
+ this.logger.warn('Error al cargar preferencia de cámara:', error);
284
319
  }
285
320
  }
286
321
  saveCameraPreference() {
@@ -291,10 +326,10 @@ const JaakStamps = class {
291
326
  timestamp: Date.now()
292
327
  };
293
328
  localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
294
- this.debugLog('💾 Saved camera preference:', preference);
329
+ this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
295
330
  }
296
331
  catch (error) {
297
- this.debugLog('⚠️ Error saving camera preference:', error);
332
+ this.logger.warn('Error al guardar preferencia de cámara:', error);
298
333
  }
299
334
  }
300
335
  async switchCamera(cameraId) {
@@ -304,7 +339,7 @@ const JaakStamps = class {
304
339
  // Check if the selected camera is still available
305
340
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
306
341
  if (!selectedCamera) {
307
- this.debugLog(' Selected camera not found, re-enumerating...');
342
+ this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
308
343
  await this.enumerateAndDetectCameras();
309
344
  return;
310
345
  }
@@ -328,10 +363,10 @@ const JaakStamps = class {
328
363
  this.saveCameraPreference();
329
364
  // Setup new camera with error handling
330
365
  await this.setupCameraWithRetry();
331
- this.debugLog('🔄 Switched to camera:', selectedCamera.label);
366
+ this.logger.state('CAMARA_CAMBIADA', { label: selectedCamera.label, deviceId: selectedCamera.deviceId });
332
367
  }
333
368
  catch (error) {
334
- this.debugLog('Error switching camera:', error);
369
+ this.logger.error('Error al cambiar de cámara:', error);
335
370
  this.handleCameraPermissionError(error);
336
371
  }
337
372
  }
@@ -346,7 +381,7 @@ const JaakStamps = class {
346
381
  return; // Success
347
382
  }
348
383
  catch (error) {
349
- this.debugLog(`❌ Camera setup attempt ${attempt} failed:`, error);
384
+ this.logger.error(`Intento ${attempt} de configuración de cámara fallido:`, error);
350
385
  if (attempt === maxRetries) {
351
386
  // Last attempt failed, handle the error
352
387
  this.statusMessage = "Error al configurar la cámara";
@@ -366,7 +401,7 @@ const JaakStamps = class {
366
401
  }
367
402
  toggleCameraSelector() {
368
403
  this.showCameraSelector = !this.showCameraSelector;
369
- this.debugLog('📹 Camera selector toggled:', {
404
+ this.logger.state('SELECTOR_CAMARA_TOGGLE', {
370
405
  showCameraSelector: this.showCameraSelector,
371
406
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
372
407
  availableCameras: this.availableCameras.length,
@@ -382,6 +417,13 @@ const JaakStamps = class {
382
417
  await this.switchCamera(nextCamera.deviceId);
383
418
  }
384
419
  async componentDidLoad() {
420
+ this.logger.state('COMPONENTE_INICIALIZANDO', {
421
+ debug: this.debug,
422
+ maskSize: this.maskSize,
423
+ cropMargin: this.cropMargin,
424
+ useDocumentClassification: this.useDocumentClassification,
425
+ preferredCamera: this.preferredCamera
426
+ });
385
427
  if (this.debug) {
386
428
  // Show detailed initialization loading state only in debug mode
387
429
  this.isLoading = true;
@@ -446,7 +488,7 @@ const JaakStamps = class {
446
488
  this.canvasRef.height = rect.height;
447
489
  // Update mask positioning based on container and video dimensions
448
490
  this.updateMaskDimensions(rect);
449
- this.debugLog('📐 Canvas resized:', { width: rect.width, height: rect.height });
491
+ this.logger.debug('Canvas redimensionado:', { width: rect.width, height: rect.height });
450
492
  }
451
493
  }
452
494
  updateMaskDimensions(containerRect) {
@@ -506,7 +548,7 @@ const JaakStamps = class {
506
548
  this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
507
549
  // Mark mask as ready now that dimensions are calculated
508
550
  this.isMaskReady = true;
509
- this.debugLog('🎯 Mask dimensions updated:', {
551
+ this.logger.state('DIMENSIONES_MASCARA_ACTUALIZADAS', {
510
552
  video: { width: videoWidth, height: videoHeight },
511
553
  displayed: { width: displayedVideoWidth, height: displayedVideoHeight },
512
554
  mask: { widthPercent: maskWidthPercent, heightPercent: maskHeightPercent },
@@ -528,7 +570,7 @@ const JaakStamps = class {
528
570
  this.captureCtx = this.captureCanvas.getContext('2d', {
529
571
  alpha: false
530
572
  });
531
- this.debugLog('🎨 Canvas pool initialized for performance');
573
+ this.logger.state('CANVAS_POOL_INICIALIZADO', { preprocessCanvasSize: this.INPUT_SIZE });
532
574
  }
533
575
  disconnectedCallback() {
534
576
  this.cleanup();
@@ -576,7 +618,7 @@ const JaakStamps = class {
576
618
  }
577
619
  async preloadModel() {
578
620
  if (this.isModelPreloaded || this.session) {
579
- this.debugLog('🚀 Model already preloaded or session exists');
621
+ this.logger.state('MODELO_YA_PRECARGADO', { sessionExists: !!this.session, modelPreloaded: this.isModelPreloaded });
580
622
  return { success: true, message: 'Model already loaded' };
581
623
  }
582
624
  try {
@@ -584,12 +626,39 @@ const JaakStamps = class {
584
626
  this.statusMessage = "Precargando modelos...";
585
627
  this.statusColor = "#007bff";
586
628
  const modelPath = this.MODEL_PATH;
587
- this.debugLog('🤖 Preloading detection model:', modelPath);
629
+ this.logger.state('PRECARGANDO_MODELO_DETECCION', { modelPath });
588
630
  // Configure ONNX Runtime with device-specific optimizations
589
631
  const sessionOptions = this.getSessionOptions();
590
- this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
632
+ const deviceInfo = this.getDeviceMemoryInfo();
633
+ try {
634
+ this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
635
+ }
636
+ catch (error) {
637
+ if (error.message.includes('failed to allocate a buffer')) {
638
+ this.logger.warn('Fallo en asignación de buffer durante precarga, intentando con configuración mínima');
639
+ const fallbackOptions = {
640
+ executionProviders: ['wasm'],
641
+ graphOptimizationLevel: 'disabled',
642
+ logSeverityLevel: 4,
643
+ enableCpuMemArena: false,
644
+ enableMemPattern: false,
645
+ executionMode: 'sequential',
646
+ interOpNumThreads: 1,
647
+ intraOpNumThreads: 1,
648
+ };
649
+ this.session = await window.ort.InferenceSession.create(modelPath, fallbackOptions);
650
+ }
651
+ else {
652
+ throw error;
653
+ }
654
+ }
591
655
  // Preload MobileNet model and classes only if classification is enabled
656
+ // For low memory devices, load sequentially to avoid memory pressure
592
657
  if (this.useDocumentClassification) {
658
+ if (deviceInfo.isLowMemory) {
659
+ this.logger.state('CARGA_SECUENCIAL_MODELOS', { reason: 'low memory device' });
660
+ await new Promise(resolve => setTimeout(resolve, 1000));
661
+ }
593
662
  await this.loadMobileNetModel();
594
663
  }
595
664
  this.isModelPreloaded = true;
@@ -597,11 +666,15 @@ const JaakStamps = class {
597
666
  this.statusMessage = "Modelos precargados. Listo para comenzar detección";
598
667
  this.statusColor = "#28a745";
599
668
  this.emitReadyEvent();
600
- this.debugLog(' Models preloaded successfully');
669
+ this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', {
670
+ detectionModel: !!this.session,
671
+ classificationModel: !!this.mobileNetSession,
672
+ useClassification: this.useDocumentClassification
673
+ });
601
674
  return { success: true, message: 'Models preloaded successfully' };
602
675
  }
603
676
  catch (error) {
604
- this.debugLog('Error preloading models:', error);
677
+ this.logger.error('Error al precargar modelos:', error);
605
678
  this.isLoading = false;
606
679
  this.statusMessage = "Error al precargar los modelos";
607
680
  this.statusColor = "#ff6b6b";
@@ -629,7 +702,7 @@ const JaakStamps = class {
629
702
  }
630
703
  async setPreferredCamera(camera) {
631
704
  this.preferredCamera = camera;
632
- this.debugLog('🎯 Camera preference changed to:', camera);
705
+ this.logger.state('PREFERENCIA_CAMARA_CAMBIADA', { newPreference: camera });
633
706
  // Re-detect and apply new camera preference
634
707
  await this.enumerateAndDetectCameras();
635
708
  // If video is active, switch to the new preferred camera
@@ -644,21 +717,42 @@ const JaakStamps = class {
644
717
  }
645
718
  async loadMobileNetModel() {
646
719
  try {
647
- this.debugLog('🤖 Loading MobileNet model...');
720
+ this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
648
721
  // Load class map
649
722
  const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
650
723
  if (!classResponse.ok) {
651
724
  throw new Error(`Failed to load class map: ${this.MOBILENET_CLASSES_PATH}`);
652
725
  }
653
726
  this.mobileNetClassMap = await classResponse.json();
654
- this.debugLog('📋 MobileNet classes loaded:', this.mobileNetClassMap);
727
+ this.logger.state('CLASES_MOBILENET_CARGADAS', { classCount: Object.keys(this.mobileNetClassMap).length });
655
728
  // Load model
656
729
  const sessionOptions = this.getSessionOptions();
657
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
658
- this.debugLog('✅ MobileNet model loaded successfully');
730
+ try {
731
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
732
+ }
733
+ catch (error) {
734
+ if (error.message.includes('failed to allocate a buffer')) {
735
+ this.logger.warn('Fallo en asignación de buffer de MobileNet, intentando con configuración mínima');
736
+ const fallbackOptions = {
737
+ executionProviders: ['wasm'],
738
+ graphOptimizationLevel: 'disabled',
739
+ logSeverityLevel: 4,
740
+ enableCpuMemArena: false,
741
+ enableMemPattern: false,
742
+ executionMode: 'sequential',
743
+ interOpNumThreads: 1,
744
+ intraOpNumThreads: 1,
745
+ };
746
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
747
+ }
748
+ else {
749
+ throw error;
750
+ }
751
+ }
752
+ this.logger.state('MODELO_MOBILENET_CARGADO_EXITOSAMENTE', { sessionCreated: !!this.mobileNetSession });
659
753
  }
660
754
  catch (error) {
661
- this.debugLog('Error loading MobileNet model:', error);
755
+ this.logger.error('Error al cargar modelo MobileNet:', error);
662
756
  throw error;
663
757
  }
664
758
  }
@@ -685,11 +779,11 @@ const JaakStamps = class {
685
779
  }
686
780
  async classifyDocument(canvas) {
687
781
  if (!this.mobileNetSession || !this.mobileNetClassMap) {
688
- this.debugLog('⚠️ MobileNet model not loaded');
782
+ this.logger.warn('Modelo MobileNet no está cargado, saltando clasificación');
689
783
  return null;
690
784
  }
691
785
  try {
692
- this.debugLog('🔍 Classifying document...');
786
+ this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
693
787
  // Preprocess image for MobileNet
694
788
  const inputTensor = this.preprocessMobileNet(canvas);
695
789
  // Run inference
@@ -700,10 +794,11 @@ const JaakStamps = class {
700
794
  const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
701
795
  const confidence = output[maxIdx];
702
796
  const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
703
- this.debugLog('📄 Document classification result:', {
797
+ this.logger.state('DOCUMENTO_CLASIFICADO', {
704
798
  class: className,
705
799
  confidence: confidence.toFixed(3),
706
- classIndex: maxIdx
800
+ classIndex: maxIdx,
801
+ timestamp: Date.now()
707
802
  });
708
803
  return {
709
804
  class: className,
@@ -712,7 +807,7 @@ const JaakStamps = class {
712
807
  };
713
808
  }
714
809
  catch (error) {
715
- this.debugLog('Error classifying document:', error);
810
+ this.logger.error('Error al clasificar documento:', error);
716
811
  return null;
717
812
  }
718
813
  }
@@ -754,7 +849,7 @@ const JaakStamps = class {
754
849
  }
755
850
  this.mobileNetClassMap = undefined;
756
851
  this.isModelPreloaded = false;
757
- this.debugLog('🧹 Canvas pool cleaned up');
852
+ this.logger.state('CANVAS_POOL_LIMPIADO', { timestamp: Date.now() });
758
853
  }
759
854
  async getMaxResolution() {
760
855
  try {
@@ -803,18 +898,19 @@ const JaakStamps = class {
803
898
  constraints.width = { ideal: maxWidth };
804
899
  constraints.height = { ideal: maxHeight };
805
900
  }
806
- this.debugLog('📐 Resolution capabilities:', {
901
+ this.logger.state('CAPACIDADES_RESOLUCION_DETECTADAS', {
807
902
  maxWidth: capabilities.width.max,
808
903
  maxHeight: capabilities.height.max,
809
904
  selectedWidth: constraints.width.ideal,
810
905
  selectedHeight: constraints.height.ideal,
811
- isTablet
906
+ isTablet,
907
+ deviceType: this.deviceType
812
908
  });
813
909
  }
814
910
  return constraints;
815
911
  }
816
912
  catch (err) {
817
- this.debugLog('⚠️ Could not get capabilities, using fallback');
913
+ this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
818
914
  // Optimized fallback for tablets
819
915
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
820
916
  const fallbackConstraints = {
@@ -847,8 +943,11 @@ const JaakStamps = class {
847
943
  // Determine if video should be mirrored
848
944
  const isRear = this.isRearCamera(stream);
849
945
  this.shouldMirrorVideo = !isRear;
850
- this.debugLog('📹 Rear camera:', isRear);
851
- this.debugLog('🪞 Should mirror video:', this.shouldMirrorVideo);
946
+ this.logger.state('CAMARA_CONFIGURADA', {
947
+ isRearCamera: isRear,
948
+ shouldMirrorVideo: this.shouldMirrorVideo,
949
+ videoActive: this.isVideoActive
950
+ });
852
951
  return new Promise((resolve) => {
853
952
  this.videoRef.onloadedmetadata = async () => {
854
953
  await this.videoRef.play();
@@ -865,7 +964,7 @@ const JaakStamps = class {
865
964
  }
866
965
  }
867
966
  catch (err) {
868
- this.debugLog("❌ No se pudo acceder a la cámara:", err);
967
+ this.logger.error('No se pudo acceder a la cámara:', err);
869
968
  this.handleCameraPermissionError(err);
870
969
  }
871
970
  }
@@ -889,15 +988,39 @@ const JaakStamps = class {
889
988
  const transposedData = new Float32Array(R.concat(G, B));
890
989
  return new window.ort.Tensor("float32", transposedData, [1, 3, this.INPUT_SIZE, this.INPUT_SIZE]);
891
990
  }
991
+ getDeviceMemoryInfo() {
992
+ const nav = navigator;
993
+ const memory = nav.deviceMemory || nav.hardwareConcurrency || 4;
994
+ const connection = nav.connection || nav.mozConnection || nav.webkitConnection;
995
+ const isSlowConnection = connection && (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g');
996
+ return {
997
+ estimatedRAM: memory,
998
+ isLowMemory: memory <= 4,
999
+ isSlowConnection: isSlowConnection
1000
+ };
1001
+ }
892
1002
  getSessionOptions() {
1003
+ const deviceInfo = this.getDeviceMemoryInfo();
1004
+ if (deviceInfo.isLowMemory) {
1005
+ return {
1006
+ executionProviders: ['wasm'],
1007
+ graphOptimizationLevel: 'basic',
1008
+ logSeverityLevel: 4,
1009
+ logVerbosityLevel: 0,
1010
+ enableCpuMemArena: false,
1011
+ enableMemPattern: false,
1012
+ executionMode: 'sequential',
1013
+ interOpNumThreads: 1,
1014
+ intraOpNumThreads: 1,
1015
+ };
1016
+ }
893
1017
  return {
894
1018
  executionProviders: [
895
- // Try WebGL first for GPU acceleration, fallback to WASM
896
1019
  'webgl',
897
1020
  'wasm'
898
1021
  ],
899
- graphOptimizationLevel: 'all', // Maximum optimization
900
- logSeverityLevel: this.debug ? 2 : 4, // Reduce logging overhead in production
1022
+ graphOptimizationLevel: 'all',
1023
+ logSeverityLevel: this.debug ? 2 : 4,
901
1024
  logVerbosityLevel: 0,
902
1025
  enableCpuMemArena: true,
903
1026
  enableMemPattern: true,
@@ -907,6 +1030,12 @@ const JaakStamps = class {
907
1030
  };
908
1031
  }
909
1032
  async startDetection() {
1033
+ this.logger.state('INICIANDO_DETECCION', {
1034
+ sessionExists: !!this.session,
1035
+ modelPreloaded: this.isModelPreloaded,
1036
+ videoActive: this.isVideoActive,
1037
+ captureStep: this.captureStep
1038
+ });
910
1039
  try {
911
1040
  // Check if model is already preloaded
912
1041
  if (!this.session) {
@@ -920,9 +1049,30 @@ const JaakStamps = class {
920
1049
  this.statusColor = "#007bff";
921
1050
  }
922
1051
  const modelPath = this.MODEL_PATH;
923
- this.debugLog('🤖 Loading detection model:', modelPath);
1052
+ this.logger.state('CARGANDO_MODELO_DETECCION', { modelPath });
924
1053
  const sessionOptions = this.getSessionOptions();
925
- this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
1054
+ try {
1055
+ this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
1056
+ }
1057
+ catch (error) {
1058
+ if (error.message.includes('failed to allocate a buffer')) {
1059
+ this.logger.warn('Fallo en asignación de buffer, intentando con configuración mínima');
1060
+ const fallbackOptions = {
1061
+ executionProviders: ['wasm'],
1062
+ graphOptimizationLevel: 'disabled',
1063
+ logSeverityLevel: 4,
1064
+ enableCpuMemArena: false,
1065
+ enableMemPattern: false,
1066
+ executionMode: 'sequential',
1067
+ interOpNumThreads: 1,
1068
+ intraOpNumThreads: 1,
1069
+ };
1070
+ this.session = await window.ort.InferenceSession.create(modelPath, fallbackOptions);
1071
+ }
1072
+ else {
1073
+ throw error;
1074
+ }
1075
+ }
926
1076
  // Load MobileNet model if classification is enabled and not already loaded
927
1077
  if (this.useDocumentClassification && !this.mobileNetSession) {
928
1078
  if (this.debug) {
@@ -937,7 +1087,7 @@ const JaakStamps = class {
937
1087
  this.emitReadyEvent();
938
1088
  }
939
1089
  else {
940
- this.debugLog('🚀 Using preloaded models');
1090
+ this.logger.state('USANDO_MODELOS_PRECARGADOS', { sessionExists: !!this.session, modelPreloaded: this.isModelPreloaded });
941
1091
  if (this.debug) {
942
1092
  this.statusMessage = "Usando modelos precargados...";
943
1093
  this.statusColor = "#007bff";
@@ -956,7 +1106,7 @@ const JaakStamps = class {
956
1106
  this.detectFrame();
957
1107
  }
958
1108
  catch (err) {
959
- this.debugLog("Error al inicializar:", err);
1109
+ this.logger.error('Error al inicializar detección:', err);
960
1110
  this.statusMessage = "Error al inicializar el detector";
961
1111
  this.statusColor = "#ff6b6b";
962
1112
  this.isLoading = false;
@@ -1038,7 +1188,7 @@ const JaakStamps = class {
1038
1188
  }
1039
1189
  }
1040
1190
  catch (e) {
1041
- this.debugLog("Error en inferencia:", e);
1191
+ this.logger.error('Error en inferencia de modelo:', e);
1042
1192
  // Solo continuar si no hemos completado el proceso
1043
1193
  if (this.captureStep !== 'completed') {
1044
1194
  // On error, wait longer before retrying
@@ -1166,7 +1316,7 @@ const JaakStamps = class {
1166
1316
  if (!this.hasScreenshotTaken) {
1167
1317
  this.lastDetectedBox = bestBox;
1168
1318
  this.takeScreenshot().catch(error => {
1169
- this.debugLog('Error taking screenshot:', error);
1319
+ this.logger.error('Error al tomar captura de pantalla:', error);
1170
1320
  });
1171
1321
  this.hasScreenshotTaken = true;
1172
1322
  // Reset para permitir segunda captura
@@ -1184,6 +1334,7 @@ const JaakStamps = class {
1184
1334
  if (boxes.length === 0) {
1185
1335
  this.statusMessage = "Posicione la identificación dentro del marco";
1186
1336
  this.statusColor = "#ff6b6b";
1337
+ this.logger.debug('Sin detección de documento en el frame');
1187
1338
  }
1188
1339
  else {
1189
1340
  const bestBox = boxes.reduce((best, current) => current.score > best.score ? current : best);
@@ -1193,6 +1344,11 @@ const JaakStamps = class {
1193
1344
  if (allSidesAligned) {
1194
1345
  this.statusMessage = "Identificación perfectamente alineada. Mantenga inmóvil";
1195
1346
  this.statusColor = "#00ff00";
1347
+ this.logger.state('DOCUMENTO_PERFECTAMENTE_ALINEADO', {
1348
+ score: bestBox.score,
1349
+ boxDimensions: { width: bestBox.w, height: bestBox.h },
1350
+ alignedSides: 4
1351
+ });
1196
1352
  }
1197
1353
  else if (alignedSides > 0) {
1198
1354
  this.statusMessage = `Alinee los lados restantes (${alignedSides}/4 lados correctos)`;
@@ -1253,6 +1409,14 @@ const JaakStamps = class {
1253
1409
  async takeScreenshot() {
1254
1410
  if (!this.videoRef || !this.lastDetectedBox)
1255
1411
  return;
1412
+ this.logger.state('INICIANDO_CAPTURA', {
1413
+ captureStep: this.captureStep,
1414
+ detectedBox: this.lastDetectedBox,
1415
+ videoResolution: {
1416
+ width: this.videoRef.videoWidth,
1417
+ height: this.videoRef.videoHeight
1418
+ }
1419
+ });
1256
1420
  // Activar animación
1257
1421
  this.triggerCaptureAnimation();
1258
1422
  // OPTIMIZATION: Reuse capture canvas for full frame
@@ -1292,7 +1456,7 @@ const JaakStamps = class {
1292
1456
  await this.loadMobileNetModel();
1293
1457
  }
1294
1458
  catch (error) {
1295
- this.debugLog('⚠️ Failed to load classification model, continuing without classification:', error);
1459
+ this.logger.warn('Fallo al cargar modelo de clasificación, continuando sin clasificación:', error);
1296
1460
  }
1297
1461
  }
1298
1462
  // Classify the cropped document if model is available
@@ -1300,7 +1464,7 @@ const JaakStamps = class {
1300
1464
  const classification = await this.classifyDocument(croppedCanvas);
1301
1465
  if (classification && classification.class === 'passport') {
1302
1466
  // If it's a passport, skip back capture since passports don't have a back side
1303
- this.debugLog('📄 Passport detected - skipping back capture');
1467
+ this.logger.state('PASAPORTE_DETECTADO_SALTANDO_REVERSO', { classification: classification?.class });
1304
1468
  this.completeProcess(true);
1305
1469
  return;
1306
1470
  }
@@ -1321,14 +1485,23 @@ const JaakStamps = class {
1321
1485
  this.isDetectionPaused = false;
1322
1486
  }, 3000);
1323
1487
  }, 800);
1324
- this.debugLog('📸 FRENTE capturado. Esperando trasera...');
1488
+ this.logger.state('CAPTURA_FRENTE_COMPLETADA', {
1489
+ captureStep: this.captureStep,
1490
+ hasFullFrame: !!this.capturedFullFrame,
1491
+ hasCroppedId: !!this.capturedCroppedId
1492
+ });
1325
1493
  }
1326
1494
  else if (this.captureStep === 'back') {
1327
1495
  // Captura de la trasera usando canvas reutilizado
1328
1496
  this.capturedBackFullFrame = this.captureCanvas.toDataURL('image/png');
1329
1497
  this.capturedBackCroppedId = croppedCanvas.toDataURL('image/png');
1330
1498
  this.completeProcess(false);
1331
- this.debugLog('📸 TRASERA capturada. Proceso completado. Detector detenido. Imágenes emitidas.');
1499
+ this.logger.state('CAPTURA_TRASERA_COMPLETADA', {
1500
+ captureStep: this.captureStep,
1501
+ hasBackFullFrame: !!this.capturedBackFullFrame,
1502
+ hasBackCroppedId: !!this.capturedBackCroppedId,
1503
+ processCompleted: true
1504
+ });
1332
1505
  }
1333
1506
  }
1334
1507
  triggerCaptureAnimation() {
@@ -1352,7 +1525,7 @@ const JaakStamps = class {
1352
1525
  const ctx = this.canvasRef.getContext("2d");
1353
1526
  ctx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height);
1354
1527
  }
1355
- this.debugLog('🛑 Detector de identificación detenido');
1528
+ this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
1356
1529
  }
1357
1530
  resetDetection() {
1358
1531
  this.bestScore = 0;
@@ -1391,6 +1564,13 @@ const JaakStamps = class {
1391
1564
  "Proceso completado (solo frente capturado)" :
1392
1565
  "Proceso de captura completado exitosamente";
1393
1566
  this.statusColor = "#28a745";
1567
+ this.logger.state('PROCESO_COMPLETADO', {
1568
+ skippedBack,
1569
+ hasFrontImages: !!(this.capturedFullFrame && this.capturedCroppedId),
1570
+ hasBackImages: !!(this.capturedBackFullFrame && this.capturedBackCroppedId),
1571
+ totalImages: skippedBack ? 2 : 4,
1572
+ timestamp: new Date().toISOString()
1573
+ });
1394
1574
  // Detener el detector
1395
1575
  this.stopDetection();
1396
1576
  // Emitir evento con las imágenes capturadas
@@ -1442,7 +1622,7 @@ const JaakStamps = class {
1442
1622
  this.cleanup();
1443
1623
  }
1444
1624
  render() {
1445
- return (h("div", { key: '86ff6966e69804bea4faef5a9201480bd7ef5b9a', class: "detector-container" }, h("div", { key: 'aaed4a078d4eff98674f2163a56a1596782612bd', class: "video-container" }, h("video", { key: '6ae6a7c626c9b2b6e6915c826517d1365205b3a0', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: 'c8666e4b4814e633155be738e23ada098fb3be93', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: '968bcd21315afcf054f881647723299e7e57c29b', class: "overlay-mask" }, h("div", { key: '77f8a331f3d9e19b35c11b6c23b06420cbab9fd5', class: "card-outline" }, h("div", { key: '9d0164e7b2dcf6392471151c236127cd57507512', class: "side side-top" }), h("div", { key: '05a003eb49d25c72644aea2511b6519c5241ffb4', class: "side side-right" }), h("div", { key: 'a561ae9a94eb78aacfc24bac717164b3e22f41e5', class: "side side-bottom" }), h("div", { key: '81d64e5b58032c14062fc8efdee417db7f8a9f12', class: "side side-left" }), h("div", { key: '03c02e2c4c73c525965e34c7c47bb74f769d577f', class: "corner corner-tl" }), h("div", { key: '84780aec021ec4e0c7d38f6a659be765c4604ed5', class: "corner corner-tr" }), h("div", { key: '1ea67789c5a38cbc790f911f8f06b55c866d3927', class: "corner corner-bl" }), h("div", { key: 'e0e1999b0c5c37451850203eed4dbc2ff5834261', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: '4909eb28d62b1674436c148ef46cd05a35ff8ec8', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: '5a971c2121cf9962719ad89fc45530b8acccb550', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), this.isVideoActive && (h("div", { key: '109e5885c49212f89d5678d9c9716ebd48614126', class: "camera-controls" }, this.isMultipleCamerasAvailable && (h("button", { key: '7c8ce2cd775c0646c80378cd0ad1f70243443ddf', class: "flip-camera-button", onClick: () => this.flipCamera(), type: "button", title: "Cambiar c\u00E1mara" }, "Girar c\u00E1mara")), h("button", { key: '5d3d066ba7e317fdc33878eeb9f2f7d28c6f65dc', class: "camera-selector-button", onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara" }, "C\u00E1maras"), this.debug && (h("div", { key: '4cbc284f2efbcb90ef91ea2e064bf0aa1e7980af', style: {
1625
+ return (h("div", { key: '87e2e2065012b5792b52b18349490492b5bdc80c', class: "detector-container" }, h("div", { key: '04b8bba38297d39c84ddc01f5a9bf72c65b9bef4', class: "video-container" }, h("video", { key: '905b99e1670fccb2ff5555818f91d0b0241fa29d', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: '3f219b649b8d4f961b15256b8586451c2a737eb8', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: 'f8d6742689d4446897ca48c2407cb3d3fab643e9', class: "overlay-mask" }, h("div", { key: '630a06c530f168cc0b71286adbf48fd922eb710d', class: "card-outline" }, h("div", { key: 'ce8c043ad41d5574cd0c4d737dec08b9f7fee927', class: "side side-top" }), h("div", { key: 'e90ead592f84e5df98143bc88714e70f32684204', class: "side side-right" }), h("div", { key: '31d6778c5036e42a3addb26c732149abc5a18ef7', class: "side side-bottom" }), h("div", { key: 'f85c31e77f024f9070c706b06b91d62a7fdbc357', class: "side side-left" }), h("div", { key: '27b362711bc53019483c0d36742d97e10ae9b9ce', class: "corner corner-tl" }), h("div", { key: '03b55893610684d690987c2eacf3c4e5d0c3bbe5', class: "corner corner-tr" }), h("div", { key: 'b6bf31327925c45a7fa8e1fd5ab2f25b824a03a2', class: "corner corner-bl" }), h("div", { key: '0737e0c82f8ad9703ce2546f3e594db6d50cafde', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: '145f8383c496e151bd6ef4f5841292adeba083c4', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: 'fcdedebc31eddd21b978dd63e93c348d324b47da', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), this.isVideoActive && (h("div", { key: 'dc9157bf7bed3b6439db742055201e2c4eddec84', class: "camera-controls" }, this.isMultipleCamerasAvailable && (h("button", { key: '6b052562ccb203dbf3cc8b627f31ed2435559f80', class: "flip-camera-button", onClick: () => this.flipCamera(), type: "button", title: "Cambiar c\u00E1mara" }, "Girar c\u00E1mara")), h("button", { key: 'f9fad1757d01f0cb45f65bb2088bd1f0bc0e4e8d', class: "camera-selector-button", onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara" }, "C\u00E1maras"), this.debug && (h("div", { key: '5362fb33508c7f903b09f65fdb449b608804260d', style: {
1446
1626
  position: 'absolute',
1447
1627
  top: '50px',
1448
1628
  right: '0',
@@ -1452,10 +1632,10 @@ const JaakStamps = class {
1452
1632
  fontSize: '10px',
1453
1633
  borderRadius: '4px',
1454
1634
  whiteSpace: 'nowrap'
1455
- } }, "C\u00E1maras: ", this.availableCameras.length, h("br", { key: 'df214515289add2cc73f989bfacb59069334a0e6' }), "M\u00FAltiples: ", this.isMultipleCamerasAvailable ? 'Sí' : 'No', h("br", { key: 'aa2b1784781b5b45d24139e2b6cc4865d9f3a00c' }), "Selector: ", this.showCameraSelector ? 'Visible' : 'Oculto', h("br", { key: 'fb95b2b58c9fb89f632d85a9271b7788d45a66c0' }), "Video: ", this.isVideoActive ? 'Activo' : 'Inactivo')))), this.showCameraSelector && this.availableCameras.length > 0 && (h("div", { key: 'ec1c90c5a6c84e3ef75724e7e600a60714545608', class: "camera-selector-dropdown" }, h("div", { key: 'ea118817b6563c78b7fbf5ee66af2e38a441a8b7', class: "camera-selector-header" }, h("span", { key: '52603259d50ffb7aa6ea5753762f0f0d018b39ff' }, "Seleccionar C\u00E1mara"), h("button", { key: '422ae0643f5d1451cf539b3a77becbf44904a484', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd5acef7ba0b1645c52fb3ea7b3c64e70fa187a5f', class: "camera-list" }, this.availableCameras.map((camera) => (h("button", { key: camera.deviceId, class: `camera-option ${this.selectedCameraId === camera.deviceId ? 'selected' : ''}`, onClick: () => {
1635
+ } }, "C\u00E1maras: ", this.availableCameras.length, h("br", { key: '5ead451a54a59c885f62740d0a0ce43ba8b674e4' }), "M\u00FAltiples: ", this.isMultipleCamerasAvailable ? 'Sí' : 'No', h("br", { key: '9a92514d07576ed7052eaa3a418c00013ef7c479' }), "Selector: ", this.showCameraSelector ? 'Visible' : 'Oculto', h("br", { key: 'd06abebad3b2e3c19aa22e511e0c4cfd4425b8b2' }), "Video: ", this.isVideoActive ? 'Activo' : 'Inactivo')))), this.showCameraSelector && this.availableCameras.length > 0 && (h("div", { key: '93a09ce3bb108175b55944a71c21e86e58de55be', class: "camera-selector-dropdown" }, h("div", { key: '7ceb4c8826945ab2a9da106a88a44cbb5490e92a', class: "camera-selector-header" }, h("span", { key: 'fa98621700584d8021ff57eca6f804b6eb792c5b' }, "Seleccionar C\u00E1mara"), h("button", { key: 'ce6477a1494f58fb5974dd7c9e62bd7d041572c5', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'eff226b006c95fafce2566947155e13ee68f9a02', class: "camera-list" }, this.availableCameras.map((camera) => (h("button", { key: camera.deviceId, class: `camera-option ${this.selectedCameraId === camera.deviceId ? 'selected' : ''}`, onClick: () => {
1456
1636
  this.switchCamera(camera.deviceId);
1457
1637
  this.toggleCameraSelector();
1458
- }, type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${this.availableCameras.indexOf(camera) + 1}`), this.selectedCameraId === camera.deviceId && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'b670e7bf441559bf156874dbe3bbf83dbff2fa0d', class: "device-info" }, h("small", { key: '4d333925c1ff4197b6009414ed694b0a0eb06159' }, "Dispositivo: ", this.deviceType)))))), this.isCapturing && (h("div", { key: '21f7ea0e50575226daba8607ab4bccc5029aa179', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: 'c2350cc43d3a119391f62f2cf771ed5afcdb3558', class: "flip-animation" }, h("div", { key: 'a91de9e8662a4750555bce2a879b89f9216b8156', class: "id-card-icon" }), h("div", { key: 'def1c2534e1aae27e262deb6fd26a73ff0c8cb9b', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: 'acbbfb6d07e4f6bf343ff18af2d55979e76db3d4', class: "success-animation" }, h("div", { key: 'f5acd84325226e6b1ede1180335d7495a6b2d95e', class: "check-icon" }), h("div", { key: '393fd8300ce48e59bd3ef94f0e895abc3a938c1f', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: 'ce0d1baebab8bf0e18531a26b549f38e349a0d72', class: "loading-overlay" }, h("div", { key: '14c76fa00ddd814f37bd9c6dc57f761119886566', class: "loading-spinner" }), h("div", { key: '87ccb288f927e61ec126d83f44061699810c9e06', class: "loading-text" }, this.statusMessage))), this.debug && (h("div", { key: '943dff6e2772f2595a95116e8943866f97cada6e', class: "status-bar" }, h("div", { key: '00fe53b71ef94d51636259f106e4518df910848d', class: "status-message", style: { 'color': this.statusColor } }, this.statusMessage))), h("div", { key: '4c3bd150006473d9540fb1418f11f45fb96d1e56', class: "watermark" }, h("img", { key: 'c634701f52a4422c7be5b4007bf34ba4aeb4ebcc', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1638
+ }, type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${this.availableCameras.indexOf(camera) + 1}`), this.selectedCameraId === camera.deviceId && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '617d33caa5d3b3e6dca56d479b27c0ac7ed19c2c', class: "device-info" }, h("small", { key: 'fba56ac1238c33864f8dba1d8bd9a5661e11d770' }, "Dispositivo: ", this.deviceType)))))), this.isCapturing && (h("div", { key: 'b46982a0b949ee9d50d0a819c59b24dceec9d7b2', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: 'c82773fffca8ec53949197a105e1e11bf0f9b294', class: "flip-animation" }, h("div", { key: 'ad52c9ec91132bf5ed94e36f621489a66b0bf680', class: "id-card-icon" }), h("div", { key: '2be407ee84beddc5ce968a9a94d9014f416cde54', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: 'f20b6be925178a123de99e535e893b7c09391dee', class: "success-animation" }, h("div", { key: 'd3ae65d40943f73bc92e9529764ded454adc5dbf', class: "check-icon" }), h("div", { key: 'f2b03082a106c69f09d384ba3b1cb4a636d3a189', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: '65736e303d9eb1c3f6f2dccb5cc0d27c571ac9c8', class: "loading-overlay" }, h("div", { key: '5863d855d1caea9202a9f3d0a29919985ddbb926', class: "loading-spinner" }), h("div", { key: 'aa5a16c96ac6bfa43846ba06346fad6dce39dfbb', class: "loading-text" }, this.statusMessage))), this.debug && (h("div", { key: '308c664b07d2c2427c38f766fc9eb78d27843f67', class: "status-bar" }, h("div", { key: '3884b311710d7f373a508eb492771e503e68f648', class: "status-message", style: { 'color': this.statusColor } }, this.statusMessage))), h("div", { key: '3125cfecda03d3ab9e80e0d9d5674f1cc167e2f8', class: "watermark" }, h("img", { key: '5ef43a18aea74f570db0d61f4fd86c3becf1d23e', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1459
1639
  }
1460
1640
  };
1461
1641
  JaakStamps.style = myComponentCss;