@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 { r as registerInstance, c as createEvent, a as getElement, h } from './index-BP1Q4KOg.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 = class {
1080
1302
  constructor(hostRef) {
@@ -1104,16 +1326,9 @@ const JaakStamps = class {
1104
1326
  showCameraSelector = false;
1105
1327
  isSwitchingCamera = false;
1106
1328
  hasDocumentDetected = false;
1107
- cameraInfoWithAutofocus = {
1108
- availableCameras: [],
1109
- selectedCameraId: null,
1110
- deviceType: 'desktop',
1111
- isMultipleCamerasAvailable: false,
1112
- preferredFacing: null
1113
- };
1114
1329
  currentStatus = {
1115
- message: 'Preparando capturador...',
1116
- description: 'Configurando herramientas de captura',
1330
+ message: 'Inicializando componente...',
1331
+ description: 'Configurando servicios y cargando recursos',
1117
1332
  type: 'initializing',
1118
1333
  isInitialized: false
1119
1334
  };
@@ -1130,6 +1345,7 @@ const JaakStamps = class {
1130
1345
  backDocumentTimerRemaining = 0;
1131
1346
  // Services
1132
1347
  serviceContainer;
1348
+ logger;
1133
1349
  eventBus;
1134
1350
  stateManager;
1135
1351
  cameraService;
@@ -1160,25 +1376,19 @@ const JaakStamps = class {
1160
1376
  lastUpdateTime: 0
1161
1377
  };
1162
1378
  performanceUpdateInterval;
1163
- // Performance optimization with adaptive frame skipping
1379
+ // Performance optimization
1164
1380
  frameSkipCounter = 0;
1165
- BASE_FRAME_SKIP = 2;
1166
- MAX_FRAME_SKIP = 8;
1381
+ FRAME_SKIP = 2;
1167
1382
  consecutiveFailures = 0;
1168
1383
  MAX_FAILURES = 30;
1169
1384
  lastInferenceTime = 0;
1170
1385
  MIN_INFERENCE_INTERVAL = 50;
1171
- performanceHistory = [];
1172
- PERFORMANCE_HISTORY_SIZE = 10;
1173
- // Canvas pool for optimized screenshot capture
1174
- canvasPool = [];
1175
- MAX_CANVAS_POOL_SIZE = 3;
1176
1386
  async componentDidLoad() {
1177
- this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
1387
+ this.updateStatus('Iniciando servicios...', 'Configurando módulos internos', 'initializing');
1178
1388
  await this.initializeServices();
1179
- this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
1389
+ this.updateStatus('Configurando eventos...', 'Preparando comunicación entre servicios', 'initializing');
1180
1390
  await this.setupEventListeners();
1181
- this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1391
+ this.updateStatus('Inicializando cámara...', 'Detectando dispositivos disponibles', 'initializing');
1182
1392
  await this.initializeComponent();
1183
1393
  if (this.debug) {
1184
1394
  this.initializePerformanceMonitor();
@@ -1195,23 +1405,33 @@ const JaakStamps = class {
1195
1405
  captureDelay: this.captureDelay
1196
1406
  };
1197
1407
  this.serviceContainer = new ServiceContainer(config);
1408
+ this.logger = this.serviceContainer.getLogger();
1198
1409
  this.eventBus = this.serviceContainer.getEventBus();
1199
1410
  this.stateManager = this.serviceContainer.getStateManager();
1200
1411
  this.cameraService = this.serviceContainer.getCameraService();
1201
1412
  this.detectionService = this.serviceContainer.getDetectionService();
1413
+ this.logger.state('SERVICIOS_INICIALIZADOS', { timestamp: Date.now() });
1202
1414
  }
1203
1415
  async setupEventListeners() {
1204
1416
  this.eventBus.on('state-changed', (data) => {
1205
1417
  this.handleStateChange(data);
1206
1418
  });
1207
- this.eventBus.on('camera-changed', () => {
1208
- // Camera changed event
1419
+ this.eventBus.on('camera-changed', (cameraId) => {
1420
+ this.logger.state('CAMARA_CAMBIADA_EVENT', { cameraId });
1209
1421
  });
1210
- this.eventBus.on('error', () => {
1211
- // Handle service errors
1422
+ this.eventBus.on('error', (error) => {
1423
+ this.logger.error('Error en servicio:', error);
1212
1424
  });
1213
1425
  }
1214
1426
  async initializeComponent() {
1427
+ this.logger.state('COMPONENTE_INICIALIZANDO', {
1428
+ debug: this.debug,
1429
+ maskSize: this.maskSize,
1430
+ cropMargin: this.cropMargin,
1431
+ useDocumentClassification: this.useDocumentClassification,
1432
+ preferredCamera: this.preferredCamera,
1433
+ captureDelay: this.captureDelay
1434
+ });
1215
1435
  this.validateProps();
1216
1436
  if (this.debug) {
1217
1437
  this.stateManager.updateCaptureState({ isLoading: true });
@@ -1220,29 +1440,32 @@ const JaakStamps = class {
1220
1440
  this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura', 'initializing');
1221
1441
  await this.cameraService.detectDeviceType();
1222
1442
  await this.cameraService.enumerateDevices();
1223
- await this.updateCameraInfoWithAutofocus();
1224
- this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'initializing');
1443
+ this.updateStatus('Cargando modelo IA...', 'Preparando reconocimiento de documentos', 'initializing');
1225
1444
  await this.loadOnnxRuntime();
1226
1445
  this.initializeResizeObserver();
1227
1446
  }
1228
1447
  validateProps() {
1229
1448
  if (this.maskSize < 50 || this.maskSize > 100) {
1449
+ this.logger.warn(`Propiedad maskSize inválida. Valor: ${this.maskSize}, esperado: 50-100. Usando valor por defecto: 90`);
1230
1450
  this.maskSize = 90;
1231
1451
  }
1232
1452
  if (this.cropMargin < 0 || this.cropMargin > 100) {
1453
+ this.logger.warn(`Propiedad cropMargin inválida. Valor: ${this.cropMargin}, esperado: 0-100. Usando valor por defecto: 0`);
1233
1454
  this.cropMargin = 0;
1234
1455
  }
1235
1456
  const validOptions = ['auto', 'front', 'back'];
1236
1457
  if (!validOptions.includes(this.preferredCamera)) {
1458
+ this.logger.warn(`Propiedad preferredCamera inválida. Valor: ${this.preferredCamera}, esperado: ${validOptions.join(', ')}. Usando valor por defecto: 'auto'`);
1237
1459
  this.preferredCamera = 'auto';
1238
1460
  }
1239
1461
  if (this.captureDelay < 0 || this.captureDelay > 10000) {
1462
+ this.logger.warn(`Propiedad captureDelay inválida. Valor: ${this.captureDelay}, esperado: 0-10000. Usando valor por defecto: 1500`);
1240
1463
  this.captureDelay = 1500;
1241
1464
  }
1242
1465
  }
1243
1466
  async loadOnnxRuntime() {
1244
1467
  if (!window.ort) {
1245
- this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
1468
+ this.updateStatus('Descargando librerías...', 'Obteniendo recursos de reconocimiento', 'initializing');
1246
1469
  const script = document.createElement('script');
1247
1470
  script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
1248
1471
  document.head.appendChild(script);
@@ -1282,6 +1505,11 @@ const JaakStamps = class {
1282
1505
  emitReadyEvent() {
1283
1506
  const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
1284
1507
  this.isReady.emit(isDocumentReady);
1508
+ this.logger.state('COMPONENTE_LISTO', {
1509
+ ortLibraryLoaded: !!window.ort,
1510
+ modelPreloaded: this.detectionService.isModelLoaded(),
1511
+ isReady: isDocumentReady
1512
+ });
1285
1513
  }
1286
1514
  handleStateChange(data) {
1287
1515
  }
@@ -1298,6 +1526,7 @@ const JaakStamps = class {
1298
1526
  const container = this.detectionContainer.parentElement;
1299
1527
  const rect = container.getBoundingClientRect();
1300
1528
  this.updateMaskDimensions(rect);
1529
+ this.logger.debug('Container redimensionado:', { width: rect.width, height: rect.height });
1301
1530
  }
1302
1531
  }
1303
1532
  updateMaskDimensions(containerRect) {
@@ -1351,38 +1580,16 @@ const JaakStamps = class {
1351
1580
  this.el.style.setProperty('--mask-center-x', `${videoCenterXPercent}%`);
1352
1581
  this.el.style.setProperty('--mask-center-y', `${videoCenterYPercent}%`);
1353
1582
  this.isMaskReady = true;
1583
+ this.logger.state('DIMENSIONES_MASCARA_ACTUALIZADAS', {
1584
+ video: { width: videoWidth, height: videoHeight },
1585
+ displayed: { width: displayedVideoWidth, height: displayedVideoHeight },
1586
+ mask: { widthPercent: maskWidthPercent, heightPercent: maskHeightPercent },
1587
+ center: { x: videoCenterXPercent, y: videoCenterYPercent },
1588
+ offset: { x: videoOffsetX, y: videoOffsetY }
1589
+ });
1354
1590
  }
1355
1591
  // PUBLIC METHODS
1356
- // Helper method to check if component is ready for operations
1357
- isComponentReady() {
1358
- if (!this.currentStatus.isInitialized) {
1359
- return {
1360
- ready: false,
1361
- message: 'El componente aún se está inicializando',
1362
- status: 'initializing'
1363
- };
1364
- }
1365
- if (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') {
1366
- return {
1367
- ready: false,
1368
- message: 'El componente está cargando recursos',
1369
- status: 'loading'
1370
- };
1371
- }
1372
- if (!this.serviceContainer || !this.stateManager || !this.cameraService || !this.detectionService) {
1373
- return {
1374
- ready: false,
1375
- message: 'Los servicios del componente no están disponibles',
1376
- status: 'error'
1377
- };
1378
- }
1379
- return { ready: true };
1380
- }
1381
1592
  async getCapturedImages() {
1382
- const readyCheck = this.isComponentReady();
1383
- if (!readyCheck.ready) {
1384
- throw new Error(readyCheck.message);
1385
- }
1386
1593
  const state = this.stateManager.getCaptureState();
1387
1594
  if (state.step !== 'completed') {
1388
1595
  throw new Error('El proceso de captura no ha sido completado');
@@ -1390,128 +1597,47 @@ const JaakStamps = class {
1390
1597
  return this.stateManager.getCapturedImages();
1391
1598
  }
1392
1599
  async isProcessCompleted() {
1393
- const readyCheck = this.isComponentReady();
1394
- if (!readyCheck.ready) {
1395
- return false; // Return false instead of error for this method
1396
- }
1397
1600
  return this.stateManager.isProcessCompleted();
1398
1601
  }
1399
1602
  async startCapture() {
1400
- const readyCheck = this.isComponentReady();
1401
- if (!readyCheck.ready) {
1402
- this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
1403
- return { success: false, error: readyCheck.message };
1404
- }
1405
- try {
1406
- await this.startDetection();
1407
- return { success: true };
1408
- }
1409
- catch (error) {
1410
- this.updateStatus('Error al iniciar captura', error.message, 'error');
1411
- return { success: false, error: error.message };
1412
- }
1603
+ await this.startDetection();
1413
1604
  }
1414
1605
  async stopCapture() {
1415
- const readyCheck = this.isComponentReady();
1416
- if (!readyCheck.ready) {
1417
- return { success: false, error: readyCheck.message };
1418
- }
1419
- try {
1420
- this.exitSession();
1421
- return { success: true };
1422
- }
1423
- catch (error) {
1424
- return { success: false, error: error.message };
1425
- }
1606
+ this.exitSession();
1426
1607
  }
1427
1608
  async resetCapture() {
1428
- const readyCheck = this.isComponentReady();
1429
- if (!readyCheck.ready) {
1430
- return { success: false, error: readyCheck.message };
1431
- }
1432
- try {
1433
- this.resetDetection();
1434
- return { success: true };
1435
- }
1436
- catch (error) {
1437
- return { success: false, error: error.message };
1438
- }
1609
+ this.resetDetection();
1439
1610
  }
1440
1611
  async skipBackCapture() {
1441
- const readyCheck = this.isComponentReady();
1442
- if (!readyCheck.ready) {
1443
- return { success: false, error: readyCheck.message };
1444
- }
1445
- try {
1446
- const captureState = this.stateManager.getCaptureState();
1447
- const capturedImages = this.stateManager.getCapturedImages();
1448
- if (captureState.step === 'back' && capturedImages.front.fullFrame) {
1449
- this.completeProcess(true);
1450
- return { success: true };
1451
- }
1452
- else {
1453
- return { success: false, error: 'No se puede saltar el reverso en el estado actual' };
1454
- }
1455
- }
1456
- catch (error) {
1457
- return { success: false, error: error.message };
1612
+ const captureState = this.stateManager.getCaptureState();
1613
+ const capturedImages = this.stateManager.getCapturedImages();
1614
+ if (captureState.step === 'back' && capturedImages.front.fullFrame) {
1615
+ this.completeProcess(true);
1458
1616
  }
1459
1617
  }
1460
1618
  async getStatus() {
1461
- const readyCheck = this.isComponentReady();
1462
- if (!readyCheck.ready) {
1463
- return {
1464
- isVideoActive: false,
1465
- captureStep: 'front',
1466
- hasImages: false,
1467
- isProcessCompleted: false,
1468
- isModelPreloaded: false,
1469
- componentStatus: readyCheck.status,
1470
- componentMessage: readyCheck.message
1471
- };
1472
- }
1473
- try {
1474
- const captureState = this.stateManager.getCaptureState();
1475
- const capturedImages = this.stateManager.getCapturedImages();
1476
- return {
1477
- isVideoActive: captureState.isVideoActive,
1478
- captureStep: captureState.step,
1479
- hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
1480
- isProcessCompleted: this.stateManager.isProcessCompleted(),
1481
- isModelPreloaded: this.detectionService.isModelLoaded(),
1482
- componentStatus: 'ready'
1483
- };
1484
- }
1485
- catch (error) {
1486
- return {
1487
- isVideoActive: false,
1488
- captureStep: 'front',
1489
- hasImages: false,
1490
- isProcessCompleted: false,
1491
- isModelPreloaded: false,
1492
- componentStatus: 'error',
1493
- componentMessage: error.message
1494
- };
1495
- }
1619
+ const captureState = this.stateManager.getCaptureState();
1620
+ const capturedImages = this.stateManager.getCapturedImages();
1621
+ return {
1622
+ isVideoActive: captureState.isVideoActive,
1623
+ captureStep: captureState.step,
1624
+ hasImages: !!(capturedImages.front.fullFrame || capturedImages.back.fullFrame),
1625
+ isProcessCompleted: this.stateManager.isProcessCompleted(),
1626
+ isModelPreloaded: this.detectionService.isModelLoaded()
1627
+ };
1496
1628
  }
1497
1629
  async preloadModel() {
1498
- const readyCheck = this.isComponentReady();
1499
- if (!readyCheck.ready) {
1500
- this.updateStatus(readyCheck.message, 'Espere a que termine la inicialización', readyCheck.status);
1501
- return { success: false, error: readyCheck.message };
1502
- }
1503
1630
  if (this.detectionService.isModelLoaded()) {
1504
- this.updateStatus('Reconocimiento listo', '', 'ready');
1631
+ this.logger.state('MODELO_YA_PRECARGADO');
1632
+ this.updateStatus('Modelos ya cargados', '', 'ready');
1505
1633
  return { success: true, message: 'Model already loaded' };
1506
1634
  }
1507
1635
  try {
1508
1636
  const loadStartTime = performance.now();
1509
- this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
1510
- if (this.stateManager) {
1511
- this.stateManager.updateCaptureState({ isLoading: true });
1512
- }
1637
+ this.updateStatus('Descargando modelo de detección...', 'Obteniendo red neuronal para reconocimiento de documentos', 'loading');
1638
+ this.stateManager.updateCaptureState({ isLoading: true });
1513
1639
  await this.detectionService.loadModel();
1514
- this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1640
+ this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de tipos de documento', 'loading');
1515
1641
  await this.detectionService.loadClassificationModel();
1516
1642
  const loadEndTime = performance.now();
1517
1643
  const loadTime = loadEndTime - loadStartTime;
@@ -1519,118 +1645,80 @@ const JaakStamps = class {
1519
1645
  if (this.debug) {
1520
1646
  this.recordOnnxPerformance(loadTime, 0);
1521
1647
  }
1522
- this.updateStatus('Finalizando configuración...', 'Preparando herramientas para captura', 'loading');
1648
+ this.updateStatus('Optimizando modelos...', 'Configurando parámetros de rendimiento', 'loading');
1523
1649
  await new Promise(resolve => setTimeout(resolve, 300));
1524
- this.updateStatus('Reconocimiento preparado', '', 'ready');
1525
- if (this.stateManager) {
1526
- this.stateManager.updateCaptureState({ isLoading: false });
1527
- }
1650
+ this.updateStatus('Modelos precargados', '', 'ready');
1651
+ this.stateManager.updateCaptureState({ isLoading: false });
1528
1652
  this.emitReadyEvent();
1653
+ this.logger.state('MODELOS_PRECARGADOS_EXITOSAMENTE', { loadTime: Math.round(loadTime) });
1529
1654
  return { success: true, message: 'Models preloaded successfully' };
1530
1655
  }
1531
1656
  catch (error) {
1532
- this.updateStatus('Error de configuración', 'No se pudieron preparar las herramientas necesarias', 'error');
1533
- if (this.stateManager) {
1534
- this.stateManager.updateCaptureState({ isLoading: false });
1535
- }
1657
+ this.logger.error('Error al precargar modelos:', error);
1658
+ this.updateStatus('Error al cargar modelos', 'No se pudieron descargar los recursos necesarios', 'error');
1659
+ this.stateManager.updateCaptureState({ isLoading: false });
1536
1660
  return { success: false, error: error.message };
1537
1661
  }
1538
1662
  }
1539
1663
  async getCameraInfo() {
1540
- const readyCheck = this.isComponentReady();
1541
- if (!readyCheck.ready) {
1542
- return {
1543
- availableCameras: [],
1544
- selectedCameraId: null,
1545
- deviceType: 'desktop',
1546
- isMultipleCamerasAvailable: false,
1547
- preferredFacing: null,
1548
- error: readyCheck.message
1549
- };
1550
- }
1551
- try {
1552
- return await this.cameraService.getCameraInfo();
1553
- }
1554
- catch (error) {
1555
- return {
1556
- availableCameras: [],
1557
- selectedCameraId: null,
1558
- deviceType: 'desktop',
1559
- isMultipleCamerasAvailable: false,
1560
- preferredFacing: null,
1561
- error: error.message
1562
- };
1563
- }
1664
+ return this.cameraService.getCameraInfo();
1564
1665
  }
1565
1666
  async setPreferredCamera(camera) {
1566
- const readyCheck = this.isComponentReady();
1567
- if (!readyCheck.ready) {
1568
- return {
1569
- success: false,
1570
- error: readyCheck.message,
1571
- selectedCamera: null,
1572
- availableCameras: 0
1573
- };
1574
- }
1575
- try {
1576
- this.preferredCamera = camera;
1577
- this.serviceContainer.updateConfig({ preferredCamera: camera });
1578
- await this.cameraService.enumerateDevices();
1579
- await this.updateCameraInfoWithAutofocus();
1580
- const captureState = this.stateManager.getCaptureState();
1581
- if (captureState.isVideoActive) {
1582
- const selectedCameraId = this.cameraService.getSelectedCameraId();
1583
- if (selectedCameraId) {
1584
- await this.cameraService.switchCamera(selectedCameraId);
1585
- }
1667
+ this.preferredCamera = camera;
1668
+ this.serviceContainer.updateConfig({ preferredCamera: camera });
1669
+ await this.cameraService.enumerateDevices();
1670
+ const captureState = this.stateManager.getCaptureState();
1671
+ if (captureState.isVideoActive) {
1672
+ const selectedCameraId = this.cameraService.getSelectedCameraId();
1673
+ if (selectedCameraId) {
1674
+ await this.cameraService.switchCamera(selectedCameraId);
1586
1675
  }
1587
- return {
1588
- success: true,
1589
- selectedCamera: this.cameraService.getSelectedCameraId(),
1590
- availableCameras: this.cameraService.getAvailableCameras().length
1591
- };
1592
- }
1593
- catch (error) {
1594
- return {
1595
- success: false,
1596
- error: error.message,
1597
- selectedCamera: null,
1598
- availableCameras: 0
1599
- };
1600
1676
  }
1677
+ return {
1678
+ success: true,
1679
+ selectedCamera: this.cameraService.getSelectedCameraId(),
1680
+ availableCameras: this.cameraService.getAvailableCameras().length
1681
+ };
1601
1682
  }
1602
1683
  async setCaptureDelay(delay) {
1603
1684
  if (delay < 0 || delay > 10000) {
1604
- return {
1605
- success: false,
1606
- error: 'Capture delay must be between 0 and 10000 milliseconds',
1607
- captureDelay: this.captureDelay
1608
- };
1685
+ throw new Error('Capture delay must be between 0 and 10000 milliseconds');
1609
1686
  }
1610
- // Allow setting capture delay even during initialization
1611
1687
  this.captureDelay = delay;
1612
- if (this.serviceContainer) {
1613
- this.serviceContainer.updateConfig({ captureDelay: delay });
1614
- }
1688
+ this.serviceContainer.updateConfig({ captureDelay: delay });
1615
1689
  return {
1616
1690
  success: true,
1617
1691
  captureDelay: this.captureDelay
1618
1692
  };
1619
1693
  }
1620
1694
  async getCaptureDelay() {
1621
- // Always allow getting capture delay, even during initialization
1622
1695
  return this.captureDelay;
1623
1696
  }
1697
+ async setTorchEnabled(enabled) {
1698
+ const torchEnabled = await this.cameraService.setTorchEnabled(enabled, this.videoStream);
1699
+ return {
1700
+ success: torchEnabled,
1701
+ enabled: torchEnabled ? enabled : false
1702
+ };
1703
+ }
1704
+ async focusAtPoint(x, y) {
1705
+ const focused = await this.cameraService.focusAtPoint(x, y, this.videoStream);
1706
+ return {
1707
+ success: focused,
1708
+ coordinates: { x, y }
1709
+ };
1710
+ }
1624
1711
  // DETECTION METHODS
1625
1712
  async startDetection() {
1713
+ this.logger.state('INICIANDO_DETECCION');
1626
1714
  try {
1627
1715
  // Paso 1: Verificar y cargar modelos si es necesario
1628
1716
  if (!this.detectionService.isModelLoaded()) {
1629
1717
  const loadStartTime = performance.now();
1630
- this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
1718
+ this.updateStatus('Cargando modelo de detección...', 'Descargando red neuronal para reconocimiento', 'loading');
1631
1719
  this.stateManager.updateCaptureState({ isLoading: true });
1632
1720
  await this.detectionService.loadModel();
1633
- this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
1721
+ this.updateStatus('Cargando clasificador...', 'Preparando modelo de clasificación de documentos', 'loading');
1634
1722
  await this.detectionService.loadClassificationModel();
1635
1723
  const loadEndTime = performance.now();
1636
1724
  const loadTime = loadEndTime - loadStartTime;
@@ -1638,13 +1726,13 @@ const JaakStamps = class {
1638
1726
  if (this.debug) {
1639
1727
  this.recordOnnxPerformance(loadTime, 0);
1640
1728
  }
1729
+ this.logger.state('MODELOS_CARGADOS_EN_DETECCION', { loadTime: Math.round(loadTime) });
1641
1730
  }
1642
1731
  // Paso 2: Detectar y configurar dispositivos
1643
- this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
1732
+ this.updateStatus('Detectando cámaras...', 'Buscando dispositivos de captura disponibles', 'loading');
1644
1733
  await this.cameraService.enumerateDevices();
1645
- await this.updateCameraInfoWithAutofocus();
1646
1734
  // Paso 3: Configurar cámara seleccionada
1647
- this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
1735
+ this.updateStatus('Configurando cámara...', 'Estableciendo resolución y parámetros óptimos', 'loading');
1648
1736
  const stream = await this.cameraService.setupCamera();
1649
1737
  // Paso 4: Inicializar video y ocultar estado inmediatamente
1650
1738
  this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
@@ -1661,7 +1749,8 @@ const JaakStamps = class {
1661
1749
  this.detectFrame();
1662
1750
  }
1663
1751
  catch (err) {
1664
- this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
1752
+ this.logger.error('Error al inicializar detección:', err);
1753
+ this.updateStatus('Error al iniciar captura', 'No se pudo completar la inicialización', 'error');
1665
1754
  this.stateManager.updateCaptureState({ isLoading: false });
1666
1755
  }
1667
1756
  }
@@ -1671,6 +1760,10 @@ const JaakStamps = class {
1671
1760
  this.videoStream = stream;
1672
1761
  const isRear = this.cameraService.isRearCamera(stream);
1673
1762
  this.shouldMirrorVideo = !isRear;
1763
+ this.logger.state('CAMARA_CONFIGURADA', {
1764
+ isRearCamera: isRear,
1765
+ shouldMirrorVideo: this.shouldMirrorVideo
1766
+ });
1674
1767
  return new Promise((resolve) => {
1675
1768
  this.videoRef.onloadedmetadata = async () => {
1676
1769
  await this.videoRef.play();
@@ -1695,10 +1788,9 @@ const JaakStamps = class {
1695
1788
  }
1696
1789
  return;
1697
1790
  }
1698
- // Adaptive frame skipping for performance
1791
+ // Frame skipping for performance
1699
1792
  this.frameSkipCounter++;
1700
- const currentFrameSkip = this.getAdaptiveFrameSkip();
1701
- if (this.frameSkipCounter <= currentFrameSkip) {
1793
+ if (this.frameSkipCounter <= this.FRAME_SKIP) {
1702
1794
  if (captureState.step !== 'completed') {
1703
1795
  this.animationId = requestAnimationFrame(() => this.detectFrame());
1704
1796
  }
@@ -1774,6 +1866,7 @@ const JaakStamps = class {
1774
1866
  }
1775
1867
  }
1776
1868
  catch (e) {
1869
+ this.logger.error('Error en inferencia de modelo:', e);
1777
1870
  const captureState = this.stateManager.getCaptureState();
1778
1871
  if (captureState.step !== 'completed') {
1779
1872
  setTimeout(() => this.detectFrame(), 100);
@@ -1800,8 +1893,6 @@ const JaakStamps = class {
1800
1893
  clearInterval(this.performanceUpdateInterval);
1801
1894
  this.performanceUpdateInterval = undefined;
1802
1895
  }
1803
- // Clear canvas pool
1804
- this.canvasPool = [];
1805
1896
  this.detectionBoxes = [];
1806
1897
  this.alignmentStartTime = undefined;
1807
1898
  this.hasDocumentDetected = false;
@@ -1815,8 +1906,11 @@ const JaakStamps = class {
1815
1906
  step: 'front',
1816
1907
  isCapturing: false
1817
1908
  };
1818
- const cameraInfo = this.cameraInfoWithAutofocus;
1819
- 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: {
1909
+ const cameraInfo = this.cameraService?.getCameraInfo() || {
1910
+ availableCameras: [],
1911
+ selectedCameraId: null,
1912
+ deviceType: 'desktop'};
1913
+ 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: {
1820
1914
  position: 'absolute',
1821
1915
  left: `${box.x}px`,
1822
1916
  top: `${box.y}px`,
@@ -1825,9 +1919,9 @@ const JaakStamps = class {
1825
1919
  border: '2px solid #32406C',
1826
1920
  pointerEvents: 'none',
1827
1921
  boxSizing: 'border-box'
1828
- } })))), 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
1922
+ } })))), 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
1829
1923
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
1830
- : '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" })))));
1924
+ : '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" })))));
1831
1925
  }
1832
1926
  // Utility methods
1833
1927
  updateDetectionBoxes(boxes) {
@@ -1908,18 +2002,35 @@ const JaakStamps = class {
1908
2002
  if (allSidesAligned && bestBox) {
1909
2003
  cardOutline?.classList.add('perfect-match');
1910
2004
  corners?.forEach(corner => corner.classList.add('perfect-match'));
2005
+ // Debug logging
2006
+ this.logger.state('CAPTURE_EVALUATION', {
2007
+ allSidesAligned: true,
2008
+ hasScreenshotTaken: this.hasScreenshotTaken,
2009
+ captureDelay: this.captureDelay,
2010
+ alignmentStartTime: this.alignmentStartTime
2011
+ });
1911
2012
  if (!this.hasScreenshotTaken) {
1912
2013
  const currentTime = Date.now();
1913
2014
  // Initialize alignment start time if not set
1914
2015
  if (!this.alignmentStartTime) {
1915
2016
  this.alignmentStartTime = currentTime;
2017
+ this.logger.state('ALIGNMENT_TIMER_STARTED', { startTime: currentTime });
1916
2018
  }
1917
2019
  // Check if document has been aligned for the configured delay
1918
2020
  const alignmentDuration = currentTime - this.alignmentStartTime;
2021
+ this.logger.state('ALIGNMENT_DURATION_CHECK', {
2022
+ alignmentDuration,
2023
+ captureDelay: this.captureDelay,
2024
+ readyToCapture: alignmentDuration >= this.captureDelay
2025
+ });
1919
2026
  if (alignmentDuration >= this.captureDelay) {
2027
+ this.logger.state('TRIGGERING_CAPTURE', {
2028
+ alignmentDuration,
2029
+ captureDelay: this.captureDelay
2030
+ });
1920
2031
  this.lastDetectedBox = bestBox;
1921
- this.takeScreenshot().catch(() => {
1922
- // Handle screenshot error silently
2032
+ this.takeScreenshot().catch(error => {
2033
+ this.logger.error('Error al tomar captura de pantalla:', error);
1923
2034
  });
1924
2035
  this.hasScreenshotTaken = true;
1925
2036
  this.alignmentStartTime = undefined;
@@ -1945,12 +2056,21 @@ const JaakStamps = class {
1945
2056
  async takeScreenshot() {
1946
2057
  if (!this.videoRef || !this.lastDetectedBox)
1947
2058
  return;
2059
+ this.logger.state('INICIANDO_CAPTURA', {
2060
+ captureStep: this.stateManager.getCaptureState().step,
2061
+ detectedBox: this.lastDetectedBox,
2062
+ videoResolution: {
2063
+ width: this.videoRef.videoWidth,
2064
+ height: this.videoRef.videoHeight
2065
+ }
2066
+ });
1948
2067
  this.stateManager.updateCaptureState({ isCapturing: true });
1949
2068
  this.triggerCaptureAnimation();
1950
- // Use pooled canvas for optimization
1951
- const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
2069
+ // Create capture canvas
2070
+ const captureCanvas = document.createElement('canvas');
2071
+ captureCanvas.width = this.videoRef.videoWidth;
2072
+ captureCanvas.height = this.videoRef.videoHeight;
1952
2073
  const captureCtx = captureCanvas.getContext('2d', { alpha: false });
1953
- captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
1954
2074
  captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
1955
2075
  // Calculate crop coordinates
1956
2076
  const INPUT_SIZE = 320;
@@ -1960,10 +2080,11 @@ const JaakStamps = class {
1960
2080
  const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
1961
2081
  const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
1962
2082
  const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
1963
- // Use pooled canvas for cropped version
1964
- const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
2083
+ // Create cropped version
2084
+ const croppedCanvas = document.createElement('canvas');
2085
+ croppedCanvas.width = cropWidth;
2086
+ croppedCanvas.height = cropHeight;
1965
2087
  const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
1966
- croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
1967
2088
  croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
1968
2089
  const captureState = this.stateManager.getCaptureState();
1969
2090
  if (captureState.step === 'front') {
@@ -1977,6 +2098,7 @@ const JaakStamps = class {
1977
2098
  if (this.useDocumentClassification) {
1978
2099
  const classification = await this.detectionService.classifyDocument(croppedCanvas);
1979
2100
  if (classification && classification.class === 'passport') {
2101
+ this.logger.state('PASAPORTE_DETECTADO_SALTANDO_REVERSO', { classification: classification?.class });
1980
2102
  this.completeProcess(true);
1981
2103
  return;
1982
2104
  }
@@ -2003,9 +2125,6 @@ const JaakStamps = class {
2003
2125
  });
2004
2126
  this.completeProcess(false);
2005
2127
  }
2006
- // Return canvases to pool after use
2007
- this.returnCanvasToPool(captureCanvas);
2008
- this.returnCanvasToPool(croppedCanvas);
2009
2128
  }
2010
2129
  triggerCaptureAnimation() {
2011
2130
  const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
@@ -2034,6 +2153,11 @@ const JaakStamps = class {
2034
2153
  setTimeout(() => {
2035
2154
  this.stateManager.updateCaptureState({ showSuccessAnimation: false });
2036
2155
  }, 3000);
2156
+ this.logger.state('PROCESO_COMPLETADO', {
2157
+ skippedBack,
2158
+ totalImages: capturedImages.metadata.totalImages,
2159
+ timestamp: new Date().toISOString()
2160
+ });
2037
2161
  }
2038
2162
  stopDetection() {
2039
2163
  if (this.animationId) {
@@ -2042,6 +2166,7 @@ const JaakStamps = class {
2042
2166
  }
2043
2167
  this.clearBackDocumentTimer();
2044
2168
  this.detectionBoxes = [];
2169
+ this.logger.state('DETECTOR_DETENIDO', { timestamp: Date.now() });
2045
2170
  }
2046
2171
  startBackDocumentTimer() {
2047
2172
  if (!this.enableBackDocumentTimer)
@@ -2070,15 +2195,6 @@ const JaakStamps = class {
2070
2195
  return; // Don't toggle if switching camera
2071
2196
  this.showCameraSelector = !this.showCameraSelector;
2072
2197
  }
2073
- async updateCameraInfoWithAutofocus() {
2074
- try {
2075
- const cameraInfo = await this.cameraService.getCameraInfo();
2076
- this.cameraInfoWithAutofocus = cameraInfo;
2077
- }
2078
- catch (error) {
2079
- // Keep existing state if update fails
2080
- }
2081
- }
2082
2198
  async handleCameraSwitch(cameraId) {
2083
2199
  if (this.isSwitchingCamera)
2084
2200
  return; // Prevent multiple simultaneous switches
@@ -2086,6 +2202,10 @@ const JaakStamps = class {
2086
2202
  // Close the selector immediately when user selects a camera
2087
2203
  this.showCameraSelector = false;
2088
2204
  this.isSwitchingCamera = true;
2205
+ this.logger.state('INICIANDO_CAMBIO_CAMARA', {
2206
+ from: this.cameraService.getSelectedCameraId(),
2207
+ to: cameraId
2208
+ });
2089
2209
  // Stop current video stream
2090
2210
  if (this.videoStream) {
2091
2211
  this.videoStream.getTracks().forEach(track => track.stop());
@@ -2096,17 +2216,21 @@ const JaakStamps = class {
2096
2216
  const newStream = await this.cameraService.setupCamera();
2097
2217
  // Update video element
2098
2218
  await this.initializeVideoStream(newStream);
2099
- // Update camera info with autofocus data
2100
- await this.updateCameraInfoWithAutofocus();
2219
+ this.logger.state('CAMBIO_CAMARA_EXITOSO', {
2220
+ newCameraId: cameraId,
2221
+ isRearCamera: this.cameraService.isRearCamera(newStream)
2222
+ });
2101
2223
  }
2102
2224
  catch (error) {
2225
+ this.logger.error('Error al cambiar cámara:', error);
2103
2226
  // Try to restore previous camera if switch failed
2104
2227
  try {
2105
2228
  const fallbackStream = await this.cameraService.setupCamera();
2106
2229
  await this.initializeVideoStream(fallbackStream);
2107
2230
  }
2108
2231
  catch (fallbackError) {
2109
- this.updateStatus('Error al cambiar cámara', 'No se pudo cambiar el dispositivo', 'error');
2232
+ this.logger.error('Error al restaurar cámara anterior:', fallbackError);
2233
+ this.updateStatus('Error al cambiar cámara', 'No se pudo completar el cambio de dispositivo', 'error');
2110
2234
  }
2111
2235
  }
2112
2236
  finally {
@@ -2147,7 +2271,7 @@ const JaakStamps = class {
2147
2271
  this.stateManager.updateCaptureState({ isVideoActive: false, isLoading: false });
2148
2272
  }
2149
2273
  this.isMaskReady = false;
2150
- this.updateStatus('Captura finalizada', '', 'ready');
2274
+ this.updateStatus('Sesión finalizada', '', 'ready');
2151
2275
  this.detectionBoxes = [];
2152
2276
  this.cleanup();
2153
2277
  }
@@ -2158,6 +2282,7 @@ const JaakStamps = class {
2158
2282
  this.performanceUpdateInterval = window.setInterval(() => {
2159
2283
  this.updatePerformanceMetrics();
2160
2284
  }, 500);
2285
+ this.logger.debug('Monitor de performance inicializado');
2161
2286
  }
2162
2287
  updatePerformanceMetrics() {
2163
2288
  const currentTime = performance.now();
@@ -2200,66 +2325,6 @@ const JaakStamps = class {
2200
2325
  if (detectionsFound > 0) {
2201
2326
  this.performanceMetrics.successfulDetections++;
2202
2327
  }
2203
- // Update performance history for adaptive frame skipping
2204
- this.performanceHistory.push(processingTime);
2205
- if (this.performanceHistory.length > this.PERFORMANCE_HISTORY_SIZE) {
2206
- this.performanceHistory.shift();
2207
- }
2208
- }
2209
- // Adaptive frame skipping based on performance
2210
- getAdaptiveFrameSkip() {
2211
- // Base conditions for high frame skip
2212
- if (this.consecutiveFailures > 20)
2213
- return this.MAX_FRAME_SKIP;
2214
- if (this.performanceMetrics.inferenceTime > 200)
2215
- return 6;
2216
- if (this.performanceMetrics.memoryUsage > 200)
2217
- return 5;
2218
- // Calculate average processing time
2219
- if (this.performanceHistory.length > 0) {
2220
- const avgProcessingTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
2221
- if (avgProcessingTime > 100)
2222
- return 4;
2223
- if (avgProcessingTime > 80)
2224
- return 3;
2225
- if (avgProcessingTime > 60)
2226
- return this.BASE_FRAME_SKIP + 1;
2227
- }
2228
- // Low memory devices get higher frame skip
2229
- if (this.performanceMetrics.memoryUsage > 150)
2230
- return 4;
2231
- // Performance is good, use base frame skip
2232
- return this.BASE_FRAME_SKIP;
2233
- }
2234
- // Canvas pool management for screenshots
2235
- getPooledCanvas(width, height) {
2236
- // Try to find a canvas with matching dimensions
2237
- const matchingIndex = this.canvasPool.findIndex(canvas => canvas.width === width && canvas.height === height);
2238
- if (matchingIndex !== -1) {
2239
- return this.canvasPool.splice(matchingIndex, 1)[0];
2240
- }
2241
- // If no matching canvas, try to reuse any canvas and resize
2242
- if (this.canvasPool.length > 0) {
2243
- const canvas = this.canvasPool.pop();
2244
- canvas.width = width;
2245
- canvas.height = height;
2246
- return canvas;
2247
- }
2248
- // Create new canvas if pool is empty
2249
- const canvas = document.createElement('canvas');
2250
- canvas.width = width;
2251
- canvas.height = height;
2252
- return canvas;
2253
- }
2254
- returnCanvasToPool(canvas) {
2255
- // Only return to pool if not at max capacity
2256
- if (this.canvasPool.length < this.MAX_CANVAS_POOL_SIZE) {
2257
- // Clear the canvas before returning to pool
2258
- const ctx = canvas.getContext('2d');
2259
- ctx.clearRect(0, 0, canvas.width, canvas.height);
2260
- this.canvasPool.push(canvas);
2261
- }
2262
- // If pool is full, let the canvas be garbage collected
2263
2328
  }
2264
2329
  };
2265
2330
  JaakStamps.style = myComponentCss;