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