@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.
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +611 -546
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.css +2 -14
- package/dist/collection/components/my-component/my-component.js +237 -346
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/CameraService.js +344 -156
- package/dist/collection/services/CameraService.js.map +1 -1
- package/dist/collection/services/DetectionService.js +38 -53
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/EventBusService.js +1 -1
- package/dist/collection/services/EventBusService.js.map +1 -1
- package/dist/collection/services/LoggerService.js +40 -0
- package/dist/collection/services/LoggerService.js.map +1 -0
- package/dist/collection/services/ServiceContainer.js +12 -2
- package/dist/collection/services/ServiceContainer.js.map +1 -1
- package/dist/collection/services/interfaces/ICameraService.js.map +1 -1
- package/dist/collection/services/interfaces/ILogger.js +2 -0
- package/dist/collection/services/interfaces/ILogger.js.map +1 -0
- package/dist/collection/types/component-types.js.map +1 -1
- package/dist/components/jaak-stamps.js +614 -548
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +611 -546
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-2264b5b4.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +17 -62
- package/dist/types/components.d.ts +8 -6
- package/dist/types/services/CameraService.d.ts +14 -12
- package/dist/types/services/DetectionService.d.ts +3 -9
- package/dist/types/services/LoggerService.d.ts +12 -0
- package/dist/types/services/ServiceContainer.d.ts +2 -0
- package/dist/types/services/interfaces/ICameraService.d.ts +12 -3
- package/dist/types/services/interfaces/ILogger.d.ts +8 -0
- package/dist/types/types/component-types.d.ts +0 -3
- package/package.json +4 -4
- package/dist/jaak-stamps-webcomponent/p-39560f23.entry.js +0 -2
- 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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
414
|
+
this.selectFrontCamera();
|
|
331
415
|
}
|
|
332
416
|
else if (this.preferredCamera === 'back') {
|
|
333
|
-
|
|
417
|
+
this.selectBackCamera();
|
|
334
418
|
}
|
|
335
419
|
else {
|
|
336
|
-
|
|
420
|
+
this.selectAutoCamera();
|
|
337
421
|
}
|
|
338
422
|
}
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
447
|
+
selectBackCamera() {
|
|
391
448
|
this.preferredCameraFacing = 'environment';
|
|
392
|
-
|
|
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
|
-
|
|
401
|
-
if (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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 =
|
|
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
|
-
|
|
479
|
+
selectAutoCamera() {
|
|
421
480
|
if (this.deviceType === 'mobile' || this.deviceType === 'tablet') {
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
443
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
648
|
-
|
|
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
|
-
|
|
963
|
-
|
|
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: '
|
|
1116
|
-
description: 'Configurando
|
|
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
|
|
1379
|
+
// Performance optimization
|
|
1164
1380
|
frameSkipCounter = 0;
|
|
1165
|
-
|
|
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('
|
|
1387
|
+
this.updateStatus('Iniciando servicios...', 'Configurando módulos internos', 'initializing');
|
|
1178
1388
|
await this.initializeServices();
|
|
1179
|
-
this.updateStatus('
|
|
1389
|
+
this.updateStatus('Configurando eventos...', 'Preparando comunicación entre servicios', 'initializing');
|
|
1180
1390
|
await this.setupEventListeners();
|
|
1181
|
-
this.updateStatus('
|
|
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
|
-
|
|
1419
|
+
this.eventBus.on('camera-changed', (cameraId) => {
|
|
1420
|
+
this.logger.state('CAMARA_CAMBIADA_EVENT', { cameraId });
|
|
1209
1421
|
});
|
|
1210
|
-
this.eventBus.on('error', () => {
|
|
1211
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1442
|
-
|
|
1443
|
-
|
|
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
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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.
|
|
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('
|
|
1510
|
-
|
|
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('
|
|
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('
|
|
1648
|
+
this.updateStatus('Optimizando modelos...', 'Configurando parámetros de rendimiento', 'loading');
|
|
1523
1649
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
1524
|
-
this.updateStatus('
|
|
1525
|
-
|
|
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.
|
|
1533
|
-
|
|
1534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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.
|
|
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
|
-
//
|
|
1791
|
+
// Frame skipping for performance
|
|
1699
1792
|
this.frameSkipCounter++;
|
|
1700
|
-
|
|
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.
|
|
1819
|
-
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
//
|
|
1951
|
-
const captureCanvas =
|
|
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
|
-
//
|
|
1964
|
-
const croppedCanvas =
|
|
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
|
-
|
|
2100
|
-
|
|
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.
|
|
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('
|
|
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;
|