@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.
@@ -68,34 +68,65 @@ export class JaakStamps {
68
68
  CONFIDENCE_THRESHOLD = 0.6;
69
69
  // ISO/IEC 7810 ID-1 standard dimensions (85.60mm x 53.98mm)
70
70
  ID1_ASPECT_RATIO = 85.60 / 53.98; // 1.5863320574...
71
- debugLog(...args) {
72
- if (this.debug) {
73
- console.log(...args);
71
+ logger = {
72
+ info: (...args) => {
73
+ if (this.debug) {
74
+ console.log(`[JAAK-STAMPS] [INFO] [${new Date().toLocaleTimeString()}]`, ...args);
75
+ }
76
+ },
77
+ warn: (...args) => {
78
+ if (this.debug) {
79
+ console.warn(`[JAAK-STAMPS] [WARN] [${new Date().toLocaleTimeString()}]`, ...args);
80
+ }
81
+ },
82
+ error: (...args) => {
83
+ if (this.debug) {
84
+ console.error(`[JAAK-STAMPS] [ERROR] [${new Date().toLocaleTimeString()}]`, ...args);
85
+ }
86
+ },
87
+ debug: (...args) => {
88
+ if (this.debug) {
89
+ console.debug(`[JAAK-STAMPS] [DEBUG] [${new Date().toLocaleTimeString()}]`, ...args);
90
+ }
91
+ },
92
+ state: (state, data) => {
93
+ if (this.debug) {
94
+ console.log(`[JAAK-STAMPS] [STATE] [${new Date().toLocaleTimeString()}] ${state}`, data || '');
95
+ }
96
+ },
97
+ performance: (operation, duration) => {
98
+ if (this.debug) {
99
+ console.log(`[JAAK-STAMPS] [PERF] [${new Date().toLocaleTimeString()}] ${operation}: ${duration}ms`);
100
+ }
74
101
  }
75
- }
102
+ };
76
103
  validateMaskSize() {
77
104
  if (this.maskSize < 50 || this.maskSize > 100) {
78
- console.warn(`maskSize debe estar entre 50 y 100. Valor actual: ${this.maskSize}. Usando valor por defecto: 90`);
105
+ this.logger.warn(`Propiedad maskSize inválida. Valor: ${this.maskSize}, esperado: 50-100. Usando valor por defecto: 90`);
79
106
  this.maskSize = 90;
80
107
  }
81
108
  }
82
109
  validateCropMargin() {
83
110
  if (this.cropMargin < 0 || this.cropMargin > 100) {
84
- console.warn(`cropMargin debe estar entre 0 y 100. Valor actual: ${this.cropMargin}. Usando valor por defecto: 0`);
111
+ this.logger.warn(`Propiedad cropMargin inválida. Valor: ${this.cropMargin}, esperado: 0-100. Usando valor por defecto: 0`);
85
112
  this.cropMargin = 0;
86
113
  }
87
114
  }
88
115
  validatePreferredCamera() {
89
116
  const validOptions = ['auto', 'front', 'back'];
90
117
  if (!validOptions.includes(this.preferredCamera)) {
91
- console.warn(`preferredCamera debe ser uno de: ${validOptions.join(', ')}. Valor actual: ${this.preferredCamera}. Usando valor por defecto: 'auto'`);
118
+ this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
92
119
  this.preferredCamera = 'auto';
93
120
  }
94
121
  }
95
122
  emitReadyEvent() {
96
123
  const isDocumentReady = !!window.ort && this.isModelPreloaded;
97
124
  this.isReady.emit(isDocumentReady);
98
- this.debugLog('🟢 isReady event emitted:', isDocumentReady);
125
+ this.logger.state('COMPONENTE_LISTO', {
126
+ ortLibraryLoaded: !!window.ort,
127
+ modelPreloaded: this.isModelPreloaded,
128
+ isReady: isDocumentReady
129
+ });
99
130
  }
100
131
  isRearCamera(stream) {
101
132
  const videoTrack = stream.getVideoTracks()[0];
@@ -118,7 +149,11 @@ export class JaakStamps {
118
149
  else {
119
150
  this.deviceType = 'desktop';
120
151
  }
121
- this.debugLog('📱 Device type detected:', this.deviceType);
152
+ this.logger.state('DISPOSITIVO_DETECTADO', {
153
+ deviceType: this.deviceType,
154
+ userAgent: navigator.userAgent,
155
+ screenDimensions: { width: window.innerWidth, height: window.innerHeight }
156
+ });
122
157
  // Enumerate available cameras
123
158
  await this.enumerateAndDetectCameras();
124
159
  // Load user preference
@@ -129,7 +164,7 @@ export class JaakStamps {
129
164
  // First, check if we have permission to enumerate devices
130
165
  const permissionStatus = await this.checkCameraPermission();
131
166
  if (permissionStatus === 'denied') {
132
- this.debugLog(' Camera permission denied');
167
+ this.logger.error('Permiso de cámara denegado por el usuario');
133
168
  this.statusMessage = "Permiso de cámara denegado";
134
169
  this.statusColor = "#ff6b6b";
135
170
  return;
@@ -143,7 +178,7 @@ export class JaakStamps {
143
178
  const devices = await navigator.mediaDevices.enumerateDevices();
144
179
  this.availableCameras = devices.filter(device => device.kind === 'videoinput');
145
180
  this.isMultipleCamerasAvailable = this.availableCameras.length > 1;
146
- this.debugLog('📹 Available cameras:', {
181
+ this.logger.state('CAMARAS_DETECTADAS', {
147
182
  count: this.availableCameras.length,
148
183
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
149
184
  cameras: this.availableCameras.map(cam => ({
@@ -155,7 +190,7 @@ export class JaakStamps {
155
190
  this.setInitialCameraPreference();
156
191
  }
157
192
  catch (error) {
158
- this.debugLog('Error enumerating cameras:', error);
193
+ this.logger.error('Error al enumerar cámaras disponibles:', error);
159
194
  this.handleCameraPermissionError(error);
160
195
  }
161
196
  }
@@ -168,7 +203,7 @@ export class JaakStamps {
168
203
  return permission.state;
169
204
  }
170
205
  catch (error) {
171
- this.debugLog('⚠️ Could not check camera permission:', error);
206
+ this.logger.warn('No se pudo verificar permisos de cámara:', error);
172
207
  return 'prompt';
173
208
  }
174
209
  }
@@ -211,11 +246,11 @@ export class JaakStamps {
211
246
  !camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
212
247
  if (frontCamera) {
213
248
  this.selectedCameraId = frontCamera.deviceId;
214
- this.debugLog('👤 User selected front camera:', frontCamera.label);
249
+ this.logger.state('CAMARA_FRONTAL_SELECCIONADA', { label: frontCamera.label, deviceId: frontCamera.deviceId });
215
250
  }
216
251
  else {
217
252
  this.selectedCameraId = this.availableCameras[0].deviceId;
218
- this.debugLog('⚠️ Front camera not found, using first available:', this.availableCameras[0].label);
253
+ this.logger.warn('Cámara frontal no encontrada, usando primera disponible:', this.availableCameras[0].label);
219
254
  }
220
255
  }
221
256
  else if (this.preferredCamera === 'back') {
@@ -226,11 +261,11 @@ export class JaakStamps {
226
261
  camera.label.toLowerCase().includes('environment'));
227
262
  if (backCamera) {
228
263
  this.selectedCameraId = backCamera.deviceId;
229
- this.debugLog('📷 User selected back camera:', backCamera.label);
264
+ this.logger.state('CAMARA_TRASERA_SELECCIONADA', { label: backCamera.label, deviceId: backCamera.deviceId });
230
265
  }
231
266
  else {
232
267
  this.selectedCameraId = this.availableCameras[0].deviceId;
233
- this.debugLog('⚠️ Back camera not found, using first available:', this.availableCameras[0].label);
268
+ this.logger.warn('Cámara trasera no encontrada, usando primera disponible:', this.availableCameras[0].label);
234
269
  }
235
270
  }
236
271
  else {
@@ -243,17 +278,17 @@ export class JaakStamps {
243
278
  camera.label.toLowerCase().includes('environment'));
244
279
  if (rearCamera) {
245
280
  this.selectedCameraId = rearCamera.deviceId;
246
- this.debugLog('📱 Auto-selected rear camera for mobile:', rearCamera.label);
281
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear', label: rearCamera.label, deviceId: rearCamera.deviceId });
247
282
  }
248
283
  else {
249
284
  this.selectedCameraId = this.availableCameras[0].deviceId;
250
- this.debugLog('📱 Rear camera not found, using first available:', this.availableCameras[0].label);
285
+ this.logger.warn('Cámara trasera no encontrada en mobile, usando primera disponible:', this.availableCameras[0].label);
251
286
  }
252
287
  }
253
288
  else {
254
289
  // For desktop, use first available camera (usually the only one)
255
290
  this.selectedCameraId = this.availableCameras[0].deviceId;
256
- this.debugLog('💻 Auto-selected desktop camera:', this.availableCameras[0].label);
291
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', { label: this.availableCameras[0].label, deviceId: this.availableCameras[0].deviceId });
257
292
  }
258
293
  }
259
294
  }
@@ -267,12 +302,12 @@ export class JaakStamps {
267
302
  if (isStillAvailable) {
268
303
  this.selectedCameraId = preference.cameraId;
269
304
  this.preferredCameraFacing = preference.facing;
270
- this.debugLog('💾 Loaded camera preference:', preference);
305
+ this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
271
306
  }
272
307
  }
273
308
  }
274
309
  catch (error) {
275
- this.debugLog('⚠️ Error loading camera preference:', error);
310
+ this.logger.warn('Error al cargar preferencia de cámara:', error);
276
311
  }
277
312
  }
278
313
  saveCameraPreference() {
@@ -283,10 +318,10 @@ export class JaakStamps {
283
318
  timestamp: Date.now()
284
319
  };
285
320
  localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
286
- this.debugLog('💾 Saved camera preference:', preference);
321
+ this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
287
322
  }
288
323
  catch (error) {
289
- this.debugLog('⚠️ Error saving camera preference:', error);
324
+ this.logger.warn('Error al guardar preferencia de cámara:', error);
290
325
  }
291
326
  }
292
327
  async switchCamera(cameraId) {
@@ -296,7 +331,7 @@ export class JaakStamps {
296
331
  // Check if the selected camera is still available
297
332
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
298
333
  if (!selectedCamera) {
299
- this.debugLog(' Selected camera not found, re-enumerating...');
334
+ this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
300
335
  await this.enumerateAndDetectCameras();
301
336
  return;
302
337
  }
@@ -320,10 +355,10 @@ export class JaakStamps {
320
355
  this.saveCameraPreference();
321
356
  // Setup new camera with error handling
322
357
  await this.setupCameraWithRetry();
323
- this.debugLog('🔄 Switched to camera:', selectedCamera.label);
358
+ this.logger.state('CAMARA_CAMBIADA', { label: selectedCamera.label, deviceId: selectedCamera.deviceId });
324
359
  }
325
360
  catch (error) {
326
- this.debugLog('Error switching camera:', error);
361
+ this.logger.error('Error al cambiar de cámara:', error);
327
362
  this.handleCameraPermissionError(error);
328
363
  }
329
364
  }
@@ -338,7 +373,7 @@ export class JaakStamps {
338
373
  return; // Success
339
374
  }
340
375
  catch (error) {
341
- this.debugLog(`❌ Camera setup attempt ${attempt} failed:`, error);
376
+ this.logger.error(`Intento ${attempt} de configuración de cámara fallido:`, error);
342
377
  if (attempt === maxRetries) {
343
378
  // Last attempt failed, handle the error
344
379
  this.statusMessage = "Error al configurar la cámara";
@@ -358,7 +393,7 @@ export class JaakStamps {
358
393
  }
359
394
  toggleCameraSelector() {
360
395
  this.showCameraSelector = !this.showCameraSelector;
361
- this.debugLog('📹 Camera selector toggled:', {
396
+ this.logger.state('SELECTOR_CAMARA_TOGGLE', {
362
397
  showCameraSelector: this.showCameraSelector,
363
398
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
364
399
  availableCameras: this.availableCameras.length,
@@ -374,6 +409,13 @@ export class JaakStamps {
374
409
  await this.switchCamera(nextCamera.deviceId);
375
410
  }
376
411
  async componentDidLoad() {
412
+ this.logger.state('COMPONENTE_INICIALIZANDO', {
413
+ debug: this.debug,
414
+ maskSize: this.maskSize,
415
+ cropMargin: this.cropMargin,
416
+ useDocumentClassification: this.useDocumentClassification,
417
+ preferredCamera: this.preferredCamera
418
+ });
377
419
  if (this.debug) {
378
420
  // Show detailed initialization loading state only in debug mode
379
421
  this.isLoading = true;
@@ -438,7 +480,7 @@ export class JaakStamps {
438
480
  this.canvasRef.height = rect.height;
439
481
  // Update mask positioning based on container and video dimensions
440
482
  this.updateMaskDimensions(rect);
441
- this.debugLog('📐 Canvas resized:', { width: rect.width, height: rect.height });
483
+ this.logger.debug('Canvas redimensionado:', { width: rect.width, height: rect.height });
442
484
  }
443
485
  }
444
486
  updateMaskDimensions(containerRect) {
@@ -498,7 +540,7 @@ export class JaakStamps {
498
540
  this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
499
541
  // Mark mask as ready now that dimensions are calculated
500
542
  this.isMaskReady = true;
501
- this.debugLog('🎯 Mask dimensions updated:', {
543
+ this.logger.state('DIMENSIONES_MASCARA_ACTUALIZADAS', {
502
544
  video: { width: videoWidth, height: videoHeight },
503
545
  displayed: { width: displayedVideoWidth, height: displayedVideoHeight },
504
546
  mask: { widthPercent: maskWidthPercent, heightPercent: maskHeightPercent },
@@ -520,7 +562,7 @@ export class JaakStamps {
520
562
  this.captureCtx = this.captureCanvas.getContext('2d', {
521
563
  alpha: false
522
564
  });
523
- this.debugLog('🎨 Canvas pool initialized for performance');
565
+ this.logger.state('CANVAS_POOL_INICIALIZADO', { preprocessCanvasSize: this.INPUT_SIZE });
524
566
  }
525
567
  disconnectedCallback() {
526
568
  this.cleanup();
@@ -568,7 +610,7 @@ export class JaakStamps {
568
610
  }
569
611
  async preloadModel() {
570
612
  if (this.isModelPreloaded || this.session) {
571
- this.debugLog('🚀 Model already preloaded or session exists');
613
+ this.logger.state('MODELO_YA_PRECARGADO', { sessionExists: !!this.session, modelPreloaded: this.isModelPreloaded });
572
614
  return { success: true, message: 'Model already loaded' };
573
615
  }
574
616
  try {
@@ -576,12 +618,39 @@ export class JaakStamps {
576
618
  this.statusMessage = "Precargando modelos...";
577
619
  this.statusColor = "#007bff";
578
620
  const modelPath = this.MODEL_PATH;
579
- this.debugLog('🤖 Preloading detection model:', modelPath);
621
+ this.logger.state('PRECARGANDO_MODELO_DETECCION', { modelPath });
580
622
  // Configure ONNX Runtime with device-specific optimizations
581
623
  const sessionOptions = this.getSessionOptions();
582
- this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
624
+ const deviceInfo = this.getDeviceMemoryInfo();
625
+ try {
626
+ this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
627
+ }
628
+ catch (error) {
629
+ if (error.message.includes('failed to allocate a buffer')) {
630
+ this.logger.warn('Fallo en asignación de buffer durante precarga, intentando con configuración mínima');
631
+ const fallbackOptions = {
632
+ executionProviders: ['wasm'],
633
+ graphOptimizationLevel: 'disabled',
634
+ logSeverityLevel: 4,
635
+ enableCpuMemArena: false,
636
+ enableMemPattern: false,
637
+ executionMode: 'sequential',
638
+ interOpNumThreads: 1,
639
+ intraOpNumThreads: 1,
640
+ };
641
+ this.session = await window.ort.InferenceSession.create(modelPath, fallbackOptions);
642
+ }
643
+ else {
644
+ throw error;
645
+ }
646
+ }
583
647
  // Preload MobileNet model and classes only if classification is enabled
648
+ // For low memory devices, load sequentially to avoid memory pressure
584
649
  if (this.useDocumentClassification) {
650
+ if (deviceInfo.isLowMemory) {
651
+ this.logger.state('CARGA_SECUENCIAL_MODELOS', { reason: 'low memory device' });
652
+ await new Promise(resolve => setTimeout(resolve, 1000));
653
+ }
585
654
  await this.loadMobileNetModel();
586
655
  }
587
656
  this.isModelPreloaded = true;
@@ -589,11 +658,15 @@ export class JaakStamps {
589
658
  this.statusMessage = "Modelos precargados. Listo para comenzar detección";
590
659
  this.statusColor = "#28a745";
591
660
  this.emitReadyEvent();
592
- this.debugLog(' Models preloaded successfully');
661
+ this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', {
662
+ detectionModel: !!this.session,
663
+ classificationModel: !!this.mobileNetSession,
664
+ useClassification: this.useDocumentClassification
665
+ });
593
666
  return { success: true, message: 'Models preloaded successfully' };
594
667
  }
595
668
  catch (error) {
596
- this.debugLog('Error preloading models:', error);
669
+ this.logger.error('Error al precargar modelos:', error);
597
670
  this.isLoading = false;
598
671
  this.statusMessage = "Error al precargar los modelos";
599
672
  this.statusColor = "#ff6b6b";
@@ -621,7 +694,7 @@ export class JaakStamps {
621
694
  }
622
695
  async setPreferredCamera(camera) {
623
696
  this.preferredCamera = camera;
624
- this.debugLog('🎯 Camera preference changed to:', camera);
697
+ this.logger.state('PREFERENCIA_CAMARA_CAMBIADA', { newPreference: camera });
625
698
  // Re-detect and apply new camera preference
626
699
  await this.enumerateAndDetectCameras();
627
700
  // If video is active, switch to the new preferred camera
@@ -636,21 +709,42 @@ export class JaakStamps {
636
709
  }
637
710
  async loadMobileNetModel() {
638
711
  try {
639
- this.debugLog('🤖 Loading MobileNet model...');
712
+ this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
640
713
  // Load class map
641
714
  const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
642
715
  if (!classResponse.ok) {
643
716
  throw new Error(`Failed to load class map: ${this.MOBILENET_CLASSES_PATH}`);
644
717
  }
645
718
  this.mobileNetClassMap = await classResponse.json();
646
- this.debugLog('📋 MobileNet classes loaded:', this.mobileNetClassMap);
719
+ this.logger.state('CLASES_MOBILENET_CARGADAS', { classCount: Object.keys(this.mobileNetClassMap).length });
647
720
  // Load model
648
721
  const sessionOptions = this.getSessionOptions();
649
- this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
650
- this.debugLog('✅ MobileNet model loaded successfully');
722
+ try {
723
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
724
+ }
725
+ catch (error) {
726
+ if (error.message.includes('failed to allocate a buffer')) {
727
+ this.logger.warn('Fallo en asignación de buffer de MobileNet, intentando con configuración mínima');
728
+ const fallbackOptions = {
729
+ executionProviders: ['wasm'],
730
+ graphOptimizationLevel: 'disabled',
731
+ logSeverityLevel: 4,
732
+ enableCpuMemArena: false,
733
+ enableMemPattern: false,
734
+ executionMode: 'sequential',
735
+ interOpNumThreads: 1,
736
+ intraOpNumThreads: 1,
737
+ };
738
+ this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
739
+ }
740
+ else {
741
+ throw error;
742
+ }
743
+ }
744
+ this.logger.state('MODELO_MOBILENET_CARGADO_EXITOSAMENTE', { sessionCreated: !!this.mobileNetSession });
651
745
  }
652
746
  catch (error) {
653
- this.debugLog('Error loading MobileNet model:', error);
747
+ this.logger.error('Error al cargar modelo MobileNet:', error);
654
748
  throw error;
655
749
  }
656
750
  }
@@ -677,11 +771,11 @@ export class JaakStamps {
677
771
  }
678
772
  async classifyDocument(canvas) {
679
773
  if (!this.mobileNetSession || !this.mobileNetClassMap) {
680
- this.debugLog('⚠️ MobileNet model not loaded');
774
+ this.logger.warn('Modelo MobileNet no está cargado, saltando clasificación');
681
775
  return null;
682
776
  }
683
777
  try {
684
- this.debugLog('🔍 Classifying document...');
778
+ this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
685
779
  // Preprocess image for MobileNet
686
780
  const inputTensor = this.preprocessMobileNet(canvas);
687
781
  // Run inference
@@ -692,10 +786,11 @@ export class JaakStamps {
692
786
  const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
693
787
  const confidence = output[maxIdx];
694
788
  const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
695
- this.debugLog('📄 Document classification result:', {
789
+ this.logger.state('DOCUMENTO_CLASIFICADO', {
696
790
  class: className,
697
791
  confidence: confidence.toFixed(3),
698
- classIndex: maxIdx
792
+ classIndex: maxIdx,
793
+ timestamp: Date.now()
699
794
  });
700
795
  return {
701
796
  class: className,
@@ -704,7 +799,7 @@ export class JaakStamps {
704
799
  };
705
800
  }
706
801
  catch (error) {
707
- this.debugLog('Error classifying document:', error);
802
+ this.logger.error('Error al clasificar documento:', error);
708
803
  return null;
709
804
  }
710
805
  }
@@ -746,7 +841,7 @@ export class JaakStamps {
746
841
  }
747
842
  this.mobileNetClassMap = undefined;
748
843
  this.isModelPreloaded = false;
749
- this.debugLog('🧹 Canvas pool cleaned up');
844
+ this.logger.state('CANVAS_POOL_LIMPIADO', { timestamp: Date.now() });
750
845
  }
751
846
  async getMaxResolution() {
752
847
  try {
@@ -796,18 +891,19 @@ export class JaakStamps {
796
891
  constraints.width = { ideal: maxWidth };
797
892
  constraints.height = { ideal: maxHeight };
798
893
  }
799
- this.debugLog('📐 Resolution capabilities:', {
894
+ this.logger.state('CAPACIDADES_RESOLUCION_DETECTADAS', {
800
895
  maxWidth: capabilities.width.max,
801
896
  maxHeight: capabilities.height.max,
802
897
  selectedWidth: constraints.width.ideal,
803
898
  selectedHeight: constraints.height.ideal,
804
- isTablet
899
+ isTablet,
900
+ deviceType: this.deviceType
805
901
  });
806
902
  }
807
903
  return constraints;
808
904
  }
809
905
  catch (err) {
810
- this.debugLog('⚠️ Could not get capabilities, using fallback');
906
+ this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
811
907
  // Optimized fallback for tablets
812
908
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
813
909
  const fallbackConstraints = {
@@ -840,8 +936,11 @@ export class JaakStamps {
840
936
  // Determine if video should be mirrored
841
937
  const isRear = this.isRearCamera(stream);
842
938
  this.shouldMirrorVideo = !isRear;
843
- this.debugLog('📹 Rear camera:', isRear);
844
- this.debugLog('🪞 Should mirror video:', this.shouldMirrorVideo);
939
+ this.logger.state('CAMARA_CONFIGURADA', {
940
+ isRearCamera: isRear,
941
+ shouldMirrorVideo: this.shouldMirrorVideo,
942
+ videoActive: this.isVideoActive
943
+ });
845
944
  return new Promise((resolve) => {
846
945
  this.videoRef.onloadedmetadata = async () => {
847
946
  await this.videoRef.play();
@@ -858,7 +957,7 @@ export class JaakStamps {
858
957
  }
859
958
  }
860
959
  catch (err) {
861
- this.debugLog("❌ No se pudo acceder a la cámara:", err);
960
+ this.logger.error('No se pudo acceder a la cámara:', err);
862
961
  this.handleCameraPermissionError(err);
863
962
  }
864
963
  }
@@ -882,15 +981,39 @@ export class JaakStamps {
882
981
  const transposedData = new Float32Array(R.concat(G, B));
883
982
  return new window.ort.Tensor("float32", transposedData, [1, 3, this.INPUT_SIZE, this.INPUT_SIZE]);
884
983
  }
984
+ getDeviceMemoryInfo() {
985
+ const nav = navigator;
986
+ const memory = nav.deviceMemory || nav.hardwareConcurrency || 4;
987
+ const connection = nav.connection || nav.mozConnection || nav.webkitConnection;
988
+ const isSlowConnection = connection && (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g');
989
+ return {
990
+ estimatedRAM: memory,
991
+ isLowMemory: memory <= 4,
992
+ isSlowConnection: isSlowConnection
993
+ };
994
+ }
885
995
  getSessionOptions() {
996
+ const deviceInfo = this.getDeviceMemoryInfo();
997
+ if (deviceInfo.isLowMemory) {
998
+ return {
999
+ executionProviders: ['wasm'],
1000
+ graphOptimizationLevel: 'basic',
1001
+ logSeverityLevel: 4,
1002
+ logVerbosityLevel: 0,
1003
+ enableCpuMemArena: false,
1004
+ enableMemPattern: false,
1005
+ executionMode: 'sequential',
1006
+ interOpNumThreads: 1,
1007
+ intraOpNumThreads: 1,
1008
+ };
1009
+ }
886
1010
  return {
887
1011
  executionProviders: [
888
- // Try WebGL first for GPU acceleration, fallback to WASM
889
1012
  'webgl',
890
1013
  'wasm'
891
1014
  ],
892
- graphOptimizationLevel: 'all', // Maximum optimization
893
- logSeverityLevel: this.debug ? 2 : 4, // Reduce logging overhead in production
1015
+ graphOptimizationLevel: 'all',
1016
+ logSeverityLevel: this.debug ? 2 : 4,
894
1017
  logVerbosityLevel: 0,
895
1018
  enableCpuMemArena: true,
896
1019
  enableMemPattern: true,
@@ -900,6 +1023,12 @@ export class JaakStamps {
900
1023
  };
901
1024
  }
902
1025
  async startDetection() {
1026
+ this.logger.state('INICIANDO_DETECCION', {
1027
+ sessionExists: !!this.session,
1028
+ modelPreloaded: this.isModelPreloaded,
1029
+ videoActive: this.isVideoActive,
1030
+ captureStep: this.captureStep
1031
+ });
903
1032
  try {
904
1033
  // Check if model is already preloaded
905
1034
  if (!this.session) {
@@ -913,9 +1042,30 @@ export class JaakStamps {
913
1042
  this.statusColor = "#007bff";
914
1043
  }
915
1044
  const modelPath = this.MODEL_PATH;
916
- this.debugLog('🤖 Loading detection model:', modelPath);
1045
+ this.logger.state('CARGANDO_MODELO_DETECCION', { modelPath });
917
1046
  const sessionOptions = this.getSessionOptions();
918
- this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
1047
+ try {
1048
+ this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
1049
+ }
1050
+ catch (error) {
1051
+ if (error.message.includes('failed to allocate a buffer')) {
1052
+ this.logger.warn('Fallo en asignación de buffer, intentando con configuración mínima');
1053
+ const fallbackOptions = {
1054
+ executionProviders: ['wasm'],
1055
+ graphOptimizationLevel: 'disabled',
1056
+ logSeverityLevel: 4,
1057
+ enableCpuMemArena: false,
1058
+ enableMemPattern: false,
1059
+ executionMode: 'sequential',
1060
+ interOpNumThreads: 1,
1061
+ intraOpNumThreads: 1,
1062
+ };
1063
+ this.session = await window.ort.InferenceSession.create(modelPath, fallbackOptions);
1064
+ }
1065
+ else {
1066
+ throw error;
1067
+ }
1068
+ }
919
1069
  // Load MobileNet model if classification is enabled and not already loaded
920
1070
  if (this.useDocumentClassification && !this.mobileNetSession) {
921
1071
  if (this.debug) {
@@ -930,7 +1080,7 @@ export class JaakStamps {
930
1080
  this.emitReadyEvent();
931
1081
  }
932
1082
  else {
933
- this.debugLog('🚀 Using preloaded models');
1083
+ this.logger.state('USANDO_MODELOS_PRECARGADOS', { sessionExists: !!this.session, modelPreloaded: this.isModelPreloaded });
934
1084
  if (this.debug) {
935
1085
  this.statusMessage = "Usando modelos precargados...";
936
1086
  this.statusColor = "#007bff";
@@ -949,7 +1099,7 @@ export class JaakStamps {
949
1099
  this.detectFrame();
950
1100
  }
951
1101
  catch (err) {
952
- this.debugLog("Error al inicializar:", err);
1102
+ this.logger.error('Error al inicializar detección:', err);
953
1103
  this.statusMessage = "Error al inicializar el detector";
954
1104
  this.statusColor = "#ff6b6b";
955
1105
  this.isLoading = false;
@@ -1031,7 +1181,7 @@ export class JaakStamps {
1031
1181
  }
1032
1182
  }
1033
1183
  catch (e) {
1034
- this.debugLog("Error en inferencia:", e);
1184
+ this.logger.error('Error en inferencia de modelo:', e);
1035
1185
  // Solo continuar si no hemos completado el proceso
1036
1186
  if (this.captureStep !== 'completed') {
1037
1187
  // On error, wait longer before retrying
@@ -1159,7 +1309,7 @@ export class JaakStamps {
1159
1309
  if (!this.hasScreenshotTaken) {
1160
1310
  this.lastDetectedBox = bestBox;
1161
1311
  this.takeScreenshot().catch(error => {
1162
- this.debugLog('Error taking screenshot:', error);
1312
+ this.logger.error('Error al tomar captura de pantalla:', error);
1163
1313
  });
1164
1314
  this.hasScreenshotTaken = true;
1165
1315
  // Reset para permitir segunda captura
@@ -1177,6 +1327,7 @@ export class JaakStamps {
1177
1327
  if (boxes.length === 0) {
1178
1328
  this.statusMessage = "Posicione la identificación dentro del marco";
1179
1329
  this.statusColor = "#ff6b6b";
1330
+ this.logger.debug('Sin detección de documento en el frame');
1180
1331
  }
1181
1332
  else {
1182
1333
  const bestBox = boxes.reduce((best, current) => current.score > best.score ? current : best);
@@ -1186,6 +1337,11 @@ export class JaakStamps {
1186
1337
  if (allSidesAligned) {
1187
1338
  this.statusMessage = "Identificación perfectamente alineada. Mantenga inmóvil";
1188
1339
  this.statusColor = "#00ff00";
1340
+ this.logger.state('DOCUMENTO_PERFECTAMENTE_ALINEADO', {
1341
+ score: bestBox.score,
1342
+ boxDimensions: { width: bestBox.w, height: bestBox.h },
1343
+ alignedSides: 4
1344
+ });
1189
1345
  }
1190
1346
  else if (alignedSides > 0) {
1191
1347
  this.statusMessage = `Alinee los lados restantes (${alignedSides}/4 lados correctos)`;
@@ -1246,6 +1402,14 @@ export class JaakStamps {
1246
1402
  async takeScreenshot() {
1247
1403
  if (!this.videoRef || !this.lastDetectedBox)
1248
1404
  return;
1405
+ this.logger.state('INICIANDO_CAPTURA', {
1406
+ captureStep: this.captureStep,
1407
+ detectedBox: this.lastDetectedBox,
1408
+ videoResolution: {
1409
+ width: this.videoRef.videoWidth,
1410
+ height: this.videoRef.videoHeight
1411
+ }
1412
+ });
1249
1413
  // Activar animación
1250
1414
  this.triggerCaptureAnimation();
1251
1415
  // OPTIMIZATION: Reuse capture canvas for full frame
@@ -1285,7 +1449,7 @@ export class JaakStamps {
1285
1449
  await this.loadMobileNetModel();
1286
1450
  }
1287
1451
  catch (error) {
1288
- this.debugLog('⚠️ Failed to load classification model, continuing without classification:', error);
1452
+ this.logger.warn('Fallo al cargar modelo de clasificación, continuando sin clasificación:', error);
1289
1453
  }
1290
1454
  }
1291
1455
  // Classify the cropped document if model is available
@@ -1293,7 +1457,7 @@ export class JaakStamps {
1293
1457
  const classification = await this.classifyDocument(croppedCanvas);
1294
1458
  if (classification && classification.class === 'passport') {
1295
1459
  // If it's a passport, skip back capture since passports don't have a back side
1296
- this.debugLog('📄 Passport detected - skipping back capture');
1460
+ this.logger.state('PASAPORTE_DETECTADO_SALTANDO_REVERSO', { classification: classification?.class });
1297
1461
  this.completeProcess(true);
1298
1462
  return;
1299
1463
  }
@@ -1314,14 +1478,23 @@ export class JaakStamps {
1314
1478
  this.isDetectionPaused = false;
1315
1479
  }, 3000);
1316
1480
  }, 800);
1317
- this.debugLog('📸 FRENTE capturado. Esperando trasera...');
1481
+ this.logger.state('CAPTURA_FRENTE_COMPLETADA', {
1482
+ captureStep: this.captureStep,
1483
+ hasFullFrame: !!this.capturedFullFrame,
1484
+ hasCroppedId: !!this.capturedCroppedId
1485
+ });
1318
1486
  }
1319
1487
  else if (this.captureStep === 'back') {
1320
1488
  // Captura de la trasera usando canvas reutilizado
1321
1489
  this.capturedBackFullFrame = this.captureCanvas.toDataURL('image/png');
1322
1490
  this.capturedBackCroppedId = croppedCanvas.toDataURL('image/png');
1323
1491
  this.completeProcess(false);
1324
- this.debugLog('📸 TRASERA capturada. Proceso completado. Detector detenido. Imágenes emitidas.');
1492
+ this.logger.state('CAPTURA_TRASERA_COMPLETADA', {
1493
+ captureStep: this.captureStep,
1494
+ hasBackFullFrame: !!this.capturedBackFullFrame,
1495
+ hasBackCroppedId: !!this.capturedBackCroppedId,
1496
+ processCompleted: true
1497
+ });
1325
1498
  }
1326
1499
  }
1327
1500
  triggerCaptureAnimation() {
@@ -1345,7 +1518,7 @@ export class JaakStamps {
1345
1518
  const ctx = this.canvasRef.getContext("2d");
1346
1519
  ctx.clearRect(0, 0, this.canvasRef.width, this.canvasRef.height);
1347
1520
  }
1348
- this.debugLog('🛑 Detector de identificación detenido');
1521
+ this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
1349
1522
  }
1350
1523
  resetDetection() {
1351
1524
  this.bestScore = 0;
@@ -1384,6 +1557,13 @@ export class JaakStamps {
1384
1557
  "Proceso completado (solo frente capturado)" :
1385
1558
  "Proceso de captura completado exitosamente";
1386
1559
  this.statusColor = "#28a745";
1560
+ this.logger.state('PROCESO_COMPLETADO', {
1561
+ skippedBack,
1562
+ hasFrontImages: !!(this.capturedFullFrame && this.capturedCroppedId),
1563
+ hasBackImages: !!(this.capturedBackFullFrame && this.capturedBackCroppedId),
1564
+ totalImages: skippedBack ? 2 : 4,
1565
+ timestamp: new Date().toISOString()
1566
+ });
1387
1567
  // Detener el detector
1388
1568
  this.stopDetection();
1389
1569
  // Emitir evento con las imágenes capturadas
@@ -1435,7 +1615,7 @@ export class JaakStamps {
1435
1615
  this.cleanup();
1436
1616
  }
1437
1617
  render() {
1438
- 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: {
1618
+ 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: {
1439
1619
  position: 'absolute',
1440
1620
  top: '50px',
1441
1621
  right: '0',
@@ -1445,10 +1625,10 @@ export class JaakStamps {
1445
1625
  fontSize: '10px',
1446
1626
  borderRadius: '4px',
1447
1627
  whiteSpace: 'nowrap'
1448
- } }, "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: () => {
1628
+ } }, "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: () => {
1449
1629
  this.switchCamera(camera.deviceId);
1450
1630
  this.toggleCameraSelector();
1451
- }, 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" })))));
1631
+ }, 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" })))));
1452
1632
  }
1453
1633
  static get is() { return "jaak-stamps"; }
1454
1634
  static get encapsulation() { return "shadow"; }