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

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 (36) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/{index-DGM9-FNg.js → index-BfhtOB0D.js} +5 -2
  3. package/dist/cjs/index-BfhtOB0D.js.map +1 -0
  4. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +2 -2
  5. package/dist/cjs/jaak-stamps.cjs.entry.js +431 -23
  6. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  7. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  8. package/dist/cjs/loader.cjs.js +2 -2
  9. package/dist/collection/components/my-component/my-component.css +252 -0
  10. package/dist/collection/components/my-component/my-component.js +493 -22
  11. package/dist/collection/components/my-component/my-component.js.map +1 -1
  12. package/dist/components/index.js +3 -0
  13. package/dist/components/index.js.map +1 -1
  14. package/dist/components/jaak-stamps.js +438 -23
  15. package/dist/components/jaak-stamps.js.map +1 -1
  16. package/dist/esm/{index-DqoVMnc7.js → index-BP1Q4KOg.js} +5 -2
  17. package/dist/esm/index-BP1Q4KOg.js.map +1 -0
  18. package/dist/esm/jaak-stamps-webcomponent.js +3 -3
  19. package/dist/esm/jaak-stamps.entry.js +431 -23
  20. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  21. package/dist/esm/loader.js +3 -3
  22. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  23. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  24. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +3 -0
  25. package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +1 -0
  26. package/dist/jaak-stamps-webcomponent/p-eb318f43.entry.js +2 -0
  27. package/dist/jaak-stamps-webcomponent/p-eb318f43.entry.js.map +1 -0
  28. package/dist/types/components/my-component/my-component.d.ts +36 -0
  29. package/dist/types/components.d.ts +10 -0
  30. package/package.json +1 -1
  31. package/dist/cjs/index-DGM9-FNg.js.map +0 -1
  32. package/dist/esm/index-DqoVMnc7.js.map +0 -1
  33. package/dist/jaak-stamps-webcomponent/p-DqoVMnc7.js +0 -3
  34. package/dist/jaak-stamps-webcomponent/p-DqoVMnc7.js.map +0 -1
  35. package/dist/jaak-stamps-webcomponent/p-f1172b06.entry.js +0 -2
  36. package/dist/jaak-stamps-webcomponent/p-f1172b06.entry.js.map +0 -1
@@ -6,6 +6,7 @@ export class JaakStamps {
6
6
  maskSize = 90; // Porcentaje del video que ocupará la máscara (default 90%)
7
7
  cropMargin = 0; // Margen en píxeles para el recorte del documento (default 0)
8
8
  useDocumentClassification = false; // Habilita la clasificación automática de documentos para determinar si solicitar reverso
9
+ preferredCamera = 'auto'; // Define qué cámara usar: 'auto' (automático según dispositivo), 'front' (frontal), 'back' (trasera)
9
10
  captureCompleted;
10
11
  isReady;
11
12
  isVideoActive = false;
@@ -31,6 +32,10 @@ export class JaakStamps {
31
32
  isLoading = false;
32
33
  isModelPreloaded = false;
33
34
  isMaskReady = false;
35
+ availableCameras = [];
36
+ selectedCameraId = null;
37
+ showCameraSelector = false;
38
+ isMultipleCamerasAvailable = false;
34
39
  videoRef;
35
40
  canvasRef;
36
41
  session;
@@ -41,6 +46,9 @@ export class JaakStamps {
41
46
  lastDetectedBox;
42
47
  mobileNetSession;
43
48
  mobileNetClassMap;
49
+ // Camera management properties
50
+ deviceType = 'desktop';
51
+ preferredCameraFacing = null;
44
52
  // Performance optimization properties
45
53
  frameSkipCounter = 0;
46
54
  FRAME_SKIP = 2; // Process every 3rd frame (reduces CPU by ~66%)
@@ -77,6 +85,13 @@ export class JaakStamps {
77
85
  this.cropMargin = 0;
78
86
  }
79
87
  }
88
+ validatePreferredCamera() {
89
+ const validOptions = ['auto', 'front', 'back'];
90
+ if (!validOptions.includes(this.preferredCamera)) {
91
+ console.warn(`preferredCamera debe ser uno de: ${validOptions.join(', ')}. Valor actual: ${this.preferredCamera}. Usando valor por defecto: 'auto'`);
92
+ this.preferredCamera = 'auto';
93
+ }
94
+ }
80
95
  emitReadyEvent() {
81
96
  const isDocumentReady = !!window.ort && this.isModelPreloaded;
82
97
  this.isReady.emit(isDocumentReady);
@@ -89,21 +104,318 @@ export class JaakStamps {
89
104
  const settings = videoTrack.getSettings();
90
105
  return settings.facingMode === 'environment';
91
106
  }
107
+ async detectDeviceTypeAndCameras() {
108
+ // Detect device type
109
+ const userAgent = navigator.userAgent;
110
+ const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
111
+ const isTablet = /iPad|Android/i.test(userAgent) && window.innerWidth >= 768;
112
+ if (isTablet) {
113
+ this.deviceType = 'tablet';
114
+ }
115
+ else if (isMobile) {
116
+ this.deviceType = 'mobile';
117
+ }
118
+ else {
119
+ this.deviceType = 'desktop';
120
+ }
121
+ this.debugLog('📱 Device type detected:', this.deviceType);
122
+ // Enumerate available cameras
123
+ await this.enumerateAndDetectCameras();
124
+ // Load user preference
125
+ this.loadCameraPreference();
126
+ }
127
+ async enumerateAndDetectCameras() {
128
+ try {
129
+ // First, check if we have permission to enumerate devices
130
+ const permissionStatus = await this.checkCameraPermission();
131
+ if (permissionStatus === 'denied') {
132
+ this.debugLog('❌ Camera permission denied');
133
+ this.statusMessage = "Permiso de cámara denegado";
134
+ this.statusColor = "#ff6b6b";
135
+ return;
136
+ }
137
+ // Request minimal permission to get device labels
138
+ if (permissionStatus === 'prompt') {
139
+ const tempStream = await navigator.mediaDevices.getUserMedia({ video: true });
140
+ tempStream.getTracks().forEach(track => track.stop());
141
+ }
142
+ // Now enumerate devices with labels
143
+ const devices = await navigator.mediaDevices.enumerateDevices();
144
+ this.availableCameras = devices.filter(device => device.kind === 'videoinput');
145
+ this.isMultipleCamerasAvailable = this.availableCameras.length > 1;
146
+ this.debugLog('📹 Available cameras:', {
147
+ count: this.availableCameras.length,
148
+ isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
149
+ cameras: this.availableCameras.map(cam => ({
150
+ id: cam.deviceId,
151
+ label: cam.label || 'Unknown Camera'
152
+ }))
153
+ });
154
+ // Set initial camera preference based on device type
155
+ this.setInitialCameraPreference();
156
+ }
157
+ catch (error) {
158
+ this.debugLog('❌ Error enumerating cameras:', error);
159
+ this.handleCameraPermissionError(error);
160
+ }
161
+ }
162
+ async checkCameraPermission() {
163
+ try {
164
+ if (!navigator.permissions) {
165
+ return 'prompt'; // Assume we need to prompt on older browsers
166
+ }
167
+ const permission = await navigator.permissions.query({ name: 'camera' });
168
+ return permission.state;
169
+ }
170
+ catch (error) {
171
+ this.debugLog('⚠️ Could not check camera permission:', error);
172
+ return 'prompt';
173
+ }
174
+ }
175
+ handleCameraPermissionError(error) {
176
+ if (error.name === 'NotAllowedError') {
177
+ this.statusMessage = "Permiso de cámara denegado. Active el permiso en configuración.";
178
+ this.statusColor = "#ff6b6b";
179
+ this.availableCameras = [];
180
+ this.isMultipleCamerasAvailable = false;
181
+ }
182
+ else if (error.name === 'NotFoundError') {
183
+ this.statusMessage = "No se encontraron cámaras disponibles.";
184
+ this.statusColor = "#ff6b6b";
185
+ this.availableCameras = [];
186
+ this.isMultipleCamerasAvailable = false;
187
+ }
188
+ else if (error.name === 'NotReadableError') {
189
+ this.statusMessage = "Cámara en uso por otra aplicación.";
190
+ this.statusColor = "#ff6b6b";
191
+ this.availableCameras = [];
192
+ this.isMultipleCamerasAvailable = false;
193
+ }
194
+ else {
195
+ this.statusMessage = "Error al acceder a las cámaras.";
196
+ this.statusColor = "#ff6b6b";
197
+ this.availableCameras = [];
198
+ this.isMultipleCamerasAvailable = false;
199
+ }
200
+ }
201
+ setInitialCameraPreference() {
202
+ if (this.availableCameras.length === 0)
203
+ return;
204
+ // Apply user preference for camera selection
205
+ if (this.preferredCamera === 'front') {
206
+ // User explicitly wants front camera
207
+ this.preferredCameraFacing = 'user';
208
+ const frontCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('front') ||
209
+ camera.label.toLowerCase().includes('user') ||
210
+ camera.label.toLowerCase().includes('selfie') ||
211
+ !camera.label.toLowerCase().includes('back') && !camera.label.toLowerCase().includes('rear'));
212
+ if (frontCamera) {
213
+ this.selectedCameraId = frontCamera.deviceId;
214
+ this.debugLog('👤 User selected front camera:', frontCamera.label);
215
+ }
216
+ else {
217
+ this.selectedCameraId = this.availableCameras[0].deviceId;
218
+ this.debugLog('⚠️ Front camera not found, using first available:', this.availableCameras[0].label);
219
+ }
220
+ }
221
+ else if (this.preferredCamera === 'back') {
222
+ // User explicitly wants back camera
223
+ this.preferredCameraFacing = 'environment';
224
+ const backCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
225
+ camera.label.toLowerCase().includes('rear') ||
226
+ camera.label.toLowerCase().includes('environment'));
227
+ if (backCamera) {
228
+ this.selectedCameraId = backCamera.deviceId;
229
+ this.debugLog('📷 User selected back camera:', backCamera.label);
230
+ }
231
+ else {
232
+ this.selectedCameraId = this.availableCameras[0].deviceId;
233
+ this.debugLog('⚠️ Back camera not found, using first available:', this.availableCameras[0].label);
234
+ }
235
+ }
236
+ else {
237
+ // Auto mode - use device type to determine best camera
238
+ if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
239
+ // For mobile/tablet, prefer rear camera for document scanning
240
+ this.preferredCameraFacing = 'environment';
241
+ const rearCamera = this.availableCameras.find(camera => camera.label.toLowerCase().includes('back') ||
242
+ camera.label.toLowerCase().includes('rear') ||
243
+ camera.label.toLowerCase().includes('environment'));
244
+ if (rearCamera) {
245
+ this.selectedCameraId = rearCamera.deviceId;
246
+ this.debugLog('📱 Auto-selected rear camera for mobile:', rearCamera.label);
247
+ }
248
+ else {
249
+ this.selectedCameraId = this.availableCameras[0].deviceId;
250
+ this.debugLog('📱 Rear camera not found, using first available:', this.availableCameras[0].label);
251
+ }
252
+ }
253
+ else {
254
+ // For desktop, use first available camera (usually the only one)
255
+ this.selectedCameraId = this.availableCameras[0].deviceId;
256
+ this.debugLog('💻 Auto-selected desktop camera:', this.availableCameras[0].label);
257
+ }
258
+ }
259
+ }
260
+ loadCameraPreference() {
261
+ try {
262
+ const saved = localStorage.getItem('jaak-stamps-camera-preference');
263
+ if (saved) {
264
+ const preference = JSON.parse(saved);
265
+ // Validate that the saved camera is still available
266
+ const isStillAvailable = this.availableCameras.some(camera => camera.deviceId === preference.cameraId);
267
+ if (isStillAvailable) {
268
+ this.selectedCameraId = preference.cameraId;
269
+ this.preferredCameraFacing = preference.facing;
270
+ this.debugLog('💾 Loaded camera preference:', preference);
271
+ }
272
+ }
273
+ }
274
+ catch (error) {
275
+ this.debugLog('⚠️ Error loading camera preference:', error);
276
+ }
277
+ }
278
+ saveCameraPreference() {
279
+ try {
280
+ const preference = {
281
+ cameraId: this.selectedCameraId,
282
+ facing: this.preferredCameraFacing,
283
+ timestamp: Date.now()
284
+ };
285
+ localStorage.setItem('jaak-stamps-camera-preference', JSON.stringify(preference));
286
+ this.debugLog('💾 Saved camera preference:', preference);
287
+ }
288
+ catch (error) {
289
+ this.debugLog('⚠️ Error saving camera preference:', error);
290
+ }
291
+ }
292
+ async switchCamera(cameraId) {
293
+ if (!this.isVideoActive || this.selectedCameraId === cameraId)
294
+ return;
295
+ try {
296
+ // Check if the selected camera is still available
297
+ const selectedCamera = this.availableCameras.find(cam => cam.deviceId === cameraId);
298
+ if (!selectedCamera) {
299
+ this.debugLog('❌ Selected camera not found, re-enumerating...');
300
+ await this.enumerateAndDetectCameras();
301
+ return;
302
+ }
303
+ // Show loading state
304
+ if (this.debug) {
305
+ this.statusMessage = "Cambiando cámara...";
306
+ this.statusColor = "#007bff";
307
+ }
308
+ // Stop current stream
309
+ if (this.videoStream) {
310
+ this.videoStream.getTracks().forEach(track => track.stop());
311
+ }
312
+ // Update selected camera
313
+ this.selectedCameraId = cameraId;
314
+ // Update facing mode preference
315
+ const isRearCamera = selectedCamera.label.toLowerCase().includes('back') ||
316
+ selectedCamera.label.toLowerCase().includes('rear') ||
317
+ selectedCamera.label.toLowerCase().includes('environment');
318
+ this.preferredCameraFacing = isRearCamera ? 'environment' : 'user';
319
+ // Save preference
320
+ this.saveCameraPreference();
321
+ // Setup new camera with error handling
322
+ await this.setupCameraWithRetry();
323
+ this.debugLog('🔄 Switched to camera:', selectedCamera.label);
324
+ }
325
+ catch (error) {
326
+ this.debugLog('❌ Error switching camera:', error);
327
+ this.handleCameraPermissionError(error);
328
+ }
329
+ }
330
+ async setupCameraWithRetry(maxRetries = 3) {
331
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
332
+ try {
333
+ await this.setupCamera();
334
+ if (this.debug) {
335
+ this.statusMessage = "Cámara configurada correctamente";
336
+ this.statusColor = "#28a745";
337
+ }
338
+ return; // Success
339
+ }
340
+ catch (error) {
341
+ this.debugLog(`❌ Camera setup attempt ${attempt} failed:`, error);
342
+ if (attempt === maxRetries) {
343
+ // Last attempt failed, handle the error
344
+ this.statusMessage = "Error al configurar la cámara";
345
+ this.statusColor = "#ff6b6b";
346
+ this.handleCameraPermissionError(error);
347
+ throw error;
348
+ }
349
+ // Update status for retry attempt
350
+ if (this.debug) {
351
+ this.statusMessage = `Reintentando configuración de cámara... (${attempt}/${maxRetries})`;
352
+ this.statusColor = "#ffb366";
353
+ }
354
+ // Wait before retrying
355
+ await new Promise(resolve => setTimeout(resolve, 500 * attempt));
356
+ }
357
+ }
358
+ }
359
+ toggleCameraSelector() {
360
+ this.showCameraSelector = !this.showCameraSelector;
361
+ this.debugLog('📹 Camera selector toggled:', {
362
+ showCameraSelector: this.showCameraSelector,
363
+ isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
364
+ availableCameras: this.availableCameras.length,
365
+ isVideoActive: this.isVideoActive
366
+ });
367
+ }
368
+ async flipCamera() {
369
+ if (!this.isMultipleCamerasAvailable)
370
+ return;
371
+ const currentIndex = this.availableCameras.findIndex(camera => camera.deviceId === this.selectedCameraId);
372
+ const nextIndex = (currentIndex + 1) % this.availableCameras.length;
373
+ const nextCamera = this.availableCameras[nextIndex];
374
+ await this.switchCamera(nextCamera.deviceId);
375
+ }
92
376
  async componentDidLoad() {
377
+ if (this.debug) {
378
+ // Show detailed initialization loading state only in debug mode
379
+ this.isLoading = true;
380
+ this.statusMessage = 'Inicializando componente...';
381
+ this.statusColor = '#007bff';
382
+ // Small delay to ensure initialization message is visible
383
+ await new Promise(resolve => setTimeout(resolve, 500));
384
+ }
93
385
  this.validateMaskSize();
94
386
  this.validateCropMargin();
387
+ this.validatePreferredCamera();
388
+ if (this.debug) {
389
+ // Update status for camera detection
390
+ this.statusMessage = 'Detectando cámaras disponibles...';
391
+ }
392
+ await this.detectDeviceTypeAndCameras();
95
393
  if (!window.ort) {
394
+ if (this.debug) {
395
+ // Update status for ONNX runtime loading
396
+ this.statusMessage = 'Cargando librerías de IA...';
397
+ }
96
398
  const script = document.createElement('script');
97
399
  script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
98
400
  document.head.appendChild(script);
99
401
  // Wait for ONNX runtime to load before emitting ready event
100
402
  script.onload = () => {
101
- this.emitReadyEvent();
403
+ setTimeout(() => {
404
+ this.statusMessage = 'Presione el botón para activar la cámara';
405
+ this.statusColor = '#aaa';
406
+ this.isLoading = false;
407
+ this.emitReadyEvent();
408
+ }, 300);
102
409
  };
103
410
  }
104
411
  else {
105
412
  // ONNX runtime already loaded
106
- this.emitReadyEvent();
413
+ setTimeout(() => {
414
+ this.statusMessage = 'Presione el botón para activar la cámara';
415
+ this.statusColor = '#aaa';
416
+ this.isLoading = false;
417
+ this.emitReadyEvent();
418
+ }, 300);
107
419
  }
108
420
  // Initialize canvas pool for better performance
109
421
  this.initializeCanvasPool();
@@ -293,6 +605,35 @@ export class JaakStamps {
293
605
  this.completeProcess(true);
294
606
  }
295
607
  }
608
+ async getCameraInfo() {
609
+ return {
610
+ availableCameras: this.availableCameras.map(camera => ({
611
+ id: camera.deviceId,
612
+ label: camera.label || 'Unknown Camera',
613
+ selected: camera.deviceId === this.selectedCameraId
614
+ })),
615
+ selectedCameraId: this.selectedCameraId,
616
+ deviceType: this.deviceType,
617
+ isMultipleCamerasAvailable: this.isMultipleCamerasAvailable,
618
+ preferredFacing: this.preferredCameraFacing,
619
+ userPreferredCamera: this.preferredCamera
620
+ };
621
+ }
622
+ async setPreferredCamera(camera) {
623
+ this.preferredCamera = camera;
624
+ this.debugLog('🎯 Camera preference changed to:', camera);
625
+ // Re-detect and apply new camera preference
626
+ await this.enumerateAndDetectCameras();
627
+ // If video is active, switch to the new preferred camera
628
+ if (this.isVideoActive && this.selectedCameraId) {
629
+ await this.switchCamera(this.selectedCameraId);
630
+ }
631
+ return {
632
+ success: true,
633
+ selectedCamera: this.selectedCameraId,
634
+ availableCameras: this.availableCameras.length
635
+ };
636
+ }
296
637
  async loadMobileNetModel() {
297
638
  try {
298
639
  this.debugLog('🤖 Loading MobileNet model...');
@@ -409,18 +750,38 @@ export class JaakStamps {
409
750
  }
410
751
  async getMaxResolution() {
411
752
  try {
412
- // Primero obtén un stream básico para acceder a las capacidades
753
+ // Build constraints with selected camera if available
754
+ const videoConstraints = {};
755
+ if (this.selectedCameraId) {
756
+ videoConstraints.deviceId = { exact: this.selectedCameraId };
757
+ }
758
+ else if (this.preferredCameraFacing) {
759
+ videoConstraints.facingMode = this.preferredCameraFacing;
760
+ }
761
+ else {
762
+ videoConstraints.facingMode = "environment";
763
+ }
764
+ // Get temporary stream to access capabilities
413
765
  const tempStream = await navigator.mediaDevices.getUserMedia({
414
- video: { facingMode: "environment" }
766
+ video: videoConstraints
415
767
  });
416
768
  const videoTrack = tempStream.getVideoTracks()[0];
417
769
  const capabilities = videoTrack.getCapabilities();
418
770
  // Detener el stream temporal
419
771
  tempStream.getTracks().forEach(track => track.stop());
420
- // Construir restricciones con resolución optimizada para tablets
421
- const constraints = {
422
- facingMode: "environment"
423
- };
772
+ // Build constraints with resolution optimized for tablets
773
+ const constraints = {};
774
+ // Set camera selection constraints
775
+ if (this.selectedCameraId) {
776
+ constraints.deviceId = { exact: this.selectedCameraId };
777
+ }
778
+ else if (this.preferredCameraFacing) {
779
+ constraints.facingMode = this.preferredCameraFacing;
780
+ }
781
+ else {
782
+ constraints.facingMode = "environment";
783
+ }
784
+ ;
424
785
  if (capabilities.width && capabilities.height) {
425
786
  // Limitar resolución máxima para tablets para evitar problemas de rendimiento
426
787
  const maxWidth = Math.min(capabilities.width.max, 1920);
@@ -447,13 +808,23 @@ export class JaakStamps {
447
808
  }
448
809
  catch (err) {
449
810
  this.debugLog('⚠️ Could not get capabilities, using fallback');
450
- // Fallback optimizado para tablets
811
+ // Optimized fallback for tablets
451
812
  const isTablet = /iPad|Android/i.test(navigator.userAgent) && window.innerWidth >= 768;
452
- return {
453
- facingMode: "environment",
813
+ const fallbackConstraints = {
454
814
  width: { ideal: isTablet ? 1280 : 1920 },
455
815
  height: { ideal: isTablet ? 720 : 1080 }
456
816
  };
817
+ // Add camera selection to fallback
818
+ if (this.selectedCameraId) {
819
+ fallbackConstraints.deviceId = { exact: this.selectedCameraId };
820
+ }
821
+ else if (this.preferredCameraFacing) {
822
+ fallbackConstraints.facingMode = this.preferredCameraFacing;
823
+ }
824
+ else {
825
+ fallbackConstraints.facingMode = "environment";
826
+ }
827
+ return fallbackConstraints;
457
828
  }
458
829
  }
459
830
  async setupCamera() {
@@ -488,8 +859,7 @@ export class JaakStamps {
488
859
  }
489
860
  catch (err) {
490
861
  this.debugLog("❌ No se pudo acceder a la cámara:", err);
491
- this.statusMessage = "Error: No se pudo acceder a la cámara.";
492
- this.statusColor = "#ff6b6b";
862
+ this.handleCameraPermissionError(err);
493
863
  }
494
864
  }
495
865
  preprocess(video) {
@@ -534,28 +904,48 @@ export class JaakStamps {
534
904
  // Check if model is already preloaded
535
905
  if (!this.session) {
536
906
  this.isLoading = true;
537
- this.statusMessage = "Cargando modelos...";
538
- this.statusColor = "#007bff";
907
+ if (this.debug) {
908
+ this.statusMessage = "Cargando modelo de detección...";
909
+ this.statusColor = "#007bff";
910
+ }
911
+ else {
912
+ this.statusMessage = "Cargando modelos...";
913
+ this.statusColor = "#007bff";
914
+ }
539
915
  const modelPath = this.MODEL_PATH;
540
916
  this.debugLog('🤖 Loading detection model:', modelPath);
541
917
  const sessionOptions = this.getSessionOptions();
542
918
  this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
543
919
  // Load MobileNet model if classification is enabled and not already loaded
544
920
  if (this.useDocumentClassification && !this.mobileNetSession) {
921
+ if (this.debug) {
922
+ this.statusMessage = "Cargando modelo de clasificación...";
923
+ }
545
924
  await this.loadMobileNetModel();
546
925
  }
547
926
  this.isModelPreloaded = true;
927
+ if (this.debug) {
928
+ this.statusMessage = "Modelos cargados exitosamente";
929
+ }
548
930
  this.emitReadyEvent();
549
931
  }
550
932
  else {
551
933
  this.debugLog('🚀 Using preloaded models');
552
- this.statusMessage = "Usando modelos precargados...";
553
- this.statusColor = "#007bff";
934
+ if (this.debug) {
935
+ this.statusMessage = "Usando modelos precargados...";
936
+ this.statusColor = "#007bff";
937
+ }
938
+ }
939
+ if (this.debug) {
940
+ this.statusMessage = "Configurando cámara...";
554
941
  }
555
- this.statusMessage = "Configurando cámara...";
556
942
  await this.setupCamera();
557
943
  this.startTime = Date.now();
558
944
  this.isLoading = false;
945
+ if (this.debug) {
946
+ this.statusMessage = "Detección activa - Busque su identificación";
947
+ this.statusColor = "#28a745";
948
+ }
559
949
  this.detectFrame();
560
950
  }
561
951
  catch (err) {
@@ -1027,8 +1417,14 @@ export class JaakStamps {
1027
1417
  this.videoStream.getTracks().forEach(track => track.stop());
1028
1418
  this.videoStream = undefined;
1029
1419
  this.isVideoActive = false;
1030
- this.statusMessage = "Sesión finalizada.";
1031
- this.statusColor = "#aaa";
1420
+ if (this.debug) {
1421
+ this.statusMessage = "Cámara desactivada - Listo para nueva sesión";
1422
+ this.statusColor = "#6c757d";
1423
+ }
1424
+ else {
1425
+ this.statusMessage = "Presione el botón para activar la cámara";
1426
+ this.statusColor = "#aaa";
1427
+ }
1032
1428
  }
1033
1429
  this.isLoading = false;
1034
1430
  // Limpiar canvas al finalizar sesión
@@ -1039,7 +1435,20 @@ export class JaakStamps {
1039
1435
  this.cleanup();
1040
1436
  }
1041
1437
  render() {
1042
- return (h("div", { key: '42591479495df221b101555198a23288d7d765b0', class: "detector-container" }, h("div", { key: '2b9ab48572aa2fb5542b8a8dc1b70a0f2a6eca89', class: "video-container" }, h("video", { key: 'b9e84f2eb67fa6f7f158e19b90871a1eebee624b', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: 'a82fc4826e220e696799b07b3510456f174a6628', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: '9b3b838f99030e3dfa760fa0dd7c89ff24f26ed9', class: "overlay-mask" }, h("div", { key: 'f6a3b1ac2f12c1fb081f6b5eb853e627040396f4', class: "card-outline" }, h("div", { key: '6626259249ac5e0fd6ba665ab802af1ac2f9fc17', class: "side side-top" }), h("div", { key: '7a3195c96b2406b40b122f4267c2a251614cdede', class: "side side-right" }), h("div", { key: 'ece78305cfb083ada1d44de1d7d5226730879cc3', class: "side side-bottom" }), h("div", { key: '107468c583bbb01dd46c36c096e49be8e0b574ba', class: "side side-left" }), h("div", { key: 'c590f13b80c5e37c0ac0c68cfc2e2904b4345ebc', class: "corner corner-tl" }), h("div", { key: 'ab1e5aab179068ad1bc7b58ef3628d9618a877a8', class: "corner corner-tr" }), h("div", { key: '3a1db6cf1105bd3a2dfe7d4b998e79f6e40625ec', class: "corner corner-bl" }), h("div", { key: '5f9bc4e4557e5539335894160dccbaa2a6d6ac2d', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: '8226d6721b0ae800f935e978a93113bd3b99656a', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: '16cec7ca4a80ba0db1ace768826da17063eddb3e', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")))), this.isCapturing && (h("div", { key: 'bfdca0059eb50d37a77e8e43ef963ba7ed953edf', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: 'a1ad4b4f884d7d9f6090f148ddd6df19250073dd', class: "flip-animation" }, h("div", { key: 'e159ce385824dc049a6e9409d3747105c6f0a08e', class: "id-card-icon" }), h("div", { key: '6fcff650a3c7fdb16f8d327750e06055b9a93d0d', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: 'c4b76ecb19c65969676e5156689c2c0b1c9a584e', class: "success-animation" }, h("div", { key: '12203a8fe7ee878089b3dda1d1142cde5489eed0', class: "check-icon" }), h("div", { key: 'a2d41c8944873d33106994b8d56c3ee1ee1ae9fc', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: 'a155cd999fa40c35fa7b391cde7c1026edb250a7', class: "loading-overlay" }, h("div", { key: 'ccba76c5d4a472158c28bc564ba95bd65829b58d', class: "loading-spinner" }), h("div", { key: '23168f9bdcf4e993539288ff5703acaed13bb16a', class: "loading-text" }, this.statusMessage))), h("div", { key: '0107b82ad19726c1bab131e17892a46b04e08cfd', class: "watermark" }, h("img", { key: 'ad8831a6f77fa61805f22e3a27f5c5619ac1cc0e', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1438
+ return (h("div", { key: '86ff6966e69804bea4faef5a9201480bd7ef5b9a', class: "detector-container" }, h("div", { key: 'aaed4a078d4eff98674f2163a56a1596782612bd', class: "video-container" }, h("video", { key: '6ae6a7c626c9b2b6e6915c826517d1365205b3a0', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: 'c8666e4b4814e633155be738e23ada098fb3be93', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: '968bcd21315afcf054f881647723299e7e57c29b', class: "overlay-mask" }, h("div", { key: '77f8a331f3d9e19b35c11b6c23b06420cbab9fd5', class: "card-outline" }, h("div", { key: '9d0164e7b2dcf6392471151c236127cd57507512', class: "side side-top" }), h("div", { key: '05a003eb49d25c72644aea2511b6519c5241ffb4', class: "side side-right" }), h("div", { key: 'a561ae9a94eb78aacfc24bac717164b3e22f41e5', class: "side side-bottom" }), h("div", { key: '81d64e5b58032c14062fc8efdee417db7f8a9f12', class: "side side-left" }), h("div", { key: '03c02e2c4c73c525965e34c7c47bb74f769d577f', class: "corner corner-tl" }), h("div", { key: '84780aec021ec4e0c7d38f6a659be765c4604ed5', class: "corner corner-tr" }), h("div", { key: '1ea67789c5a38cbc790f911f8f06b55c866d3927', class: "corner corner-bl" }), h("div", { key: 'e0e1999b0c5c37451850203eed4dbc2ff5834261', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: '4909eb28d62b1674436c148ef46cd05a35ff8ec8', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: '5a971c2121cf9962719ad89fc45530b8acccb550', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), this.isVideoActive && (h("div", { key: '109e5885c49212f89d5678d9c9716ebd48614126', class: "camera-controls" }, this.isMultipleCamerasAvailable && (h("button", { key: '7c8ce2cd775c0646c80378cd0ad1f70243443ddf', class: "flip-camera-button", onClick: () => this.flipCamera(), type: "button", title: "Cambiar c\u00E1mara" }, "Girar c\u00E1mara")), h("button", { key: '5d3d066ba7e317fdc33878eeb9f2f7d28c6f65dc', class: "camera-selector-button", onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara" }, "C\u00E1maras"), this.debug && (h("div", { key: '4cbc284f2efbcb90ef91ea2e064bf0aa1e7980af', style: {
1439
+ position: 'absolute',
1440
+ top: '50px',
1441
+ right: '0',
1442
+ background: 'rgba(0,0,0,0.8)',
1443
+ color: 'white',
1444
+ padding: '8px',
1445
+ fontSize: '10px',
1446
+ borderRadius: '4px',
1447
+ whiteSpace: 'nowrap'
1448
+ } }, "C\u00E1maras: ", this.availableCameras.length, h("br", { key: 'df214515289add2cc73f989bfacb59069334a0e6' }), "M\u00FAltiples: ", this.isMultipleCamerasAvailable ? 'Sí' : 'No', h("br", { key: 'aa2b1784781b5b45d24139e2b6cc4865d9f3a00c' }), "Selector: ", this.showCameraSelector ? 'Visible' : 'Oculto', h("br", { key: 'fb95b2b58c9fb89f632d85a9271b7788d45a66c0' }), "Video: ", this.isVideoActive ? 'Activo' : 'Inactivo')))), this.showCameraSelector && this.availableCameras.length > 0 && (h("div", { key: 'ec1c90c5a6c84e3ef75724e7e600a60714545608', class: "camera-selector-dropdown" }, h("div", { key: 'ea118817b6563c78b7fbf5ee66af2e38a441a8b7', class: "camera-selector-header" }, h("span", { key: '52603259d50ffb7aa6ea5753762f0f0d018b39ff' }, "Seleccionar C\u00E1mara"), h("button", { key: '422ae0643f5d1451cf539b3a77becbf44904a484', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'd5acef7ba0b1645c52fb3ea7b3c64e70fa187a5f', class: "camera-list" }, this.availableCameras.map((camera) => (h("button", { key: camera.deviceId, class: `camera-option ${this.selectedCameraId === camera.deviceId ? 'selected' : ''}`, onClick: () => {
1449
+ this.switchCamera(camera.deviceId);
1450
+ this.toggleCameraSelector();
1451
+ }, type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${this.availableCameras.indexOf(camera) + 1}`), this.selectedCameraId === camera.deviceId && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: 'b670e7bf441559bf156874dbe3bbf83dbff2fa0d', class: "device-info" }, h("small", { key: '4d333925c1ff4197b6009414ed694b0a0eb06159' }, "Dispositivo: ", this.deviceType)))))), this.isCapturing && (h("div", { key: '21f7ea0e50575226daba8607ab4bccc5029aa179', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: 'c2350cc43d3a119391f62f2cf771ed5afcdb3558', class: "flip-animation" }, h("div", { key: 'a91de9e8662a4750555bce2a879b89f9216b8156', class: "id-card-icon" }), h("div", { key: 'def1c2534e1aae27e262deb6fd26a73ff0c8cb9b', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: 'acbbfb6d07e4f6bf343ff18af2d55979e76db3d4', class: "success-animation" }, h("div", { key: 'f5acd84325226e6b1ede1180335d7495a6b2d95e', class: "check-icon" }), h("div", { key: '393fd8300ce48e59bd3ef94f0e895abc3a938c1f', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: 'ce0d1baebab8bf0e18531a26b549f38e349a0d72', class: "loading-overlay" }, h("div", { key: '14c76fa00ddd814f37bd9c6dc57f761119886566', class: "loading-spinner" }), h("div", { key: '87ccb288f927e61ec126d83f44061699810c9e06', class: "loading-text" }, this.statusMessage))), this.debug && (h("div", { key: '943dff6e2772f2595a95116e8943866f97cada6e', class: "status-bar" }, h("div", { key: '00fe53b71ef94d51636259f106e4518df910848d', class: "status-message", style: { 'color': this.statusColor } }, this.statusMessage))), h("div", { key: '4c3bd150006473d9540fb1418f11f45fb96d1e56', class: "watermark" }, h("img", { key: 'c634701f52a4422c7be5b4007bf34ba4aeb4ebcc', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1043
1452
  }
1044
1453
  static get is() { return "jaak-stamps"; }
1045
1454
  static get encapsulation() { return "shadow"; }
@@ -1154,6 +1563,26 @@ export class JaakStamps {
1154
1563
  "setter": false,
1155
1564
  "reflect": false,
1156
1565
  "defaultValue": "false"
1566
+ },
1567
+ "preferredCamera": {
1568
+ "type": "string",
1569
+ "attribute": "preferred-camera",
1570
+ "mutable": false,
1571
+ "complexType": {
1572
+ "original": "'auto' | 'front' | 'back'",
1573
+ "resolved": "\"auto\" | \"back\" | \"front\"",
1574
+ "references": {}
1575
+ },
1576
+ "required": false,
1577
+ "optional": false,
1578
+ "docs": {
1579
+ "tags": [],
1580
+ "text": ""
1581
+ },
1582
+ "getter": false,
1583
+ "setter": false,
1584
+ "reflect": false,
1585
+ "defaultValue": "'auto'"
1157
1586
  }
1158
1587
  };
1159
1588
  }
@@ -1176,7 +1605,11 @@ export class JaakStamps {
1176
1605
  "isDetectionPaused": {},
1177
1606
  "isLoading": {},
1178
1607
  "isModelPreloaded": {},
1179
- "isMaskReady": {}
1608
+ "isMaskReady": {},
1609
+ "availableCameras": {},
1610
+ "selectedCameraId": {},
1611
+ "showCameraSelector": {},
1612
+ "isMultipleCamerasAvailable": {}
1180
1613
  };
1181
1614
  }
1182
1615
  static get events() {
@@ -1349,6 +1782,44 @@ export class JaakStamps {
1349
1782
  "text": "",
1350
1783
  "tags": []
1351
1784
  }
1785
+ },
1786
+ "getCameraInfo": {
1787
+ "complexType": {
1788
+ "signature": "() => Promise<{ availableCameras: { id: string; label: string; selected: boolean; }[]; selectedCameraId: string; deviceType: \"mobile\" | \"desktop\" | \"tablet\"; isMultipleCamerasAvailable: boolean; preferredFacing: \"environment\" | \"user\"; userPreferredCamera: \"auto\" | \"front\" | \"back\"; }>",
1789
+ "parameters": [],
1790
+ "references": {
1791
+ "Promise": {
1792
+ "location": "global",
1793
+ "id": "global::Promise"
1794
+ }
1795
+ },
1796
+ "return": "Promise<{ availableCameras: { id: string; label: string; selected: boolean; }[]; selectedCameraId: string; deviceType: \"mobile\" | \"desktop\" | \"tablet\"; isMultipleCamerasAvailable: boolean; preferredFacing: \"environment\" | \"user\"; userPreferredCamera: \"auto\" | \"front\" | \"back\"; }>"
1797
+ },
1798
+ "docs": {
1799
+ "text": "",
1800
+ "tags": []
1801
+ }
1802
+ },
1803
+ "setPreferredCamera": {
1804
+ "complexType": {
1805
+ "signature": "(camera: \"auto\" | \"front\" | \"back\") => Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>",
1806
+ "parameters": [{
1807
+ "name": "camera",
1808
+ "type": "\"auto\" | \"front\" | \"back\"",
1809
+ "docs": ""
1810
+ }],
1811
+ "references": {
1812
+ "Promise": {
1813
+ "location": "global",
1814
+ "id": "global::Promise"
1815
+ }
1816
+ },
1817
+ "return": "Promise<{ success: boolean; selectedCamera: string; availableCameras: number; }>"
1818
+ },
1819
+ "docs": {
1820
+ "text": "",
1821
+ "tags": []
1822
+ }
1352
1823
  }
1353
1824
  };
1354
1825
  }