@jaak.ai/stamps 2.0.0-dev.58 → 2.0.1

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.
Files changed (44) hide show
  1. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  2. package/dist/cjs/jaak-stamps.cjs.entry.js +611 -546
  3. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  4. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  5. package/dist/cjs/loader.cjs.js +1 -1
  6. package/dist/collection/components/my-component/my-component.css +2 -14
  7. package/dist/collection/components/my-component/my-component.js +237 -346
  8. package/dist/collection/components/my-component/my-component.js.map +1 -1
  9. package/dist/collection/services/CameraService.js +344 -156
  10. package/dist/collection/services/CameraService.js.map +1 -1
  11. package/dist/collection/services/DetectionService.js +38 -53
  12. package/dist/collection/services/DetectionService.js.map +1 -1
  13. package/dist/collection/services/EventBusService.js +1 -1
  14. package/dist/collection/services/EventBusService.js.map +1 -1
  15. package/dist/collection/services/LoggerService.js +40 -0
  16. package/dist/collection/services/LoggerService.js.map +1 -0
  17. package/dist/collection/services/ServiceContainer.js +12 -2
  18. package/dist/collection/services/ServiceContainer.js.map +1 -1
  19. package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
  20. package/dist/collection/services/interfaces/ILogger.js +2 -0
  21. package/dist/collection/services/interfaces/ILogger.js.map +1 -0
  22. package/dist/collection/types/component-types.js.map +1 -1
  23. package/dist/components/jaak-stamps.js +614 -548
  24. package/dist/components/jaak-stamps.js.map +1 -1
  25. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  26. package/dist/esm/jaak-stamps.entry.js +611 -546
  27. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  28. package/dist/esm/loader.js +1 -1
  29. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  30. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  31. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +2 -0
  32. package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +1 -0
  33. package/dist/types/components/my-component/my-component.d.ts +17 -62
  34. package/dist/types/components.d.ts +8 -6
  35. package/dist/types/services/CameraService.d.ts +14 -12
  36. package/dist/types/services/DetectionService.d.ts +3 -9
  37. package/dist/types/services/LoggerService.d.ts +12 -0
  38. package/dist/types/services/ServiceContainer.d.ts +2 -0
  39. package/dist/types/services/interfaces/ICameraService.d.ts +12 -3
  40. package/dist/types/services/interfaces/ILogger.d.ts +8 -0
  41. package/dist/types/types/component-types.d.ts +0 -3
  42. package/package.json +4 -4
  43. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
  44. package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js.map +0 -1
@@ -1,5 +1,45 @@
1
1
  import { p as proxyCustomElement, H, c as createEvent, h } from './index.js';
2
2
 
3
+ class LoggerService {
4
+ debugMode;
5
+ constructor(debug = false) {
6
+ this.debugMode = debug;
7
+ }
8
+ setDebugMode(debug) {
9
+ this.debugMode = debug;
10
+ }
11
+ info(...args) {
12
+ if (this.debugMode) {
13
+ console.log(`[JAAK-STAMPS] [INFO] [${new Date().toLocaleTimeString()}]`, ...args);
14
+ }
15
+ }
16
+ warn(...args) {
17
+ if (this.debugMode) {
18
+ console.warn(`[JAAK-STAMPS] [WARN] [${new Date().toLocaleTimeString()}]`, ...args);
19
+ }
20
+ }
21
+ error(...args) {
22
+ if (this.debugMode) {
23
+ console.error(`[JAAK-STAMPS] [ERROR] [${new Date().toLocaleTimeString()}]`, ...args);
24
+ }
25
+ }
26
+ debug(...args) {
27
+ if (this.debugMode) {
28
+ console.debug(`[JAAK-STAMPS] [DEBUG] [${new Date().toLocaleTimeString()}]`, ...args);
29
+ }
30
+ }
31
+ state(state, data) {
32
+ if (this.debugMode) {
33
+ console.log(`[JAAK-STAMPS] [STATE] [${new Date().toLocaleTimeString()}] ${state}`, data || '');
34
+ }
35
+ }
36
+ performance(operation, duration) {
37
+ if (this.debugMode) {
38
+ console.log(`[JAAK-STAMPS] [PERF] [${new Date().toLocaleTimeString()}] ${operation}: ${duration}ms`);
39
+ }
40
+ }
41
+ }
42
+
3
43
  class EventBusService {
4
44
  events = new Map();
5
45
  on(event, callback) {
@@ -25,7 +65,7 @@ class EventBusService {
25
65
  callback(data);
26
66
  }
27
67
  catch (error) {
28
- // Silently handle callback errors
68
+ console.error(`Error in event callback for ${event}:`, error);
29
69
  }
30
70
  });
31
71
  }
@@ -152,20 +192,16 @@ class StateManagerService {
152
192
  }
153
193
 
154
194
  class CameraService {
195
+ logger;
155
196
  eventBus;
156
197
  availableCameras = [];
157
198
  selectedCameraId = null;
158
199
  deviceType = 'desktop';
159
200
  preferredCameraFacing = null;
160
201
  preferredCamera = 'auto';
161
- // Optimization: Static caches for performance
162
- static cameraCapabilitiesCache = new Map();
163
- static deviceEnumerationCache = null;
164
- static CACHE_DURATION = 30000; // 30 seconds
165
- // Stream management for optimization
166
- currentStreamPromise;
167
- lastStreamConstraints;
168
- constructor(eventBus, preferredCamera = 'auto') {
202
+ isManuallySelected = false;
203
+ constructor(logger, eventBus, preferredCamera = 'auto') {
204
+ this.logger = logger;
169
205
  this.eventBus = eventBus;
170
206
  this.preferredCamera = preferredCamera;
171
207
  }
@@ -182,21 +218,18 @@ class CameraService {
182
218
  else {
183
219
  this.deviceType = 'desktop';
184
220
  }
221
+ this.logger.state('DISPOSITIVO_DETECTADO', {
222
+ deviceType: this.deviceType,
223
+ userAgent: navigator.userAgent,
224
+ screenDimensions: { width: window.innerWidth, height: window.innerHeight }
225
+ });
185
226
  return this.deviceType;
186
227
  }
187
228
  async enumerateDevices() {
188
229
  try {
189
- // Check cache first for optimization
190
- const now = Date.now();
191
- if (CameraService.deviceEnumerationCache &&
192
- (now - CameraService.deviceEnumerationCache.timestamp) < CameraService.CACHE_DURATION) {
193
- this.availableCameras = CameraService.deviceEnumerationCache.devices;
194
- await this.setInitialCameraPreference();
195
- this.eventBus.emit('camera-changed', this.selectedCameraId);
196
- return this.availableCameras;
197
- }
198
230
  const permissionStatus = await this.checkCameraPermission();
199
231
  if (permissionStatus === 'denied') {
232
+ this.logger.error('Permiso de cámara denegado por el usuario');
200
233
  return [];
201
234
  }
202
235
  if (permissionStatus === 'prompt') {
@@ -205,16 +238,24 @@ class CameraService {
205
238
  }
206
239
  const devices = await navigator.mediaDevices.enumerateDevices();
207
240
  this.availableCameras = devices.filter(device => device.kind === 'videoinput');
208
- // Cache the results for optimization
209
- CameraService.deviceEnumerationCache = {
210
- devices: this.availableCameras,
211
- timestamp: now
212
- };
213
- await this.setInitialCameraPreference();
241
+ this.logger.state('CAMARAS_DETECTADAS', {
242
+ count: this.availableCameras.length,
243
+ cameras: this.availableCameras.map(cam => ({
244
+ id: cam.deviceId,
245
+ label: cam.label || 'Unknown Camera'
246
+ }))
247
+ });
248
+ this.setInitialCameraPreference();
249
+ this.logger.state('PREFERENCIA_INICIAL_APLICADA', {
250
+ selectedCameraId: this.selectedCameraId,
251
+ preferredCameraFacing: this.preferredCameraFacing,
252
+ preferredCamera: this.preferredCamera
253
+ });
214
254
  this.eventBus.emit('camera-changed', this.selectedCameraId);
215
255
  return this.availableCameras;
216
256
  }
217
257
  catch (error) {
258
+ this.logger.error('Error al enumerar cámaras disponibles:', error);
218
259
  this.handleCameraPermissionError(error);
219
260
  return [];
220
261
  }
@@ -235,6 +276,8 @@ class CameraService {
235
276
  }
236
277
  this.selectedCameraId = cameraId;
237
278
  this.updatePreferredFacing(camera);
279
+ this.isManuallySelected = true; // Mark as manually selected
280
+ this.savePreference();
238
281
  this.eventBus.emit('camera-changed', cameraId);
239
282
  }
240
283
  getPreferredFacing() {
@@ -243,20 +286,14 @@ class CameraService {
243
286
  async setupCamera(constraints) {
244
287
  try {
245
288
  const finalConstraints = constraints || await this.getMaxResolution();
246
- // Optimization: Check if we can reuse existing stream with same constraints
247
- if (this.currentStreamPromise && this.constraintsEqual(finalConstraints, this.lastStreamConstraints)) {
248
- return await this.currentStreamPromise;
249
- }
250
- // Create new stream promise for lazy loading
251
- this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
289
+ const stream = await navigator.mediaDevices.getUserMedia({
252
290
  video: finalConstraints,
253
291
  audio: false
254
292
  });
255
- this.lastStreamConstraints = finalConstraints;
256
- const stream = await this.currentStreamPromise;
257
293
  return stream;
258
294
  }
259
295
  catch (error) {
296
+ this.logger.error('Error en setupCamera, intentando con restricciones básicas:', error);
260
297
  // Fallback to basic constraints if getMaxResolution fails
261
298
  const basicConstraints = {
262
299
  width: { ideal: 1280 },
@@ -266,21 +303,65 @@ class CameraService {
266
303
  if (this.deviceType !== 'desktop' && this.preferredCameraFacing) {
267
304
  basicConstraints.facingMode = this.preferredCameraFacing;
268
305
  }
269
- this.currentStreamPromise = navigator.mediaDevices.getUserMedia({
306
+ this.logger.state('USANDO_RESTRICCIONES_BASICAS', { constraints: basicConstraints });
307
+ return await navigator.mediaDevices.getUserMedia({
270
308
  video: basicConstraints,
271
309
  audio: false
272
310
  });
273
- this.lastStreamConstraints = basicConstraints;
274
- return await this.currentStreamPromise;
275
311
  }
276
312
  }
277
313
  async switchCamera(cameraId) {
278
314
  const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
279
315
  if (!selectedCamera) {
316
+ this.logger.warn('Cámara seleccionada no encontrada, re-enumerando dispositivos...');
280
317
  await this.enumerateDevices();
281
318
  return;
282
319
  }
283
320
  await this.setSelectedCamera(cameraId);
321
+ this.logger.state('CAMARA_CAMBIADA', {
322
+ label: selectedCamera.label,
323
+ deviceId: selectedCamera.deviceId,
324
+ isManuallySelected: this.isManuallySelected
325
+ });
326
+ }
327
+ async flipToNextCamera() {
328
+ if (!this.isMultipleCamerasAvailable())
329
+ return;
330
+ const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
331
+ const nextIndex = (currentIndex + 1) % this.availableCameras.length;
332
+ const nextCamera = this.availableCameras[nextIndex];
333
+ await this.switchCamera(nextCamera.deviceId);
334
+ }
335
+ savePreference() {
336
+ try {
337
+ const preference = {
338
+ cameraId: this.selectedCameraId,
339
+ facing: this.preferredCameraFacing,
340
+ timestamp: Date.now()
341
+ };
342
+ localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
343
+ this.logger.state('PREFERENCIA_CAMARA_GUARDADA', preference);
344
+ }
345
+ catch (error) {
346
+ this.logger.warn('Error al guardar preferencia de cámara:', error);
347
+ }
348
+ }
349
+ loadPreference() {
350
+ try {
351
+ const saved = localStorage.getItem('jaak-stamps-camera-preference');
352
+ if (saved) {
353
+ const preference = JSON.parse(saved);
354
+ const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
355
+ if (isStillAvailable) {
356
+ this.selectedCameraId = preference.cameraId;
357
+ this.preferredCameraFacing = preference.facing;
358
+ this.logger.state('PREFERENCIA_CAMARA_CARGADA', preference);
359
+ }
360
+ }
361
+ }
362
+ catch (error) {
363
+ this.logger.warn('Error al cargar preferencia de cámara:', error);
364
+ }
284
365
  }
285
366
  isRearCamera(stream) {
286
367
  const videoTrack = stream.getVideoTracks()[0];
@@ -289,18 +370,13 @@ class CameraService {
289
370
  const settings = videoTrack.getSettings();
290
371
  return settings.facingMode === 'environment';
291
372
  }
292
- async getCameraInfo() {
293
- const availableCamerasWithAutofocus = await Promise.all(this.availableCameras.map(async (camera) => {
294
- const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
295
- return {
373
+ getCameraInfo() {
374
+ return {
375
+ availableCameras: this.availableCameras.map(camera => ({
296
376
  id: camera.deviceId,
297
377
  label: camera.label || 'Unknown Camera',
298
- selected: camera.deviceId === this.selectedCameraId,
299
- hasAutofocus
300
- };
301
- }));
302
- return {
303
- availableCameras: availableCamerasWithAutofocus,
378
+ selected: camera.deviceId === this.selectedCameraId
379
+ })),
304
380
  selectedCameraId: this.selectedCameraId,
305
381
  deviceType: this.deviceType,
306
382
  isMultipleCamerasAvailable: this.isMultipleCamerasAvailable(),
@@ -316,6 +392,7 @@ class CameraService {
316
392
  return permission.state;
317
393
  }
318
394
  catch (error) {
395
+ this.logger.warn('No se pudo verificar permisos de cámara:', error);
319
396
  return 'prompt';
320
397
  }
321
398
  }
@@ -323,48 +400,35 @@ class CameraService {
323
400
  this.availableCameras = [];
324
401
  this.eventBus.emit('error', new Error(`Camera permission error: ${error.message}`));
325
402
  }
326
- async setInitialCameraPreference() {
403
+ setInitialCameraPreference() {
327
404
  if (this.availableCameras.length === 0)
328
405
  return;
406
+ // Clear any existing localStorage preferences to ensure fresh start
407
+ localStorage.removeItem('jaak-stamps-camera-preference');
408
+ this.logger.state('APLICANDO_PREFERENCIA_INICIAL', {
409
+ preferredCamera: this.preferredCamera,
410
+ availableCamerasCount: this.availableCameras.length
411
+ });
412
+ this.isManuallySelected = false; // Reset manual selection flag
329
413
  if (this.preferredCamera === 'front') {
330
- await this.selectFrontCamera();
414
+ this.selectFrontCamera();
331
415
  }
332
416
  else if (this.preferredCamera === 'back') {
333
- await this.selectBackCamera();
417
+ this.selectBackCamera();
334
418
  }
335
419
  else {
336
- await this.selectAutoCamera();
420
+ this.selectAutoCamera();
337
421
  }
338
422
  }
339
- async checkCameraAutofocus(deviceId) {
340
- try {
341
- // Check cache first for optimization
342
- if (CameraService.cameraCapabilitiesCache.has(deviceId)) {
343
- const cached = CameraService.cameraCapabilitiesCache.get(deviceId);
344
- return typeof cached === 'boolean' ? cached : false;
345
- }
346
- const stream = await navigator.mediaDevices.getUserMedia({
347
- video: { deviceId: { exact: deviceId } }
348
- });
349
- const videoTrack = stream.getVideoTracks()[0];
350
- const capabilities = videoTrack.getCapabilities();
351
- stream.getTracks().forEach(track => track.stop());
352
- const hasFocusMode = capabilities.focusMode && Array.isArray(capabilities.focusMode) && capabilities.focusMode.length > 0;
353
- const hasAutofocus = hasFocusMode && (capabilities.focusMode.includes('continuous') ||
354
- capabilities.focusMode.includes('auto'));
355
- // Cache the result for future use
356
- CameraService.cameraCapabilitiesCache.set(deviceId, hasAutofocus);
357
- return hasAutofocus;
358
- }
359
- catch (error) {
360
- // Cache negative result to avoid repeated failures
361
- CameraService.cameraCapabilitiesCache.set(deviceId, false);
362
- return false;
363
- }
364
- }
365
- async selectFrontCamera() {
423
+ selectFrontCamera() {
366
424
  this.preferredCameraFacing = 'user';
367
- const frontCameras = this.availableCameras.filter(camera => {
425
+ this.logger.state('BUSCANDO_CAMARA_FRONTAL', {
426
+ availableCameras: this.availableCameras.map(cam => ({
427
+ id: cam.deviceId,
428
+ label: cam.label
429
+ }))
430
+ });
431
+ const frontCamera = this.availableCameras.find(camera => {
368
432
  const label = camera.label.toLowerCase();
369
433
  return label.includes('front') ||
370
434
  label.includes('user') ||
@@ -372,24 +436,24 @@ class CameraService {
372
436
  label.includes('frontal') ||
373
437
  (!label.includes('back') && !label.includes('rear') && !label.includes('environment'));
374
438
  });
375
- let selectedCamera = null;
376
- if (frontCameras.length > 0) {
377
- for (const camera of frontCameras) {
378
- const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
379
- if (hasAutofocus) {
380
- selectedCamera = camera;
381
- break;
382
- }
383
- }
384
- if (!selectedCamera) {
385
- selectedCamera = frontCameras[0];
386
- }
387
- }
388
- this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
439
+ // If not found, use the first camera (usually front on mobile)
440
+ this.selectedCameraId = frontCamera ? frontCamera.deviceId : this.availableCameras[0].deviceId;
441
+ this.logger.state('CAMARA_FRONTAL_SELECCIONADA', {
442
+ found: !!frontCamera,
443
+ label: frontCamera?.label || this.availableCameras[0].label,
444
+ deviceId: this.selectedCameraId
445
+ });
389
446
  }
390
- async selectBackCamera() {
447
+ selectBackCamera() {
391
448
  this.preferredCameraFacing = 'environment';
392
- const backCameras = this.availableCameras.filter(camera => {
449
+ this.logger.state('BUSCANDO_CAMARA_TRASERA', {
450
+ availableCameras: this.availableCameras.map(cam => ({
451
+ id: cam.deviceId,
452
+ label: cam.label
453
+ }))
454
+ });
455
+ // Try to find rear camera with multiple detection methods
456
+ let backCamera = this.availableCameras.find(camera => {
393
457
  const label = camera.label.toLowerCase();
394
458
  return label.includes('back') ||
395
459
  label.includes('rear') ||
@@ -397,33 +461,34 @@ class CameraService {
397
461
  label.includes('trasera') ||
398
462
  label.includes('posterior');
399
463
  });
400
- let selectedCamera = null;
401
- if (backCameras.length > 0) {
402
- for (const camera of backCameras) {
403
- const hasAutofocus = await this.checkCameraAutofocus(camera.deviceId);
404
- if (hasAutofocus) {
405
- selectedCamera = camera;
406
- break;
407
- }
408
- }
409
- if (!selectedCamera) {
410
- selectedCamera = backCameras[0];
411
- }
412
- }
413
- else if (this.availableCameras.length > 1) {
414
- const lastCamera = this.availableCameras[this.availableCameras.length - 1];
415
- const hasAutofocus = await this.checkCameraAutofocus(lastCamera.deviceId);
416
- selectedCamera = hasAutofocus ? lastCamera : this.availableCameras[this.availableCameras.length - 1];
464
+ // If not found by label, try to use the last camera (usually rear on mobile)
465
+ if (!backCamera && this.availableCameras.length > 1) {
466
+ backCamera = this.availableCameras[this.availableCameras.length - 1];
467
+ this.logger.state('USANDO_ULTIMA_CAMARA_COMO_TRASERA', {
468
+ label: backCamera.label,
469
+ deviceId: backCamera.deviceId
470
+ });
417
471
  }
418
- this.selectedCameraId = selectedCamera ? selectedCamera.deviceId : this.availableCameras[0].deviceId;
472
+ this.selectedCameraId = backCamera ? backCamera.deviceId : this.availableCameras[0].deviceId;
473
+ this.logger.state('CAMARA_TRASERA_SELECCIONADA', {
474
+ found: !!backCamera,
475
+ label: backCamera?.label || this.availableCameras[0].label,
476
+ deviceId: this.selectedCameraId
477
+ });
419
478
  }
420
- async selectAutoCamera() {
479
+ selectAutoCamera() {
421
480
  if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
422
- await this.selectBackCamera();
481
+ this.selectBackCamera();
482
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_MOBILE', { type: 'rear' });
423
483
  }
424
484
  else {
425
485
  // For desktop, prefer front camera (usually built-in webcam)
426
- await this.selectFrontCamera();
486
+ this.selectFrontCamera();
487
+ this.logger.state('CAMARA_AUTO_SELECCIONADA_DESKTOP', {
488
+ type: 'front',
489
+ label: this.availableCameras[0].label,
490
+ deviceId: this.selectedCameraId
491
+ });
427
492
  }
428
493
  }
429
494
  updatePreferredFacing(camera) {
@@ -439,19 +504,40 @@ class CameraService {
439
504
  await this.detectDeviceType();
440
505
  }
441
506
  const videoConstraints = {};
442
- // Priority 1: Always use deviceId if available (ensures specific camera with autofocus)
443
- if (this.selectedCameraId) {
507
+ this.logger.state('CONFIGURANDO_CONSTRAINS_CAMARA', {
508
+ selectedCameraId: this.selectedCameraId,
509
+ preferredCameraFacing: this.preferredCameraFacing,
510
+ preferredCamera: this.preferredCamera,
511
+ deviceType: this.deviceType,
512
+ isManuallySelected: this.isManuallySelected
513
+ });
514
+ if (this.isManuallySelected && this.selectedCameraId) {
515
+ // When manually selected, use deviceId for exact camera selection
444
516
  videoConstraints.deviceId = { exact: this.selectedCameraId };
517
+ this.logger.state('USANDO_CAMARA_MANUAL', {
518
+ deviceId: this.selectedCameraId
519
+ });
445
520
  }
446
- // Priority 2: Use facingMode as fallback when no specific camera is selected
447
521
  else if (this.preferredCameraFacing) {
522
+ // Use facingMode for initial preference-based selection
523
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
448
524
  const facingConstraint = this.deviceType === 'desktop'
449
525
  ? { ideal: this.preferredCameraFacing }
450
526
  : { exact: this.preferredCameraFacing };
451
527
  videoConstraints.facingMode = facingConstraint;
528
+ this.logger.state('USANDO_FACING_CONSTRAINT', {
529
+ facingMode: this.preferredCameraFacing,
530
+ constraintType: this.deviceType === 'desktop' ? 'ideal' : 'exact'
531
+ });
532
+ }
533
+ else if (this.selectedCameraId) {
534
+ videoConstraints.deviceId = { exact: this.selectedCameraId };
535
+ this.logger.state('USANDO_CAMARA_SELECCIONADA', {
536
+ deviceId: this.selectedCameraId
537
+ });
452
538
  }
453
- // Priority 3: Fallback to preferredCamera setting
454
539
  else {
540
+ // Use preferredCamera setting to determine default facing mode
455
541
  if (this.preferredCamera === 'front') {
456
542
  videoConstraints.facingMode = "user";
457
543
  }
@@ -459,25 +545,24 @@ class CameraService {
459
545
  videoConstraints.facingMode = "environment";
460
546
  }
461
547
  else {
548
+ // Auto mode: use environment for mobile/tablet, user for desktop
462
549
  videoConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
463
550
  }
551
+ this.logger.state('USANDO_PREFERENCIA_CONFIGURADA', {
552
+ facingMode: videoConstraints.facingMode,
553
+ preferredCamera: this.preferredCamera,
554
+ deviceType: this.deviceType
555
+ });
464
556
  }
465
- // Optimization: Use cached capabilities if available
466
- let capabilities;
467
- if (this.selectedCameraId && CameraService.cameraCapabilitiesCache.has(this.selectedCameraId + '_caps')) {
468
- capabilities = JSON.parse(CameraService.cameraCapabilitiesCache.get(this.selectedCameraId + '_caps'));
469
- }
470
- else {
471
- const tempStream = await navigator.mediaDevices.getUserMedia({ video: videoConstraints });
472
- const videoTrack = tempStream.getVideoTracks()[0];
473
- capabilities = videoTrack.getCapabilities();
474
- tempStream.getTracks().forEach(track => track.stop());
475
- // Cache capabilities for future use
476
- if (this.selectedCameraId) {
477
- CameraService.cameraCapabilitiesCache.set(this.selectedCameraId + '_caps', JSON.stringify(capabilities));
478
- }
479
- }
557
+ const tempStream = await navigator.mediaDevices.getUserMedia({
558
+ video: videoConstraints
559
+ });
560
+ const videoTrack = tempStream.getVideoTracks()[0];
561
+ const capabilities = videoTrack.getCapabilities();
562
+ tempStream.getTracks().forEach(track => track.stop());
480
563
  const constraints = { ...videoConstraints };
564
+ // Enhanced focus and image quality settings
565
+ this.applyAdvancedCameraSettings(constraints, capabilities);
481
566
  if (capabilities.width && capabilities.height) {
482
567
  const maxWidth = Math.min(capabilities.width.max, 1920);
483
568
  const maxHeight = Math.min(capabilities.height.max, 1080);
@@ -491,20 +576,15 @@ class CameraService {
491
576
  constraints.height = { ideal: maxHeight };
492
577
  }
493
578
  }
494
- // Configure autofocus if camera has the capability
495
- const capabilitiesAny = capabilities;
496
- if (capabilitiesAny.focusMode && Array.isArray(capabilitiesAny.focusMode)) {
497
- if (capabilitiesAny.focusMode.includes('continuous')) {
498
- constraints.focusMode = 'continuous';
499
- }
500
- else if (capabilitiesAny.focusMode.includes('auto')) {
501
- constraints.focusMode = 'auto';
502
- }
503
- }
579
+ this.logger.state('CONFIGURACION_CAMARA_OPTIMIZADA', {
580
+ constraints,
581
+ capabilities: this.getCapabilitiesSummary(capabilities)
582
+ });
504
583
  return constraints;
505
584
  }
506
585
  catch (err) {
507
- // Fallback logic with minimal temporary streams
586
+ this.logger.warn('No se pudieron obtener capacidades de cámara, usando configuración de respaldo');
587
+ // Ensure device type is detected before determining constraints
508
588
  if (!this.deviceType || this.deviceType === 'desktop') {
509
589
  await this.detectDeviceType();
510
590
  }
@@ -513,16 +593,25 @@ class CameraService {
513
593
  width: { ideal: isTablet ? 1280 : 1920 },
514
594
  height: { ideal: isTablet ? 720 : 1080 }
515
595
  };
516
- if (this.selectedCameraId) {
596
+ // Apply basic focus settings even for fallback
597
+ this.applyBasicFocusSettings(fallbackConstraints);
598
+ if (this.isManuallySelected && this.selectedCameraId) {
599
+ // When manually selected, use deviceId for exact camera selection
517
600
  fallbackConstraints.deviceId = { exact: this.selectedCameraId };
518
601
  }
519
602
  else if (this.preferredCameraFacing) {
603
+ // Use facingMode for initial preference-based selection
604
+ // Use 'ideal' instead of 'exact' to avoid OverconstrainedError on desktop
520
605
  const facingConstraint = this.deviceType === 'desktop'
521
606
  ? { ideal: this.preferredCameraFacing }
522
607
  : { exact: this.preferredCameraFacing };
523
608
  fallbackConstraints.facingMode = facingConstraint;
524
609
  }
610
+ else if (this.selectedCameraId) {
611
+ fallbackConstraints.deviceId = { exact: this.selectedCameraId };
612
+ }
525
613
  else {
614
+ // Use preferredCamera setting to determine default facing mode
526
615
  if (this.preferredCamera === 'front') {
527
616
  fallbackConstraints.facingMode = "user";
528
617
  }
@@ -530,26 +619,165 @@ class CameraService {
530
619
  fallbackConstraints.facingMode = "environment";
531
620
  }
532
621
  else {
622
+ // Auto mode: use environment for mobile/tablet, user for desktop
533
623
  fallbackConstraints.facingMode = (this.deviceType === 'mobile' || this.deviceType === 'tablet') ? "environment" : "user";
534
624
  }
535
625
  }
536
626
  return fallbackConstraints;
537
627
  }
538
628
  }
539
- // Optimization helper methods
540
- constraintsEqual(a, b) {
541
- if (!a || !b)
542
- return false;
543
- return JSON.stringify(a) === JSON.stringify(b);
629
+ applyAdvancedCameraSettings(constraints, capabilities) {
630
+ // Apply standard camera settings that are widely supported
631
+ // Frame rate optimization
632
+ if (capabilities.frameRate) {
633
+ constraints.frameRate = {
634
+ ideal: 30,
635
+ min: 15,
636
+ max: 60
637
+ };
638
+ }
639
+ // Width and height constraints for optimal resolution
640
+ if (capabilities.width && capabilities.height) {
641
+ constraints.width = {
642
+ ideal: Math.min(1920, capabilities.width.max || 1920),
643
+ min: 640
644
+ };
645
+ constraints.height = {
646
+ ideal: Math.min(1080, capabilities.height.max || 1080),
647
+ min: 480
648
+ };
649
+ }
650
+ // Apply enhanced constraints through advanced array for better browser compatibility
651
+ const advancedConstraints = [];
652
+ // Try to set optimal settings through advanced constraints
653
+ const advanced = {};
654
+ // Focus mode - use 'any' type to bypass TypeScript limitations
655
+ if (capabilities.focusMode) {
656
+ if (capabilities.focusMode.includes('continuous')) {
657
+ advanced.focusMode = 'continuous';
658
+ }
659
+ else if (capabilities.focusMode.includes('single-shot')) {
660
+ advanced.focusMode = 'single-shot';
661
+ }
662
+ }
663
+ // Focus distance for close objects
664
+ if (capabilities.focusDistance) {
665
+ advanced.focusDistance = {
666
+ ideal: 0.4, // 40cm - optimal for document capture
667
+ min: 0.2,
668
+ max: 1.0
669
+ };
670
+ }
671
+ // Zoom control
672
+ if (capabilities.zoom) {
673
+ advanced.zoom = {
674
+ ideal: 1.0,
675
+ min: capabilities.zoom.min,
676
+ max: Math.min(capabilities.zoom.max, 3.0)
677
+ };
678
+ }
679
+ // Add advanced constraints if any were set
680
+ if (Object.keys(advanced).length > 0) {
681
+ advancedConstraints.push(advanced);
682
+ }
683
+ if (advancedConstraints.length > 0) {
684
+ constraints.advanced = advancedConstraints;
685
+ }
544
686
  }
545
- // Cleanup method for optimization
546
- static clearCaches() {
547
- CameraService.cameraCapabilitiesCache.clear();
548
- CameraService.deviceEnumerationCache = null;
687
+ applyBasicFocusSettings(constraints) {
688
+ // Apply basic focus settings when capabilities are not available
689
+ // Use advanced constraints for non-standard properties
690
+ const advanced = {
691
+ focusMode: 'continuous',
692
+ exposureMode: 'continuous',
693
+ whiteBalanceMode: 'continuous'
694
+ };
695
+ if (!constraints.advanced) {
696
+ constraints.advanced = [];
697
+ }
698
+ constraints.advanced.push(advanced);
699
+ constraints.frameRate = { ideal: 30 };
549
700
  }
550
- // Method to invalidate device cache when needed
551
- invalidateDeviceCache() {
552
- CameraService.deviceEnumerationCache = null;
701
+ getCapabilitiesSummary(capabilities) {
702
+ return {
703
+ hasAutoFocus: !!capabilities.focusMode,
704
+ hasFocusDistance: !!capabilities.focusDistance,
705
+ hasExposureControl: !!capabilities.exposureMode,
706
+ hasWhiteBalance: !!capabilities.whiteBalanceMode,
707
+ hasZoom: !!capabilities.zoom,
708
+ hasTorch: capabilities.torch !== undefined,
709
+ hasImageControls: !!(capabilities.brightness || capabilities.contrast || capabilities.saturation || capabilities.sharpness),
710
+ resolutionRange: capabilities.width && capabilities.height ?
711
+ `${capabilities.width.min}x${capabilities.height.min} - ${capabilities.width.max}x${capabilities.height.max}` : 'unknown'
712
+ };
713
+ }
714
+ // New method to enable torch/flash for better illumination
715
+ async setTorchEnabled(enabled, stream) {
716
+ try {
717
+ if (!stream) {
718
+ this.logger.warn('No hay stream disponible para controlar el flash');
719
+ return false;
720
+ }
721
+ const videoTrack = stream.getVideoTracks()[0];
722
+ if (!videoTrack) {
723
+ this.logger.warn('No hay track de video disponible');
724
+ return false;
725
+ }
726
+ const capabilities = videoTrack.getCapabilities();
727
+ if (capabilities.torch === undefined) {
728
+ this.logger.warn('El dispositivo no soporta control de flash');
729
+ return false;
730
+ }
731
+ await videoTrack.applyConstraints({
732
+ advanced: [{ torch: enabled }]
733
+ });
734
+ this.logger.state('FLASH_CONTROLADO', { enabled });
735
+ return true;
736
+ }
737
+ catch (error) {
738
+ this.logger.error('Error al controlar el flash:', error);
739
+ return false;
740
+ }
741
+ }
742
+ // Method to manually trigger focus on a specific point
743
+ async focusAtPoint(x, y, stream) {
744
+ try {
745
+ if (!stream) {
746
+ this.logger.warn('No hay stream disponible para enfocar');
747
+ return false;
748
+ }
749
+ const videoTrack = stream.getVideoTracks()[0];
750
+ if (!videoTrack) {
751
+ this.logger.warn('No hay track de video disponible');
752
+ return false;
753
+ }
754
+ const capabilities = videoTrack.getCapabilities();
755
+ if (!capabilities.focusMode || !capabilities.focusMode.includes('single-shot')) {
756
+ this.logger.warn('El dispositivo no soporta enfoque manual');
757
+ return false;
758
+ }
759
+ // Trigger single-shot focus
760
+ await videoTrack.applyConstraints({
761
+ advanced: [{ focusMode: 'single-shot' }]
762
+ });
763
+ // Return to continuous focus after a short delay
764
+ setTimeout(async () => {
765
+ try {
766
+ await videoTrack.applyConstraints({
767
+ advanced: [{ focusMode: 'continuous' }]
768
+ });
769
+ }
770
+ catch (error) {
771
+ this.logger.warn('Error al volver a enfoque continuo:', error);
772
+ }
773
+ }, 1000);
774
+ this.logger.state('ENFOQUE_MANUAL_ACTIVADO', { x, y });
775
+ return true;
776
+ }
777
+ catch (error) {
778
+ this.logger.error('Error al enfocar manualmente:', error);
779
+ return false;
780
+ }
553
781
  }
554
782
  }
555
783
 
@@ -628,6 +856,7 @@ class DeviceStrategyFactory {
628
856
  }
629
857
 
630
858
  class DetectionService {
859
+ logger;
631
860
  debug;
632
861
  useDocumentClassification;
633
862
  session;
@@ -644,10 +873,8 @@ class DetectionService {
644
873
  preprocessCanvas;
645
874
  preprocessCtx;
646
875
  captureCanvas;
647
- // Static canvas pool for reuse across instances
648
- static canvasPool = new Map();
649
- static MAX_POOL_SIZE = 5;
650
- constructor(debug = false, useDocumentClassification = false) {
876
+ constructor(logger, debug = false, useDocumentClassification = false) {
877
+ this.logger = logger;
651
878
  this.debug = debug;
652
879
  this.useDocumentClassification = useDocumentClassification;
653
880
  this.deviceStrategy = DeviceStrategyFactory.createStrategy();
@@ -655,15 +882,21 @@ class DetectionService {
655
882
  }
656
883
  async loadModel() {
657
884
  if (this.modelLoaded || this.session) {
885
+ this.logger.state('MODELO_YA_PRECARGADO', {
886
+ sessionExists: !!this.session,
887
+ modelPreloaded: this.modelLoaded
888
+ });
658
889
  return;
659
890
  }
660
891
  try {
892
+ this.logger.state('PRECARGANDO_MODELO_DETECCION', { modelPath: this.MODEL_PATH });
661
893
  const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
662
894
  try {
663
895
  this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
664
896
  }
665
897
  catch (error) {
666
898
  if (error.message.includes('failed to allocate a buffer')) {
899
+ this.logger.warn('Fallo en asignación de buffer durante precarga, intentando con configuración mínima');
667
900
  const fallbackOptions = {
668
901
  executionProviders: ['wasm'],
669
902
  graphOptimizationLevel: 'disabled',
@@ -681,8 +914,10 @@ class DetectionService {
681
914
  }
682
915
  }
683
916
  this.modelLoaded = true;
917
+ this.logger.state('MODELO_DETECCION_CARGADO_EXITOSAMENTE', { sessionCreated: !!this.session });
684
918
  }
685
919
  catch (error) {
920
+ this.logger.error('Error al precargar modelo de detección:', error);
686
921
  throw error;
687
922
  }
688
923
  }
@@ -691,14 +926,19 @@ class DetectionService {
691
926
  return;
692
927
  }
693
928
  try {
929
+ this.logger.state('CARGANDO_MODELO_MOBILENET', { path: this.MOBILENET_MODEL_PATH });
694
930
  // Try to load class map (optional - SqueezeNet may not need it for basic classification)
695
931
  try {
696
932
  const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
697
933
  if (classResponse.ok) {
698
934
  this.mobileNetClassMap = await classResponse.json();
935
+ this.logger.state('CLASES_MOBILENET_CARGADAS', {
936
+ classCount: Object.keys(this.mobileNetClassMap).length
937
+ });
699
938
  }
700
939
  }
701
940
  catch (error) {
941
+ this.logger.warn('No se pudo cargar el mapa de clases de MobileNet, usando clasificación básica');
702
942
  // Create a basic class map for document types
703
943
  this.mobileNetClassMap = {
704
944
  "0": "document",
@@ -715,6 +955,7 @@ class DetectionService {
715
955
  }
716
956
  catch (error) {
717
957
  if (error.message.includes('failed to allocate a buffer')) {
958
+ this.logger.warn('Fallo en asignación de buffer de MobileNet, intentando con configuración mínima');
718
959
  const fallbackOptions = {
719
960
  executionProviders: ['wasm'],
720
961
  graphOptimizationLevel: 'disabled',
@@ -731,8 +972,12 @@ class DetectionService {
731
972
  throw error;
732
973
  }
733
974
  }
975
+ this.logger.state('MODELO_MOBILENET_CARGADO_EXITOSAMENTE', {
976
+ sessionCreated: !!this.mobileNetSession
977
+ });
734
978
  }
735
979
  catch (error) {
980
+ this.logger.error('Error al cargar modelo MobileNet:', error);
736
981
  throw error;
737
982
  }
738
983
  }
@@ -785,9 +1030,11 @@ class DetectionService {
785
1030
  }
786
1031
  async classifyDocument(canvas) {
787
1032
  if (!this.mobileNetSession || !this.mobileNetClassMap) {
1033
+ this.logger.warn('Modelo MobileNet no está cargado, saltando clasificación');
788
1034
  return null;
789
1035
  }
790
1036
  try {
1037
+ this.logger.state('CLASIFICANDO_DOCUMENTO', { timestamp: Date.now() });
791
1038
  const inputTensor = this.preprocessMobileNet(canvas);
792
1039
  const feeds = { [this.mobileNetSession.inputNames[0]]: inputTensor };
793
1040
  const results = await this.mobileNetSession.run(feeds);
@@ -795,6 +1042,13 @@ class DetectionService {
795
1042
  const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
796
1043
  const confidence = output[maxIdx];
797
1044
  const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
1045
+ this.logger.state('DOCUMENTO_CLASIFICADO', {
1046
+ class: className,
1047
+ confidence: confidence.toFixed(3),
1048
+ classIndex: maxIdx,
1049
+ timestamp: Date.now(),
1050
+ results
1051
+ });
798
1052
  return {
799
1053
  class: className,
800
1054
  confidence: confidence,
@@ -802,6 +1056,7 @@ class DetectionService {
802
1056
  };
803
1057
  }
804
1058
  catch (error) {
1059
+ this.logger.error('Error al clasificar documento:', error);
805
1060
  return null;
806
1061
  }
807
1062
  }
@@ -914,6 +1169,7 @@ class DetectionService {
914
1169
  }
915
1170
  this.mobileNetClassMap = undefined;
916
1171
  this.modelLoaded = false;
1172
+ this.logger.state('DETECCION_SERVICE_LIMPIADO', { timestamp: Date.now() });
917
1173
  }
918
1174
  initializeCanvasPool() {
919
1175
  this.preprocessCanvas = document.createElement('canvas');
@@ -924,6 +1180,9 @@ class DetectionService {
924
1180
  willReadFrequently: true
925
1181
  });
926
1182
  this.captureCanvas = document.createElement('canvas');
1183
+ this.logger.state('CANVAS_POOL_INICIALIZADO', {
1184
+ preprocessCanvasSize: this.INPUT_SIZE
1185
+ });
927
1186
  }
928
1187
  cleanupCanvasPool() {
929
1188
  if (this.preprocessCanvas) {
@@ -959,11 +1218,10 @@ class DetectionService {
959
1218
  return (sign << 15) | (newExp << 10) | (frac >> 13);
960
1219
  }
961
1220
  preprocessMobileNet(canvas) {
962
- // Use pooled canvas instead of creating new one
963
- const tempCanvas = this.getCanvas(224, 224);
1221
+ const tempCanvas = document.createElement('canvas');
1222
+ tempCanvas.width = 224;
1223
+ tempCanvas.height = 224;
964
1224
  const tempCtx = tempCanvas.getContext('2d');
965
- // Clear canvas before use
966
- tempCtx.clearRect(0, 0, 224, 224);
967
1225
  // Resize image to 224x224 for SqueezeNet (using MobileNet interface)
968
1226
  tempCtx.drawImage(canvas, 0, 0, 224, 224);
969
1227
  const imageData = tempCtx.getImageData(0, 0, 224, 224);
@@ -979,53 +1237,8 @@ class DetectionService {
979
1237
  // Blue channel
980
1238
  arr[2 * hw + i] = data[i * 4 + 2] / 255.0;
981
1239
  }
982
- // Return canvas to pool after use
983
- this.returnCanvas(tempCanvas);
984
1240
  return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
985
1241
  }
986
- // Canvas pool management methods for optimization
987
- getCanvas(width, height) {
988
- const key = `${width}x${height}`;
989
- if (!DetectionService.canvasPool.has(key)) {
990
- DetectionService.canvasPool.set(key, []);
991
- }
992
- const pool = DetectionService.canvasPool.get(key);
993
- let canvas = pool.pop();
994
- if (!canvas) {
995
- canvas = document.createElement('canvas');
996
- canvas.width = width;
997
- canvas.height = height;
998
- }
999
- else {
1000
- // Clear the canvas before reuse
1001
- const ctx = canvas.getContext('2d');
1002
- ctx.clearRect(0, 0, width, height);
1003
- }
1004
- return canvas;
1005
- }
1006
- returnCanvas(canvas) {
1007
- const key = `${canvas.width}x${canvas.height}`;
1008
- if (!DetectionService.canvasPool.has(key)) {
1009
- DetectionService.canvasPool.set(key, []);
1010
- }
1011
- const pool = DetectionService.canvasPool.get(key);
1012
- // Only return to pool if not at max capacity
1013
- if (pool.length < DetectionService.MAX_POOL_SIZE) {
1014
- pool.push(canvas);
1015
- }
1016
- }
1017
- // Static method to clear all canvas pools (useful for memory management)
1018
- static clearCanvasPools() {
1019
- DetectionService.canvasPool.clear();
1020
- }
1021
- // Method to get pool statistics for debugging
1022
- getPoolStats() {
1023
- const stats = {};
1024
- DetectionService.canvasPool.forEach((pool, key) => {
1025
- stats[key] = pool.length;
1026
- });
1027
- return stats;
1028
- }
1029
1242
  }
1030
1243
 
1031
1244
  class ServiceContainer {
@@ -1036,11 +1249,13 @@ class ServiceContainer {
1036
1249
  initializeServices(config) {
1037
1250
  // Initialize services in dependency order
1038
1251
  const eventBus = new EventBusService();
1252
+ const logger = new LoggerService(config.debug);
1039
1253
  const stateManager = new StateManagerService(eventBus);
1040
- const cameraService = new CameraService(eventBus, config.preferredCamera);
1041
- const detectionService = new DetectionService(config.debug, config.useDocumentClassification);
1254
+ const cameraService = new CameraService(logger, eventBus, config.preferredCamera);
1255
+ const detectionService = new DetectionService(logger, config.debug, config.useDocumentClassification);
1042
1256
  // Register services
1043
1257
  this.services.set('eventBus', eventBus);
1258
+ this.services.set('logger', logger);
1044
1259
  this.services.set('stateManager', stateManager);
1045
1260
  this.services.set('cameraService', cameraService);
1046
1261
  this.services.set('detectionService', detectionService);
@@ -1052,6 +1267,9 @@ class ServiceContainer {
1052
1267
  }
1053
1268
  return service;
1054
1269
  }
1270
+ getLogger() {
1271
+ return this.get('logger');
1272
+ }
1055
1273
  getEventBus() {
1056
1274
  return this.get('eventBus');
1057
1275
  }
@@ -1065,6 +1283,10 @@ class ServiceContainer {
1065
1283
  return this.get('detectionService');
1066
1284
  }
1067
1285
  updateConfig(config) {
1286
+ if (config.debug !== undefined) {
1287
+ const logger = this.services.get('logger');
1288
+ logger.setDebugMode(config.debug);
1289
+ }
1068
1290
  }
1069
1291
  cleanup() {
1070
1292
  // Cleanup services in reverse order
@@ -1074,7 +1296,7 @@ class ServiceContainer {
1074
1296
  }
1075
1297
  }
1076
1298
 
1077
- const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);padding:12px 20px;border-radius:8px;max-width:300px;z-index:20;border:1px solid rgba(255, 255, 255, 0.1)}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}";
1299
+ const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);padding:12px 20px;border-radius:8px;max-width:300px;z-index:20;border:1px solid rgba(255, 255, 255, 0.1)}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:space-between;align-items:center;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:8px;font-weight:400}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}";
1078
1300
 
1079
1301
  const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
1080
1302
  constructor() {
@@ -1106,16 +1328,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1106
1328
  showCameraSelector = false;
1107
1329
  isSwitchingCamera = false;
1108
1330
  hasDocumentDetected = false;
1109
- cameraInfoWithAutofocus = {
1110
- availableCameras: [],
1111
- selectedCameraId: null,
1112
- deviceType: 'desktop',
1113
- isMultipleCamerasAvailable: false,
1114
- preferredFacing: null
1115
- };
1116
1331
  currentStatus = {
1117
- message: 'Preparando capturador...',
1118
- description: 'Configurando herramientas de captura',
1332
+ message: 'Inicializando componente...',
1333
+ description: 'Configurando servicios y cargando recursos',
1119
1334
  type: 'initializing',
1120
1335
  isInitialized: false
1121
1336
  };
@@ -1132,6 +1347,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1132
1347
  backDocumentTimerRemaining = 0;
1133
1348
  // Services
1134
1349
  serviceContainer;
1350
+ logger;
1135
1351
  eventBus;
1136
1352
  stateManager;
1137
1353
  cameraService;
@@ -1162,25 +1378,19 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1162
1378
  lastUpdateTime: 0
1163
1379
  };
1164
1380
  performanceUpdateInterval;
1165
- // Performance optimization with adaptive frame skipping
1381
+ // Performance optimization
1166
1382
  frameSkipCounter = 0;
1167
- BASE_FRAME_SKIP = 2;
1168
- MAX_FRAME_SKIP = 8;
1383
+ FRAME_SKIP = 2;
1169
1384
  consecutiveFailures = 0;
1170
1385
  MAX_FAILURES = 30;
1171
1386
  lastInferenceTime = 0;
1172
1387
  MIN_INFERENCE_INTERVAL = 50;
1173
- performanceHistory = [];
1174
- PERFORMANCE_HISTORY_SIZE = 10;
1175
- // Canvas pool for optimized screenshot capture
1176
- canvasPool = [];
1177
- MAX_CANVAS_POOL_SIZE = 3;
1178
1388
  async componentDidLoad() {
1179
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1389
+ this.updateStatus('Iniciando servicios...', 'Configurando módulos internos', 'initializing');
1180
1390
  await this.initializeServices();
1181
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1391
+ this.updateStatus('Configurando eventos...', 'Preparando comunicación entre servicios', 'initializing');
1182
1392
  await this.setupEventListeners();
1183
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1393
+ this.updateStatus('Inicializando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1184
1394
  await this.initializeComponent();
1185
1395
  if (this.debug) {
1186
1396
  this.initializePerformanceMonitor();
@@ -1197,23 +1407,33 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1197
1407
  captureDelay: this.captureDelay
1198
1408
  };
1199
1409
  this.serviceContainer = new ServiceContainer(config);
1410
+ this.logger = this.serviceContainer.getLogger();
1200
1411
  this.eventBus = this.serviceContainer.getEventBus();
1201
1412
  this.stateManager = this.serviceContainer.getStateManager();
1202
1413
  this.cameraService = this.serviceContainer.getCameraService();
1203
1414
  this.detectionService = this.serviceContainer.getDetectionService();
1415
+ this.logger.state('SERVICIOS_INICIALIZADOS', { timestamp: Date.now() });
1204
1416
  }
1205
1417
  async setupEventListeners() {
1206
1418
  this.eventBus.on('state-changed', (data) => {
1207
1419
  this.handleStateChange(data);
1208
1420
  });
1209
- this.eventBus.on('camera-changed', () => {
1210
- // Camera changed event
1421
+ this.eventBus.on('camera-changed', (cameraId) => {
1422
+ this.logger.state('CAMARA_CAMBIADA_EVENT', { cameraId });
1211
1423
  });
1212
- this.eventBus.on('error', () => {
1213
- // Handle service errors
1424
+ this.eventBus.on('error', (error) => {
1425
+ this.logger.error('Error en servicio:', error);
1214
1426
  });
1215
1427
  }
1216
1428
  async initializeComponent() {
1429
+ this.logger.state('COMPONENTE_INICIALIZANDO', {
1430
+ debug: this.debug,
1431
+ maskSize: this.maskSize,
1432
+ cropMargin: this.cropMargin,
1433
+ useDocumentClassification: this.useDocumentClassification,
1434
+ preferredCamera: this.preferredCamera,
1435
+ captureDelay: this.captureDelay
1436
+ });
1217
1437
  this.validateProps();
1218
1438
  if (this.debug) {
1219
1439
  this.stateManager.updateCaptureState({ isLoading: true });
@@ -1222,29 +1442,32 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1222
1442
  this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura', 'initializing');
1223
1443
  await this.cameraService.detectDeviceType();
1224
1444
  await this.cameraService.enumerateDevices();
1225
- await this.updateCameraInfoWithAutofocus();
1226
- this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'initializing');
1445
+ this.updateStatus('Cargando modelo IA...', 'Preparando reconocimiento de documentos', 'initializing');
1227
1446
  await this.loadOnnxRuntime();
1228
1447
  this.initializeResizeObserver();
1229
1448
  }
1230
1449
  validateProps() {
1231
1450
  if (this.maskSize < 50 || this.maskSize > 100) {
1451
+ this.logger.warn(`Propiedad maskSize inválida. Valor: ${this.maskSize}, esperado: 50-100. Usando valor por defecto: 90`);
1232
1452
  this.maskSize = 90;
1233
1453
  }
1234
1454
  if (this.cropMargin < 0 || this.cropMargin > 100) {
1455
+ this.logger.warn(`Propiedad cropMargin inválida. Valor: ${this.cropMargin}, esperado: 0-100. Usando valor por defecto: 0`);
1235
1456
  this.cropMargin = 0;
1236
1457
  }
1237
1458
  const validOptions = ['auto', 'front', 'back'];
1238
1459
  if (!validOptions.includes(this.preferredCamera)) {
1460
+ this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
1239
1461
  this.preferredCamera = 'auto';
1240
1462
  }
1241
1463
  if (this.captureDelay < 0 || this.captureDelay > 10000) {
1464
+ this.logger.warn(`Propiedad captureDelay inválida. Valor: ${this.captureDelay}, esperado: 0-10000. Usando valor por defecto: 1500`);
1242
1465
  this.captureDelay = 1500;
1243
1466
  }
1244
1467
  }
1245
1468
  async loadOnnxRuntime() {
1246
1469
  if (!window.ort) {
1247
- this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
1470
+ this.updateStatus('Descargando librerías...', 'Obteniendo recursos de reconocimiento', 'initializing');
1248
1471
  const script = document.createElement('script');
1249
1472
  script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
1250
1473
  document.head.appendChild(script);
@@ -1284,6 +1507,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1284
1507
  emitReadyEvent() {
1285
1508
  const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
1286
1509
  this.isReady.emit(isDocumentReady);
1510
+ this.logger.state('COMPONENTE_LISTO', {
1511
+ ortLibraryLoaded: !!window.ort,
1512
+ modelPreloaded: this.detectionService.isModelLoaded(),
1513
+ isReady: isDocumentReady
1514
+ });
1287
1515
  }
1288
1516
  handleStateChange(data) {
1289
1517
  }
@@ -1300,6 +1528,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1300
1528
  const container = this.detectionContainer.parentElement;
1301
1529
  const rect = container.getBoundingClientRect();
1302
1530
  this.updateMaskDimensions(rect);
1531
+ this.logger.debug('Container redimensionado:', { width: rect.width, height: rect.height });
1303
1532
  }
1304
1533
  }
1305
1534
  updateMaskDimensions(containerRect) {
@@ -1353,38 +1582,16 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1353
1582
  this.el.style.setProperty('--mask-center-x', `${videoCenterXPercent}%`);
1354
1583
  this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
1355
1584
  this.isMaskReady = true;
1585
+ this.logger.state('DIMENSIONES_MASCARA_ACTUALIZADAS', {
1586
+ video: { width: videoWidth, height: videoHeight },
1587
+ displayed: { width: displayedVideoWidth, height: displayedVideoHeight },
1588
+ mask: { widthPercent: maskWidthPercent, heightPercent: maskHeightPercent },
1589
+ center: { x: videoCenterXPercent, y: videoCenterYPercent },
1590
+ offset: { x: videoOffsetX, y: videoOffsetY }
1591
+ });
1356
1592
  }
1357
1593
  // PUBLIC METHODS
1358
- // Helper method to check if component is ready for operations
1359
- isComponentReady() {
1360
- if (!this.currentStatus.isInitialized) {
1361
- return {
1362
- ready: false,
1363
- message: 'El componente aún se está inicializando',
1364
- status: 'initializing'
1365
- };
1366
- }
1367
- if (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') {
1368
- return {
1369
- ready: false,
1370
- message: 'El componente está cargando recursos',
1371
- status: 'loading'
1372
- };
1373
- }
1374
- if (!this.serviceContainer || !this.stateManager || !this.cameraService || !this.detectionService) {
1375
- return {
1376
- ready: false,
1377
- message: 'Los servicios del componente no están disponibles',
1378
- status: 'error'
1379
- };
1380
- }
1381
- return { ready: true };
1382
- }
1383
1594
  async getCapturedImages() {
1384
- const readyCheck = this.isComponentReady();
1385
- if (!readyCheck.ready) {
1386
- throw new Error(readyCheck.message);
1387
- }
1388
1595
  const state = this.stateManager.getCaptureState();
1389
1596
  if (state.step !== 'completed') {
1390
1597
  throw new Error('El proceso de captura no ha sido completado');
@@ -1392,128 +1599,47 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1392
1599
  return this.stateManager.getCapturedImages();
1393
1600
  }
1394
1601
  async isProcessCompleted() {
1395
- const readyCheck = this.isComponentReady();
1396
- if (!readyCheck.ready) {
1397
- return false; // Return false instead of error for this method
1398
- }
1399
1602
  return this.stateManager.isProcessCompleted();
1400
1603
  }
1401
1604
  async startCapture() {
1402
- const readyCheck = this.isComponentReady();
1403
- if (!readyCheck.ready) {
1404
- this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
1405
- return { success: false, error: readyCheck.message };
1406
- }
1407
- try {
1408
- await this.startDetection();
1409
- return { success: true };
1410
- }
1411
- catch (error) {
1412
- this.updateStatus('Error al iniciar captura', error.message, 'error');
1413
- return { success: false, error: error.message };
1414
- }
1605
+ await this.startDetection();
1415
1606
  }
1416
1607
  async stopCapture() {
1417
- const readyCheck = this.isComponentReady();
1418
- if (!readyCheck.ready) {
1419
- return { success: false, error: readyCheck.message };
1420
- }
1421
- try {
1422
- this.exitSession();
1423
- return { success: true };
1424
- }
1425
- catch (error) {
1426
- return { success: false, error: error.message };
1427
- }
1608
+ this.exitSession();
1428
1609
  }
1429
1610
  async resetCapture() {
1430
- const readyCheck = this.isComponentReady();
1431
- if (!readyCheck.ready) {
1432
- return { success: false, error: readyCheck.message };
1433
- }
1434
- try {
1435
- this.resetDetection();
1436
- return { success: true };
1437
- }
1438
- catch (error) {
1439
- return { success: false, error: error.message };
1440
- }
1611
+ this.resetDetection();
1441
1612
  }
1442
1613
  async skipBackCapture() {
1443
- const readyCheck = this.isComponentReady();
1444
- if (!readyCheck.ready) {
1445
- return { success: false, error: readyCheck.message };
1446
- }
1447
- try {
1448
- const captureState = this.stateManager.getCaptureState();
1449
- const capturedImages = this.stateManager.getCapturedImages();
1450
- if (captureState.step === 'back' && capturedImages.front.fullFrame) {
1451
- this.completeProcess(true);
1452
- return { success: true };
1453
- }
1454
- else {
1455
- return { success: false, error: 'No se puede saltar el reverso en el estado actual' };
1456
- }
1457
- }
1458
- catch (error) {
1459
- return { success: false, error: error.message };
1614
+ const captureState = this.stateManager.getCaptureState();
1615
+ const capturedImages = this.stateManager.getCapturedImages();
1616
+ if (captureState.step === 'back' && capturedImages.front.fullFrame) {
1617
+ this.completeProcess(true);
1460
1618
  }
1461
1619
  }
1462
1620
  async getStatus() {
1463
- const readyCheck = this.isComponentReady();
1464
- if (!readyCheck.ready) {
1465
- return {
1466
- isVideoActive: false,
1467
- captureStep: 'front',
1468
- hasImages: false,
1469
- isProcessCompleted: false,
1470
- isModelPreloaded: false,
1471
- componentStatus: readyCheck.status,
1472
- componentMessage: readyCheck.message
1473
- };
1474
- }
1475
- try {
1476
- const captureState = this.stateManager.getCaptureState();
1477
- const capturedImages = this.stateManager.getCapturedImages();
1478
- return {
1479
- isVideoActive: captureState.isVideoActive,
1480
- captureStep: captureState.step,
1481
- hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
1482
- isProcessCompleted: this.stateManager.isProcessCompleted(),
1483
- isModelPreloaded: this.detectionService.isModelLoaded(),
1484
- componentStatus: 'ready'
1485
- };
1486
- }
1487
- catch (error) {
1488
- return {
1489
- isVideoActive: false,
1490
- captureStep: 'front',
1491
- hasImages: false,
1492
- isProcessCompleted: false,
1493
- isModelPreloaded: false,
1494
- componentStatus: 'error',
1495
- componentMessage: error.message
1496
- };
1497
- }
1621
+ const captureState = this.stateManager.getCaptureState();
1622
+ const capturedImages = this.stateManager.getCapturedImages();
1623
+ return {
1624
+ isVideoActive: captureState.isVideoActive,
1625
+ captureStep: captureState.step,
1626
+ hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
1627
+ isProcessCompleted: this.stateManager.isProcessCompleted(),
1628
+ isModelPreloaded: this.detectionService.isModelLoaded()
1629
+ };
1498
1630
  }
1499
1631
  async preloadModel() {
1500
- const readyCheck = this.isComponentReady();
1501
- if (!readyCheck.ready) {
1502
- this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
1503
- return { success: false, error: readyCheck.message };
1504
- }
1505
1632
  if (this.detectionService.isModelLoaded()) {
1506
- this.updateStatus('Reconocimiento listo', '', 'ready');
1633
+ this.logger.state('MODELO_YA_PRECARGADO');
1634
+ this.updateStatus('Modelos ya cargados', '', 'ready');
1507
1635
  return { success: true, message: 'Model already loaded' };
1508
1636
  }
1509
1637
  try {
1510
1638
  const loadStartTime = performance.now();
1511
- this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
1512
- if (this.stateManager) {
1513
- this.stateManager.updateCaptureState({ isLoading: true });
1514
- }
1639
+ this.updateStatus('Descargando modelo de detección...', 'Obteniendo red neuronal para reconocimiento de documentos', 'loading');
1640
+ this.stateManager.updateCaptureState({ isLoading: true });
1515
1641
  await this.detectionService.loadModel();
1516
- this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1642
+ this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de tipos de documento', 'loading');
1517
1643
  await this.detectionService.loadClassificationModel();
1518
1644
  const loadEndTime = performance.now();
1519
1645
  const loadTime = loadEndTime - loadStartTime;
@@ -1521,118 +1647,80 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1521
1647
  if (this.debug) {
1522
1648
  this.recordOnnxPerformance(loadTime, 0);
1523
1649
  }
1524
- this.updateStatus('Finalizando configuración...', 'Preparando herramientas para captura', 'loading');
1650
+ this.updateStatus('Optimizando modelos...', 'Configurando parámetros de rendimiento', 'loading');
1525
1651
  await new Promise(resolve => setTimeout(resolve, 300));
1526
- this.updateStatus('Reconocimiento preparado', '', 'ready');
1527
- if (this.stateManager) {
1528
- this.stateManager.updateCaptureState({ isLoading: false });
1529
- }
1652
+ this.updateStatus('Modelos precargados', '', 'ready');
1653
+ this.stateManager.updateCaptureState({ isLoading: false });
1530
1654
  this.emitReadyEvent();
1655
+ this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
1531
1656
  return { success: true, message: 'Models preloaded successfully' };
1532
1657
  }
1533
1658
  catch (error) {
1534
- this.updateStatus('Error de configuración', 'No se pudieron preparar las herramientas necesarias', 'error');
1535
- if (this.stateManager) {
1536
- this.stateManager.updateCaptureState({ isLoading: false });
1537
- }
1659
+ this.logger.error('Error al precargar modelos:', error);
1660
+ this.updateStatus('Error al cargar modelos', 'No se pudieron descargar los recursos necesarios', 'error');
1661
+ this.stateManager.updateCaptureState({ isLoading: false });
1538
1662
  return { success: false, error: error.message };
1539
1663
  }
1540
1664
  }
1541
1665
  async getCameraInfo() {
1542
- const readyCheck = this.isComponentReady();
1543
- if (!readyCheck.ready) {
1544
- return {
1545
- availableCameras: [],
1546
- selectedCameraId: null,
1547
- deviceType: 'desktop',
1548
- isMultipleCamerasAvailable: false,
1549
- preferredFacing: null,
1550
- error: readyCheck.message
1551
- };
1552
- }
1553
- try {
1554
- return await this.cameraService.getCameraInfo();
1555
- }
1556
- catch (error) {
1557
- return {
1558
- availableCameras: [],
1559
- selectedCameraId: null,
1560
- deviceType: 'desktop',
1561
- isMultipleCamerasAvailable: false,
1562
- preferredFacing: null,
1563
- error: error.message
1564
- };
1565
- }
1666
+ return this.cameraService.getCameraInfo();
1566
1667
  }
1567
1668
  async setPreferredCamera(camera) {
1568
- const readyCheck = this.isComponentReady();
1569
- if (!readyCheck.ready) {
1570
- return {
1571
- success: false,
1572
- error: readyCheck.message,
1573
- selectedCamera: null,
1574
- availableCameras: 0
1575
- };
1576
- }
1577
- try {
1578
- this.preferredCamera = camera;
1579
- this.serviceContainer.updateConfig({ preferredCamera: camera });
1580
- await this.cameraService.enumerateDevices();
1581
- await this.updateCameraInfoWithAutofocus();
1582
- const captureState = this.stateManager.getCaptureState();
1583
- if (captureState.isVideoActive) {
1584
- const selectedCameraId = this.cameraService.getSelectedCameraId();
1585
- if (selectedCameraId) {
1586
- await this.cameraService.switchCamera(selectedCameraId);
1587
- }
1669
+ this.preferredCamera = camera;
1670
+ this.serviceContainer.updateConfig({ preferredCamera: camera });
1671
+ await this.cameraService.enumerateDevices();
1672
+ const captureState = this.stateManager.getCaptureState();
1673
+ if (captureState.isVideoActive) {
1674
+ const selectedCameraId = this.cameraService.getSelectedCameraId();
1675
+ if (selectedCameraId) {
1676
+ await this.cameraService.switchCamera(selectedCameraId);
1588
1677
  }
1589
- return {
1590
- success: true,
1591
- selectedCamera: this.cameraService.getSelectedCameraId(),
1592
- availableCameras: this.cameraService.getAvailableCameras().length
1593
- };
1594
- }
1595
- catch (error) {
1596
- return {
1597
- success: false,
1598
- error: error.message,
1599
- selectedCamera: null,
1600
- availableCameras: 0
1601
- };
1602
1678
  }
1679
+ return {
1680
+ success: true,
1681
+ selectedCamera: this.cameraService.getSelectedCameraId(),
1682
+ availableCameras: this.cameraService.getAvailableCameras().length
1683
+ };
1603
1684
  }
1604
1685
  async setCaptureDelay(delay) {
1605
1686
  if (delay < 0 || delay > 10000) {
1606
- return {
1607
- success: false,
1608
- error: 'Capture delay must be between 0 and 10000 milliseconds',
1609
- captureDelay: this.captureDelay
1610
- };
1687
+ throw new Error('Capture delay must be between 0 and 10000 milliseconds');
1611
1688
  }
1612
- // Allow setting capture delay even during initialization
1613
1689
  this.captureDelay = delay;
1614
- if (this.serviceContainer) {
1615
- this.serviceContainer.updateConfig({ captureDelay: delay });
1616
- }
1690
+ this.serviceContainer.updateConfig({ captureDelay: delay });
1617
1691
  return {
1618
1692
  success: true,
1619
1693
  captureDelay: this.captureDelay
1620
1694
  };
1621
1695
  }
1622
1696
  async getCaptureDelay() {
1623
- // Always allow getting capture delay, even during initialization
1624
1697
  return this.captureDelay;
1625
1698
  }
1699
+ async setTorchEnabled(enabled) {
1700
+ const torchEnabled = await this.cameraService.setTorchEnabled(enabled, this.videoStream);
1701
+ return {
1702
+ success: torchEnabled,
1703
+ enabled: torchEnabled ? enabled : false
1704
+ };
1705
+ }
1706
+ async focusAtPoint(x, y) {
1707
+ const focused = await this.cameraService.focusAtPoint(x, y, this.videoStream);
1708
+ return {
1709
+ success: focused,
1710
+ coordinates: { x, y }
1711
+ };
1712
+ }
1626
1713
  // DETECTION METHODS
1627
1714
  async startDetection() {
1715
+ this.logger.state('INICIANDO_DETECCION');
1628
1716
  try {
1629
1717
  // Paso 1: Verificar y cargar modelos si es necesario
1630
1718
  if (!this.detectionService.isModelLoaded()) {
1631
1719
  const loadStartTime = performance.now();
1632
- this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
1720
+ this.updateStatus('Cargando modelo de detección...', 'Descargando red neuronal para reconocimiento', 'loading');
1633
1721
  this.stateManager.updateCaptureState({ isLoading: true });
1634
1722
  await this.detectionService.loadModel();
1635
- this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1723
+ this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de documentos', 'loading');
1636
1724
  await this.detectionService.loadClassificationModel();
1637
1725
  const loadEndTime = performance.now();
1638
1726
  const loadTime = loadEndTime - loadStartTime;
@@ -1640,13 +1728,13 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1640
1728
  if (this.debug) {
1641
1729
  this.recordOnnxPerformance(loadTime, 0);
1642
1730
  }
1731
+ this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
1643
1732
  }
1644
1733
  // Paso 2: Detectar y configurar dispositivos
1645
- this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1734
+ this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura disponibles', 'loading');
1646
1735
  await this.cameraService.enumerateDevices();
1647
- await this.updateCameraInfoWithAutofocus();
1648
1736
  // Paso 3: Configurar cámara seleccionada
1649
- this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1737
+ this.updateStatus('Configurando cámara...', 'Estableciendo resolución y parámetros óptimos', 'loading');
1650
1738
  const stream = await this.cameraService.setupCamera();
1651
1739
  // Paso 4: Inicializar video y ocultar estado inmediatamente
1652
1740
  this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
@@ -1663,7 +1751,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1663
1751
  this.detectFrame();
1664
1752
  }
1665
1753
  catch (err) {
1666
- this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
1754
+ this.logger.error('Error al inicializar detección:', err);
1755
+ this.updateStatus('Error al iniciar captura', 'No se pudo completar la inicialización', 'error');
1667
1756
  this.stateManager.updateCaptureState({ isLoading: false });
1668
1757
  }
1669
1758
  }
@@ -1673,6 +1762,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1673
1762
  this.videoStream = stream;
1674
1763
  const isRear = this.cameraService.isRearCamera(stream);
1675
1764
  this.shouldMirrorVideo = !isRear;
1765
+ this.logger.state('CAMARA_CONFIGURADA', {
1766
+ isRearCamera: isRear,
1767
+ shouldMirrorVideo: this.shouldMirrorVideo
1768
+ });
1676
1769
  return new Promise((resolve) => {
1677
1770
  this.videoRef.onloadedmetadata = async () => {
1678
1771
  await this.videoRef.play();
@@ -1697,10 +1790,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1697
1790
  }
1698
1791
  return;
1699
1792
  }
1700
- // Adaptive frame skipping for performance
1793
+ // Frame skipping for performance
1701
1794
  this.frameSkipCounter++;
1702
- const currentFrameSkip = this.getAdaptiveFrameSkip();
1703
- if (this.frameSkipCounter <= currentFrameSkip) {
1795
+ if (this.frameSkipCounter <= this.FRAME_SKIP) {
1704
1796
  if (captureState.step !== 'completed') {
1705
1797
  this.animationId = requestAnimationFrame(() => this.detectFrame());
1706
1798
  }
@@ -1776,6 +1868,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1776
1868
  }
1777
1869
  }
1778
1870
  catch (e) {
1871
+ this.logger.error('Error en inferencia de modelo:', e);
1779
1872
  const captureState = this.stateManager.getCaptureState();
1780
1873
  if (captureState.step !== 'completed') {
1781
1874
  setTimeout(() => this.detectFrame(), 100);
@@ -1802,8 +1895,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1802
1895
  clearInterval(this.performanceUpdateInterval);
1803
1896
  this.performanceUpdateInterval = undefined;
1804
1897
  }
1805
- // Clear canvas pool
1806
- this.canvasPool = [];
1807
1898
  this.detectionBoxes = [];
1808
1899
  this.alignmentStartTime = undefined;
1809
1900
  this.hasDocumentDetected = false;
@@ -1817,8 +1908,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1817
1908
  step: 'front',
1818
1909
  isCapturing: false
1819
1910
  };
1820
- const cameraInfo = this.cameraInfoWithAutofocus;
1821
- return (h("div", { key: '69a8df7969288e19088e8809f1fefcc89b237cda', class: "detector-container" }, h("div", { key: '79e59a315d266be9d4fb5eed5740e5ad825d5890', class: "video-container" }, h("video", { key: 'a3312788c739748b774a25acb30c2dcea7f01695', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'ddab107500c2dab24c140049bef0b44aafc35dc7', 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: {
1911
+ const cameraInfo = this.cameraService?.getCameraInfo() || {
1912
+ availableCameras: [],
1913
+ selectedCameraId: null,
1914
+ deviceType: 'desktop'};
1915
+ return (h("div", { key: '9d4d77042218ab3d0bc4fb1ce879db09dc3e78b4', class: "detector-container" }, h("div", { key: '51ac8ccb1275637282b038ad8364d19f14ef61b1', class: "video-container" }, h("video", { key: 'a34f0481da20ec5b7b62b21aa275ec5f6dfec5d1', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: '43b71dfe3eaf20ffa66a4139e24b1af6a2074631', 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: {
1822
1916
  position: 'absolute',
1823
1917
  left: `${box.x}px`,
1824
1918
  top: `${box.y}px`,
@@ -1827,9 +1921,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1827
1921
  border: '2px solid #32406C',
1828
1922
  pointerEvents: 'none',
1829
1923
  boxSizing: 'border-box'
1830
- } })))), this.isMaskReady && (h("div", { key: '370337089a3de949b8cb4ae84dc0037e6044b2d5', class: "overlay-mask" }, h("div", { key: 'cb35c2e42bf1729ad02a8fe7e7abaf27cf1a8d7a', class: "card-outline" }, h("div", { key: '637d72ada63805e7f25051cd1ad9093030cb025a', class: "side side-top" }), h("div", { key: 'c7a465aa686afa41f80433d949715b40f6f349dc', class: "side side-right" }), h("div", { key: '384aade0610b5bc7bdd7db350a70d82f9bebd8ab', class: "side side-bottom" }), h("div", { key: '5506cc346f3fd718e3fda4365f69ea780304b6d6', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'f5b9b62937eabab7c096b7417a13bbf6f822ee8a', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '74b122125a42839d2600cf99fb855140042ab761', class: "skip-section" }, h("div", { key: '0b4a39d1599854d6c445e2c35dcc89a0e4970846', class: "skip-explanation" }, "Si tu documento no tiene lado trasero, da clic en el bot\u00F3n para continuar con el proceso"), h("button", { key: 'b52b47df390cc099c8847ca8a676270bb154294d', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1924
+ } })))), this.isMaskReady && (h("div", { key: 'c2596f8cabb227e3ea3b082495f52b141cb1690f', class: "overlay-mask" }, h("div", { key: 'ba1ea39b5df1cd312a9bd9410b7225a29cfb8a29', class: "card-outline" }, h("div", { key: '6ce4d4d969d5f3fbcf75a4eb285f61d434c5633b', class: "side side-top" }), h("div", { key: '36653ecc4fcda25dfc84bc4d1f4c4c5dfb71ced7', class: "side side-right" }), h("div", { key: '8b108fa8fb370b229a0d1a77cb1ff044319a8f75', class: "side side-bottom" }), h("div", { key: 'a7aa3b45c065a50445aafd5c3ccefb57b0c9c769', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'b94b92273e12a019238e11ae57f21e140f13c3f1', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '144c3741003ec0cc148bb6ce92012d7b3cd110c8', class: "skip-section" }, h("div", { key: 'f0a984a6b75afa374c5d47da9d53e39f98071da0', class: "skip-explanation" }, "Si tu documento no tiene lado trasero, da clic en el bot\u00F3n para continuar con el proceso"), h("button", { key: '57ac30d776e30709aab6e0a621cef889da3cc677', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1831
1925
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
1832
- : 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: 'f04e47749a25197c434b849844c67eadca5c6af3', class: "camera-controls" }, h("button", { key: '82625eef057f59b6dac11c4443ccb315231eac7f', 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: '61cfb89cd9bb549cfff2cb70293db06ee6cdf06b', class: "camera-selector-dropdown" }, h("div", { key: '60f751d8db7b305f2bdfdc089d2110bcff940150', class: "camera-selector-header" }, h("span", { key: '9dae045a34cc06f323ff4850921b495fc845be47' }, "Seleccionar C\u00E1mara"), h("button", { key: '4584d7964d91ddd7f3d980c3e5e90493530d33bd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '9881f2b4fc6ffbdc76db06a7b49d060cefbe6277', 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}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '3dfe9ffc7b4cc9afb199a78026cef6d4b469805e', class: "device-info" }, h("small", { key: 'bddc865a30469bd2724624b2f7c8f641f9d97a07' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '2fd963e44f69f80d17c6bbbfa3c9b5e11bc816be', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'ba4872ea5388a0ac3134be61feff5412373465ab', class: "flip-animation" }, h("div", { key: 'e83a92cea79f3e85aefc6964c5d9de7b495d511e', class: "id-card-icon" }), h("div", { key: '1d06b51fd6ad14d9b37aa6bca72bd13c951a386a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '503c377896d5fdc3ae8476b09f511d571f23ae72', class: "success-animation" }, h("div", { key: 'e1929c245b0d1479a6fb1513b0b37c53070c4291', class: "check-icon" }), h("div", { key: '024c897c70427b205390770fc3dfb999ffcc84d3', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '54e350707628e5c88ae2900e412e6bda2eace943', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '82ea10569ed46285686cf3e63885ca1941bb559c', class: "status-spinner" })), h("div", { key: '1fecf179c10965cccf1be3dc0a54afd377ba38ad', class: "status-content" }, h("div", { key: '1b301c1bbb02e5cac2e6babcb20c190662d45c68', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '5b6c61308d17176daade02c951a436d114a74100', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'b8104db64208dc87a617a072957c007f28055194', class: "performance-monitor" }, h("div", { key: 'e66aec071f10821be2713858dcc251c04f480e63', class: "performance-expanded" }, h("div", { key: '1ae09a91d0704ab21b9eb51097cf452fd5f44359', class: "metrics-row" }, h("div", { key: '340d4d16d048a5217fe13d5de63618c9ea221165', class: "metric-compact" }, h("span", { key: '20cfda14969ef70d232edd68f2e9902bd44b5d9a', class: "metric-label" }, "FPS"), h("span", { key: '3a089c2814a71ed6513bf05b0120a66aa159daee', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '23f700def988c3220fc5df37aeb57a0a20ff56b3', class: "metric-compact" }, h("span", { key: '441e8490a871469813a6d832114e03583d375e91', class: "metric-label" }, "MEM"), h("span", { key: '51f7c49164c9d5e699141c92c88375fc99b312ed', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '6547fee80e2a1db7c9ff889a1c478fc8248159e6', class: "metrics-row" }, h("div", { key: '7b2d1642fd122ee0fbed36387afb23152afe10c2', class: "metric-compact" }, h("span", { key: 'a9137119bf7da366f18feaf8f28807ee40ad7a25', class: "metric-label" }, "INF"), h("span", { key: '5cbc0c040102688f1039cdcd4bb58020248cf700', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'cb4a86f5da46e4081d997c8b8bb6a899f06dfcd2', class: "metric-compact" }, h("span", { key: '88ad5e1d842eb156cf6813ecaa9d2bec76a5999e', class: "metric-label" }, "FRAME"), h("span", { key: 'a9b14cd98b64dcfc058d3b68a3c168a9e092fb3c', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '86e01f3596682cb8ad2ff5464748e6ecb7db374a', class: "metrics-row" }, h("div", { key: 'e73b9d39e9bcbc5d46fa0ba0a737700e3e5566d9', class: "metric-compact" }, h("span", { key: '8ecfe969dfe77c87d9da76d7390706014b4872f3', class: "metric-label" }, "DET"), h("span", { key: 'bd1a6a52e0bd1fb4d377413d7515e4e0f00da10f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '36539ae98787fd60642082db1bf89031003c4bef', class: "metric-compact" }, h("span", { key: '7f3a81629c09053e3beeca97f485da0f60412052', class: "metric-label" }, "RATE"), h("span", { key: '72399e0ac39270c82b265ce64e402f84cda9fb2c', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '4e3a914ba2f5993f85de7b9dd6dc93d76b41c96d', class: "watermark" }, h("img", { key: 'aa634c2ba2090f64ee9b41aa1d75a9114d46fd63', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1926
+ : 'Saltar reverso'))), captureState.isVideoActive && (h("div", { key: 'bd45628707e0169317ed3dd4247e1056d7046104', class: "camera-controls" }, h("button", { key: 'e750fbf4d5d76d9e9596823bb2b86801ef19e485', 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: '40ada780a8ea13aeeaee9cb39bc6496f69f2679d', class: "camera-selector-dropdown" }, h("div", { key: '3a920bb02ba024f0359513f10544bf0528ecee6d', class: "camera-selector-header" }, h("span", { key: 'e5efd478a602b13821ba53cb9a7a59d1b4a71178' }, "Seleccionar C\u00E1mara"), h("button", { key: '2df6cc11e162be74862029e33815927e3fa8f2f8', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '06934642dff134c9e1a6872f935a5892a9c7b835', 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: '8d5ede287c2efb0ebe418bee8dc7fb310443c991', class: "device-info" }, h("small", { key: 'b7dd10565113694b09f01ab28ffa29262ae1d2bc' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '2f9e549e6c0c56d4db5835e58361fb17bba6cf90', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: 'a6ef0ed127f23e9d764896ed743ca8d8454851a6', class: "flip-animation" }, h("div", { key: '0b04411f88a634bb3243b00ca2d5a45fd6962dea', class: "id-card-icon" }), h("div", { key: '9327be58879b407cea87f455989406e58302e9d1', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'bd268578b5003e99256e3340b4a9a6fb7656be64', class: "success-animation" }, h("div", { key: '064e0a5b9851de14bc6829f028e7ea7161fd1633', class: "check-icon" }), h("div", { key: 'bdcd94a8806399e396e218a359c10b11d05f38d6', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '3d9af617376c16e44e065873919c3d90ff46110a', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'be7b56decfd6f12dcca46b2a9456480e3d5ac00e', class: "status-spinner" })), h("div", { key: '5fe9850883cb749cebaa18c3e9c13ca21325e373', class: "status-content" }, h("div", { key: '43fdb50691e4b98e56126af1a129f625061128bf', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'b76cbfa4a0349421fcd535d3fd8bf40c89ca3aef', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: 'eddefb464af0487ab9ab2a0f76dccbb6a2092e51', class: "performance-monitor" }, h("div", { key: '7b7d93b56cb4cd4fbb1bdde89c6150b282fa805e', class: "performance-expanded" }, h("div", { key: 'f302e8b9d985fb8cad53c4c4e0d37a3bf5a63261', class: "metrics-row" }, h("div", { key: 'e8d8f2420ee8643a7f454d38065c75b5be598f77', class: "metric-compact" }, h("span", { key: '6e52eb5bb06e0df57e61774de38c31349f45e22b', class: "metric-label" }, "FPS"), h("span", { key: '6636f9cea20c49b49c17db86188e2f6b214c45c3', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'afa1d9dec05b14256a1b1fcec8638b8c21a56e37', class: "metric-compact" }, h("span", { key: '9c55e30f5d01582648bacc1fb38ca1990485a591', class: "metric-label" }, "MEM"), h("span", { key: 'ca263601e8715027f95ffb2d5413a643dc570cf2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '03caf19961fd01b7781484d49d8121b1e78c161f', class: "metrics-row" }, h("div", { key: 'bffb08f01f703411f7c8605e4f8d95b1c37b5c0f', class: "metric-compact" }, h("span", { key: '9b92279b3253cc669412733facb3751f4344d69f', class: "metric-label" }, "INF"), h("span", { key: '3234d4150e2e1479e3b552767af8123e69911d6c', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '77082d2669db53f19fb50b3bbfa1ebefb0b86ff4', class: "metric-compact" }, h("span", { key: 'cc6ef96c72c800ba36dab2a388f1de863b2dacd5', class: "metric-label" }, "FRAME"), h("span", { key: 'e2bb4b7351869a76e810f8bbf20d805154495e35', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '5700d7ef839fa77686299d79445203090dc8079f', class: "metrics-row" }, h("div", { key: 'a2999a035fccb7f250c01b084075490678513c87', class: "metric-compact" }, h("span", { key: '3677d58665996fa5969507dbaff9e956b2ea54ee', class: "metric-label" }, "DET"), h("span", { key: 'c584312438b339c82a3bdc33f00fb985ff2a6f55', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '58866219f213bf8607083f30be2d5ca5d0515cea', class: "metric-compact" }, h("span", { key: 'd90b450051331347f219b6c0f197536d0dcee9b7', class: "metric-label" }, "RATE"), h("span", { key: 'f6e6cd7948e06014e5a0416e4e81394fa36ec61d', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'c1d0194f181a0082cc1aa5452de382908da61b65', class: "watermark" }, h("img", { key: 'e04d35f3c79a2b4a3922ab5b0f102220171fa7c0', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1833
1927
  }
1834
1928
  // Utility methods
1835
1929
  updateDetectionBoxes(boxes) {
@@ -1910,18 +2004,35 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1910
2004
  if (allSidesAligned && bestBox) {
1911
2005
  cardOutline?.classList.add('perfect-match');
1912
2006
  corners?.forEach(corner => corner.classList.add('perfect-match'));
2007
+ // Debug logging
2008
+ this.logger.state('CAPTURE_EVALUATION', {
2009
+ allSidesAligned: true,
2010
+ hasScreenshotTaken: this.hasScreenshotTaken,
2011
+ captureDelay: this.captureDelay,
2012
+ alignmentStartTime: this.alignmentStartTime
2013
+ });
1913
2014
  if (!this.hasScreenshotTaken) {
1914
2015
  const currentTime = Date.now();
1915
2016
  // Initialize alignment start time if not set
1916
2017
  if (!this.alignmentStartTime) {
1917
2018
  this.alignmentStartTime = currentTime;
2019
+ this.logger.state('ALIGNMENT_TIMER_STARTED', { startTime: currentTime });
1918
2020
  }
1919
2021
  // Check if document has been aligned for the configured delay
1920
2022
  const alignmentDuration = currentTime - this.alignmentStartTime;
2023
+ this.logger.state('ALIGNMENT_DURATION_CHECK', {
2024
+ alignmentDuration,
2025
+ captureDelay: this.captureDelay,
2026
+ readyToCapture: alignmentDuration >= this.captureDelay
2027
+ });
1921
2028
  if (alignmentDuration >= this.captureDelay) {
2029
+ this.logger.state('TRIGGERING_CAPTURE', {
2030
+ alignmentDuration,
2031
+ captureDelay: this.captureDelay
2032
+ });
1922
2033
  this.lastDetectedBox = bestBox;
1923
- this.takeScreenshot().catch(() => {
1924
- // Handle screenshot error silently
2034
+ this.takeScreenshot().catch(error => {
2035
+ this.logger.error('Error al tomar captura de pantalla:', error);
1925
2036
  });
1926
2037
  this.hasScreenshotTaken = true;
1927
2038
  this.alignmentStartTime = undefined;
@@ -1947,12 +2058,21 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1947
2058
  async takeScreenshot() {
1948
2059
  if (!this.videoRef || !this.lastDetectedBox)
1949
2060
  return;
2061
+ this.logger.state('INICIANDO_CAPTURA', {
2062
+ captureStep: this.stateManager.getCaptureState().step,
2063
+ detectedBox: this.lastDetectedBox,
2064
+ videoResolution: {
2065
+ width: this.videoRef.videoWidth,
2066
+ height: this.videoRef.videoHeight
2067
+ }
2068
+ });
1950
2069
  this.stateManager.updateCaptureState({ isCapturing: true });
1951
2070
  this.triggerCaptureAnimation();
1952
- // Use pooled canvas for optimization
1953
- const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
2071
+ // Create capture canvas
2072
+ const captureCanvas = document.createElement('canvas');
2073
+ captureCanvas.width = this.videoRef.videoWidth;
2074
+ captureCanvas.height = this.videoRef.videoHeight;
1954
2075
  const captureCtx = captureCanvas.getContext('2d', { alpha: false });
1955
- captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
1956
2076
  captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
1957
2077
  // Calculate crop coordinates
1958
2078
  const INPUT_SIZE = 320;
@@ -1962,10 +2082,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1962
2082
  const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
1963
2083
  const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
1964
2084
  const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
1965
- // Use pooled canvas for cropped version
1966
- const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
2085
+ // Create cropped version
2086
+ const croppedCanvas = document.createElement('canvas');
2087
+ croppedCanvas.width = cropWidth;
2088
+ croppedCanvas.height = cropHeight;
1967
2089
  const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
1968
- croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
1969
2090
  croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
1970
2091
  const captureState = this.stateManager.getCaptureState();
1971
2092
  if (captureState.step === 'front') {
@@ -1979,6 +2100,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1979
2100
  if (this.useDocumentClassification) {
1980
2101
  const classification = await this.detectionService.classifyDocument(croppedCanvas);
1981
2102
  if (classification && classification.class === 'passport') {
2103
+ this.logger.state('PASAPORTE_DETECTADO_SALTANDO_REVERSO', { classification: classification?.class });
1982
2104
  this.completeProcess(true);
1983
2105
  return;
1984
2106
  }
@@ -2005,9 +2127,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2005
2127
  });
2006
2128
  this.completeProcess(false);
2007
2129
  }
2008
- // Return canvases to pool after use
2009
- this.returnCanvasToPool(captureCanvas);
2010
- this.returnCanvasToPool(croppedCanvas);
2011
2130
  }
2012
2131
  triggerCaptureAnimation() {
2013
2132
  const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
@@ -2036,6 +2155,11 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2036
2155
  setTimeout(() => {
2037
2156
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2038
2157
  }, 3000);
2158
+ this.logger.state('PROCESO_COMPLETADO', {
2159
+ skippedBack,
2160
+ totalImages: capturedImages.metadata.totalImages,
2161
+ timestamp: new Date().toISOString()
2162
+ });
2039
2163
  }
2040
2164
  stopDetection() {
2041
2165
  if (this.animationId) {
@@ -2044,6 +2168,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2044
2168
  }
2045
2169
  this.clearBackDocumentTimer();
2046
2170
  this.detectionBoxes = [];
2171
+ this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
2047
2172
  }
2048
2173
  startBackDocumentTimer() {
2049
2174
  if (!this.enableBackDocumentTimer)
@@ -2072,15 +2197,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2072
2197
  return; // Don't toggle if switching camera
2073
2198
  this.showCameraSelector = !this.showCameraSelector;
2074
2199
  }
2075
- async updateCameraInfoWithAutofocus() {
2076
- try {
2077
- const cameraInfo = await this.cameraService.getCameraInfo();
2078
- this.cameraInfoWithAutofocus = cameraInfo;
2079
- }
2080
- catch (error) {
2081
- // Keep existing state if update fails
2082
- }
2083
- }
2084
2200
  async handleCameraSwitch(cameraId) {
2085
2201
  if (this.isSwitchingCamera)
2086
2202
  return; // Prevent multiple simultaneous switches
@@ -2088,6 +2204,10 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2088
2204
  // Close the selector immediately when user selects a camera
2089
2205
  this.showCameraSelector = false;
2090
2206
  this.isSwitchingCamera = true;
2207
+ this.logger.state('INICIANDO_CAMBIO_CAMARA', {
2208
+ from: this.cameraService.getSelectedCameraId(),
2209
+ to: cameraId
2210
+ });
2091
2211
  // Stop current video stream
2092
2212
  if (this.videoStream) {
2093
2213
  this.videoStream.getTracks().forEach(track => track.stop());
@@ -2098,17 +2218,21 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2098
2218
  const newStream = await this.cameraService.setupCamera();
2099
2219
  // Update video element
2100
2220
  await this.initializeVideoStream(newStream);
2101
- // Update camera info with autofocus data
2102
- await this.updateCameraInfoWithAutofocus();
2221
+ this.logger.state('CAMBIO_CAMARA_EXITOSO', {
2222
+ newCameraId: cameraId,
2223
+ isRearCamera: this.cameraService.isRearCamera(newStream)
2224
+ });
2103
2225
  }
2104
2226
  catch (error) {
2227
+ this.logger.error('Error al cambiar cámara:', error);
2105
2228
  // Try to restore previous camera if switch failed
2106
2229
  try {
2107
2230
  const fallbackStream = await this.cameraService.setupCamera();
2108
2231
  await this.initializeVideoStream(fallbackStream);
2109
2232
  }
2110
2233
  catch (fallbackError) {
2111
- this.updateStatus('Error al cambiar cámara', 'No se pudo cambiar el dispositivo', 'error');
2234
+ this.logger.error('Error al restaurar cámara anterior:', fallbackError);
2235
+ this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
2112
2236
  }
2113
2237
  }
2114
2238
  finally {
@@ -2149,7 +2273,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2149
2273
  this.stateManager.updateCaptureState({ isVideoActive: false, isLoading: false });
2150
2274
  }
2151
2275
  this.isMaskReady = false;
2152
- this.updateStatus('Captura finalizada', '', 'ready');
2276
+ this.updateStatus('Sesión finalizada', '', 'ready');
2153
2277
  this.detectionBoxes = [];
2154
2278
  this.cleanup();
2155
2279
  }
@@ -2160,6 +2284,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2160
2284
  this.performanceUpdateInterval = window.setInterval(() => {
2161
2285
  this.updatePerformanceMetrics();
2162
2286
  }, 500);
2287
+ this.logger.debug('Monitor de performance inicializado');
2163
2288
  }
2164
2289
  updatePerformanceMetrics() {
2165
2290
  const currentTime = performance.now();
@@ -2202,66 +2327,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2202
2327
  if (detectionsFound > 0) {
2203
2328
  this.performanceMetrics.successfulDetections++;
2204
2329
  }
2205
- // Update performance history for adaptive frame skipping
2206
- this.performanceHistory.push(processingTime);
2207
- if (this.performanceHistory.length > this.PERFORMANCE_HISTORY_SIZE) {
2208
- this.performanceHistory.shift();
2209
- }
2210
- }
2211
- // Adaptive frame skipping based on performance
2212
- getAdaptiveFrameSkip() {
2213
- // Base conditions for high frame skip
2214
- if (this.consecutiveFailures > 20)
2215
- return this.MAX_FRAME_SKIP;
2216
- if (this.performanceMetrics.inferenceTime > 200)
2217
- return 6;
2218
- if (this.performanceMetrics.memoryUsage > 200)
2219
- return 5;
2220
- // Calculate average processing time
2221
- if (this.performanceHistory.length > 0) {
2222
- const avgProcessingTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
2223
- if (avgProcessingTime > 100)
2224
- return 4;
2225
- if (avgProcessingTime > 80)
2226
- return 3;
2227
- if (avgProcessingTime > 60)
2228
- return this.BASE_FRAME_SKIP + 1;
2229
- }
2230
- // Low memory devices get higher frame skip
2231
- if (this.performanceMetrics.memoryUsage > 150)
2232
- return 4;
2233
- // Performance is good, use base frame skip
2234
- return this.BASE_FRAME_SKIP;
2235
- }
2236
- // Canvas pool management for screenshots
2237
- getPooledCanvas(width, height) {
2238
- // Try to find a canvas with matching dimensions
2239
- const matchingIndex = this.canvasPool.findIndex(canvas => canvas.width === width && canvas.height === height);
2240
- if (matchingIndex !== -1) {
2241
- return this.canvasPool.splice(matchingIndex, 1)[0];
2242
- }
2243
- // If no matching canvas, try to reuse any canvas and resize
2244
- if (this.canvasPool.length > 0) {
2245
- const canvas = this.canvasPool.pop();
2246
- canvas.width = width;
2247
- canvas.height = height;
2248
- return canvas;
2249
- }
2250
- // Create new canvas if pool is empty
2251
- const canvas = document.createElement('canvas');
2252
- canvas.width = width;
2253
- canvas.height = height;
2254
- return canvas;
2255
- }
2256
- returnCanvasToPool(canvas) {
2257
- // Only return to pool if not at max capacity
2258
- if (this.canvasPool.length < this.MAX_CANVAS_POOL_SIZE) {
2259
- // Clear the canvas before returning to pool
2260
- const ctx = canvas.getContext('2d');
2261
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2262
- this.canvasPool.push(canvas);
2263
- }
2264
- // If pool is full, let the canvas be garbage collected
2265
2330
  }
2266
2331
  static get style() { return myComponentCss; }
2267
2332
  }, [1, "jaak-stamps", {
@@ -2281,7 +2346,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2281
2346
  "showCameraSelector": [32],
2282
2347
  "isSwitchingCamera": [32],
2283
2348
  "hasDocumentDetected": [32],
2284
- "cameraInfoWithAutofocus": [32],
2285
2349
  "currentStatus": [32],
2286
2350
  "performanceData": [32],
2287
2351
  "backDocumentTimerRemaining": [32],
@@ -2296,7 +2360,9 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2296
2360
  "getCameraInfo": [64],
2297
2361
  "setPreferredCamera": [64],
2298
2362
  "setCaptureDelay": [64],
2299
- "getCaptureDelay": [64]
2363
+ "getCaptureDelay": [64],
2364
+ "setTorchEnabled": [64],
2365
+ "focusAtPoint": [64]
2300
2366
  }]);
2301
2367
  function defineCustomElement$1() {
2302
2368
  if (typeof customElements === "undefined") {