@jaak.ai/stamps 2.5.3 → 2.5.4-dev.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.
@@ -124,6 +124,18 @@ export class JaakStamps {
124
124
  processedFramesCount = 0; // Contador total de frames procesados
125
125
  hasAutoSwitchedToManual = false;
126
126
  _manualModeLoggedOnce = false;
127
+ VIDEO_DIAGNOSTIC_EVENTS = [
128
+ 'loadstart',
129
+ 'loadedmetadata',
130
+ 'loadeddata',
131
+ 'canplay',
132
+ 'playing',
133
+ 'pause',
134
+ 'stalled',
135
+ 'suspend',
136
+ 'abort',
137
+ 'emptied',
138
+ ];
127
139
  // Canvas pool for optimized screenshot capture
128
140
  canvasPool = [];
129
141
  MAX_CANVAS_POOL_SIZE = 3;
@@ -852,6 +864,130 @@ export class JaakStamps {
852
864
  memoryMB: deviceMemory ? deviceMemory * 1024 : null
853
865
  };
854
866
  }
867
+ debugLog(message, details) {
868
+ if (!this.debug) {
869
+ return;
870
+ }
871
+ if (typeof details === 'undefined') {
872
+ console.log(`[jaak-stamps][diag] ${message}`);
873
+ return;
874
+ }
875
+ console.log(`[jaak-stamps][diag] ${message}`, details);
876
+ }
877
+ debugWarn(message, details) {
878
+ if (!this.debug) {
879
+ return;
880
+ }
881
+ if (typeof details === 'undefined') {
882
+ console.warn(`[jaak-stamps][diag] ${message}`);
883
+ return;
884
+ }
885
+ console.warn(`[jaak-stamps][diag] ${message}`, details);
886
+ }
887
+ summarizeId(id) {
888
+ if (!id) {
889
+ return null;
890
+ }
891
+ return id.length <= 8 ? id : `${id.slice(0, 8)}...`;
892
+ }
893
+ getCameraDevicesSnapshot() {
894
+ return this.cameraService.getAvailableCameras().map(camera => ({
895
+ label: camera.label || '(sin label)',
896
+ deviceId: this.summarizeId(camera.deviceId),
897
+ groupId: this.summarizeId(camera.groupId),
898
+ kind: camera.kind,
899
+ }));
900
+ }
901
+ sanitizeTrackSettings(settings) {
902
+ if (!settings) {
903
+ return null;
904
+ }
905
+ return {
906
+ ...settings,
907
+ deviceId: this.summarizeId(settings.deviceId),
908
+ groupId: this.summarizeId(settings.groupId),
909
+ };
910
+ }
911
+ sanitizeTrackConstraints(track) {
912
+ if (!track) {
913
+ return null;
914
+ }
915
+ const constraints = track.getConstraints ? track.getConstraints() : {};
916
+ const sanitizedConstraints = { ...constraints };
917
+ if (typeof sanitizedConstraints.deviceId === 'string') {
918
+ sanitizedConstraints.deviceId = this.summarizeId(sanitizedConstraints.deviceId);
919
+ }
920
+ return sanitizedConstraints;
921
+ }
922
+ getStreamSnapshot(stream) {
923
+ if (!stream) {
924
+ return { hasStream: false };
925
+ }
926
+ const videoTrack = stream.getVideoTracks()[0];
927
+ if (!videoTrack) {
928
+ return {
929
+ hasStream: true,
930
+ streamActive: stream.active,
931
+ trackCount: stream.getTracks().length,
932
+ hasVideoTrack: false,
933
+ };
934
+ }
935
+ return {
936
+ hasStream: true,
937
+ streamActive: stream.active,
938
+ trackCount: stream.getTracks().length,
939
+ trackLabel: videoTrack.label || '(sin label)',
940
+ trackEnabled: videoTrack.enabled,
941
+ trackMuted: videoTrack.muted,
942
+ trackReadyState: videoTrack.readyState,
943
+ settings: this.sanitizeTrackSettings(videoTrack.getSettings ? videoTrack.getSettings() : undefined),
944
+ constraints: this.sanitizeTrackConstraints(videoTrack),
945
+ };
946
+ }
947
+ getVideoElementSnapshot() {
948
+ if (!this.videoRef) {
949
+ return { hasVideoRef: false };
950
+ }
951
+ const computedStyle = this.el.ownerDocument?.defaultView?.getComputedStyle(this.videoRef);
952
+ return {
953
+ hasVideoRef: true,
954
+ isConnected: this.videoRef.isConnected,
955
+ readyState: this.videoRef.readyState,
956
+ networkState: this.videoRef.networkState,
957
+ paused: this.videoRef.paused,
958
+ muted: this.videoRef.muted,
959
+ autoplay: this.videoRef.autoplay,
960
+ playsInline: this.videoRef.playsInline,
961
+ currentTime: Number.isFinite(this.videoRef.currentTime)
962
+ ? Number(this.videoRef.currentTime.toFixed(3))
963
+ : this.videoRef.currentTime,
964
+ videoWidth: this.videoRef.videoWidth,
965
+ videoHeight: this.videoRef.videoHeight,
966
+ clientWidth: this.videoRef.clientWidth,
967
+ clientHeight: this.videoRef.clientHeight,
968
+ hasSrcObject: !!this.videoRef.srcObject,
969
+ display: computedStyle?.display ?? 'unknown',
970
+ visibility: computedStyle?.visibility ?? 'unknown',
971
+ };
972
+ }
973
+ attachVideoDiagnosticListeners() {
974
+ if (!this.debug || !this.videoRef) {
975
+ return () => undefined;
976
+ }
977
+ const video = this.videoRef;
978
+ const listeners = this.VIDEO_DIAGNOSTIC_EVENTS.map(eventName => {
979
+ const handler = () => {
980
+ this.debugLog(`video event "${eventName}"`, this.getVideoElementSnapshot());
981
+ };
982
+ video.addEventListener(eventName, handler);
983
+ return { eventName, handler };
984
+ });
985
+ return () => {
986
+ listeners.forEach(({ eventName, handler }) => {
987
+ video.removeEventListener(eventName, handler);
988
+ });
989
+ };
990
+ }
855
991
  // DETECTION METHODS
856
992
  async startDetection() {
857
993
  try {
@@ -938,12 +1074,19 @@ export class JaakStamps {
938
1074
  this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
939
1075
  try {
940
1076
  await this.cameraService.enumerateDevices();
1077
+ this.debugLog('step 2/5 enumerateDevices()', {
1078
+ cameras: this.getCameraDevicesSnapshot(),
1079
+ selectedCameraId: this.summarizeId(this.cameraService.getSelectedCameraId() || undefined),
1080
+ });
941
1081
  }
942
1082
  catch (enumerateError) {
943
1083
  throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
944
1084
  }
945
1085
  try {
946
1086
  await this.updateCameraInfoWithAutofocus();
1087
+ this.debugLog('step 2/5 updateCameraInfoWithAutofocus()', {
1088
+ cameraInfo: this.cameraInfoWithAutofocus,
1089
+ });
947
1090
  }
948
1091
  catch (cameraInfoError) {
949
1092
  throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
@@ -953,6 +1096,10 @@ export class JaakStamps {
953
1096
  let stream;
954
1097
  try {
955
1098
  stream = await this.cameraService.setupCamera();
1099
+ this.debugLog('step 3/5 setupCamera() resolved', {
1100
+ stream: this.getStreamSnapshot(stream),
1101
+ videoRef: this.getVideoElementSnapshot(),
1102
+ });
956
1103
  }
957
1104
  catch (setupError) {
958
1105
  throw new Error(`Error al configurar cámara: ${setupError.message}`);
@@ -965,7 +1112,15 @@ export class JaakStamps {
965
1112
  this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
966
1113
  }
967
1114
  try {
1115
+ this.debugLog('step 4/5 initializeVideoStream() start', {
1116
+ stream: this.getStreamSnapshot(stream),
1117
+ videoRef: this.getVideoElementSnapshot(),
1118
+ });
968
1119
  await this.initializeVideoStream(stream);
1120
+ this.debugLog('step 4/5 initializeVideoStream() completed', {
1121
+ stream: this.getStreamSnapshot(stream),
1122
+ videoRef: this.getVideoElementSnapshot(),
1123
+ });
969
1124
  }
970
1125
  catch (videoError) {
971
1126
  throw new Error(`Error al inicializar video: ${videoError.message}`);
@@ -983,9 +1138,19 @@ export class JaakStamps {
983
1138
  if (!this.useDocumentDetector) {
984
1139
  this.showManualCaptureButton = true;
985
1140
  }
1141
+ this.debugLog('step 5/5 capture loop ready', {
1142
+ videoRef: this.getVideoElementSnapshot(),
1143
+ stream: this.getStreamSnapshot(this.videoStream),
1144
+ useDocumentDetector: this.useDocumentDetector,
1145
+ });
986
1146
  this.detectFrame();
987
1147
  }
988
1148
  catch (err) {
1149
+ this.debugWarn('startDetection() failed', {
1150
+ error: err instanceof Error ? err.message : String(err),
1151
+ videoRef: this.getVideoElementSnapshot(),
1152
+ stream: this.getStreamSnapshot(this.videoStream),
1153
+ });
989
1154
  console.error('[jaak-stamps] Error al preparar captura:', err);
990
1155
  this.updateStatus('Error al iniciar', 'No se pudo preparar la captura', 'error');
991
1156
  this.stateManager.updateCaptureState({ isLoading: false });
@@ -995,11 +1160,93 @@ export class JaakStamps {
995
1160
  if (!this.videoRef) {
996
1161
  throw new Error('Video element not available');
997
1162
  }
1163
+ const detachVideoDiagnosticListeners = this.attachVideoDiagnosticListeners();
1164
+ this.debugLog('initializeVideoStream() pre-assign', {
1165
+ stream: this.getStreamSnapshot(stream),
1166
+ videoRef: this.getVideoElementSnapshot(),
1167
+ });
998
1168
  this.videoRef.srcObject = stream;
999
1169
  this.videoStream = stream;
1170
+ this.debugLog('initializeVideoStream() post-srcObject assignment', {
1171
+ stream: this.getStreamSnapshot(stream),
1172
+ videoRef: this.getVideoElementSnapshot(),
1173
+ });
1000
1174
  const isRear = this.cameraService.isRearCamera(stream);
1001
1175
  this.shouldMirrorVideo = !isRear;
1002
1176
  const LOAD_METADATA_TIMEOUT_MS = 10000;
1177
+ // Diagnostic instrumentation: when the timeout fires we need enough
1178
+ // context to distinguish between "OS muted the track", "permission
1179
+ // revoked mid-flight", "device never produced frames" and other modes
1180
+ // that all surface as the same generic timeout error.
1181
+ const track = stream.getVideoTracks()[0];
1182
+ const waitStart = performance.now();
1183
+ const trackEvents = [];
1184
+ const recordTrackEvent = (event) => {
1185
+ trackEvents.push({
1186
+ at: Math.round(performance.now() - waitStart),
1187
+ event,
1188
+ state: track?.readyState,
1189
+ });
1190
+ };
1191
+ const onTrackMute = () => recordTrackEvent('mute');
1192
+ const onTrackUnmute = () => recordTrackEvent('unmute');
1193
+ const onTrackEnded = () => recordTrackEvent('ended');
1194
+ if (track) {
1195
+ track.addEventListener('mute', onTrackMute);
1196
+ track.addEventListener('unmute', onTrackUnmute);
1197
+ track.addEventListener('ended', onTrackEnded);
1198
+ }
1199
+ const cleanupTrackListeners = () => {
1200
+ if (!track)
1201
+ return;
1202
+ track.removeEventListener('mute', onTrackMute);
1203
+ track.removeEventListener('unmute', onTrackUnmute);
1204
+ track.removeEventListener('ended', onTrackEnded);
1205
+ };
1206
+ const captureDiagnostics = () => {
1207
+ const settings = track?.getSettings?.();
1208
+ const computed = this.videoRef ? getComputedStyle(this.videoRef) : null;
1209
+ return {
1210
+ reason: 'VIDEO_METADATA_TIMEOUT',
1211
+ waitedMs: Math.round(performance.now() - waitStart),
1212
+ stream: {
1213
+ videoTrackCount: stream.getVideoTracks().length,
1214
+ audioTrackCount: stream.getAudioTracks().length,
1215
+ },
1216
+ track: track ? {
1217
+ readyState: track.readyState,
1218
+ muted: track.muted,
1219
+ enabled: track.enabled,
1220
+ label: track.label,
1221
+ kind: track.kind,
1222
+ settings: settings ? {
1223
+ width: settings.width,
1224
+ height: settings.height,
1225
+ frameRate: settings.frameRate,
1226
+ deviceId: settings.deviceId,
1227
+ facingMode: settings.facingMode,
1228
+ } : null,
1229
+ } : null,
1230
+ video: this.videoRef ? {
1231
+ readyState: this.videoRef.readyState,
1232
+ networkState: this.videoRef.networkState,
1233
+ error: this.videoRef.error ? { code: this.videoRef.error.code, message: this.videoRef.error.message } : null,
1234
+ paused: this.videoRef.paused,
1235
+ offsetWidth: this.videoRef.offsetWidth,
1236
+ offsetHeight: this.videoRef.offsetHeight,
1237
+ videoWidth: this.videoRef.videoWidth,
1238
+ videoHeight: this.videoRef.videoHeight,
1239
+ display: computed?.display,
1240
+ visibility: computed?.visibility,
1241
+ } : null,
1242
+ document: {
1243
+ visibilityState: document.visibilityState,
1244
+ hasFocus: document.hasFocus(),
1245
+ },
1246
+ trackEvents,
1247
+ userAgent: navigator.userAgent,
1248
+ };
1249
+ };
1003
1250
  const finalizeStreamInitialization = async () => {
1004
1251
  try {
1005
1252
  await this.videoRef.play();
@@ -1014,24 +1261,39 @@ export class JaakStamps {
1014
1261
  const rect = container.getBoundingClientRect();
1015
1262
  this.updateMaskDimensions(rect);
1016
1263
  }
1264
+ this.debugLog('initializeVideoStream() finalized after play()', {
1265
+ stream: this.getStreamSnapshot(stream),
1266
+ videoRef: this.getVideoElementSnapshot(),
1267
+ });
1017
1268
  };
1018
1269
  // If metadata is already available (readyState >= HAVE_METADATA = 1),
1019
1270
  // the 'loadedmetadata' event will not fire again. Handle both cases.
1020
1271
  if (this.videoRef.readyState >= 1) {
1021
- if (this.debug) {
1022
- console.log('[JAAK-DEBUG] Video metadata already available (readyState:', this.videoRef.readyState, '), skipping event wait');
1023
- }
1272
+ this.debugLog('video metadata already available, skipping event wait', this.getVideoElementSnapshot());
1273
+ cleanupTrackListeners();
1024
1274
  await finalizeStreamInitialization();
1275
+ detachVideoDiagnosticListeners();
1025
1276
  return;
1026
1277
  }
1027
1278
  return new Promise((resolve, reject) => {
1028
1279
  let settled = false;
1280
+ const cleanup = () => {
1281
+ this.videoRef.onloadedmetadata = null;
1282
+ this.videoRef.onerror = null;
1283
+ detachVideoDiagnosticListeners();
1284
+ };
1029
1285
  const timeoutId = setTimeout(() => {
1030
1286
  if (settled)
1031
1287
  return;
1032
1288
  settled = true;
1033
- this.videoRef.onloadedmetadata = null;
1034
- this.videoRef.onerror = null;
1289
+ const diagnostics = captureDiagnostics();
1290
+ this.debugWarn(`initializeVideoStream() timeout after ${LOAD_METADATA_TIMEOUT_MS}ms`, {
1291
+ videoRef: this.getVideoElementSnapshot(),
1292
+ stream: this.getStreamSnapshot(stream),
1293
+ });
1294
+ console.error('[jaak-stamps] Video metadata timeout diagnostics (json):', JSON.stringify(diagnostics));
1295
+ cleanup();
1296
+ cleanupTrackListeners();
1035
1297
  reject(new Error(`Video metadata load timeout after ${LOAD_METADATA_TIMEOUT_MS}ms`));
1036
1298
  }, LOAD_METADATA_TIMEOUT_MS);
1037
1299
  this.videoRef.onloadedmetadata = async () => {
@@ -1039,13 +1301,18 @@ export class JaakStamps {
1039
1301
  return;
1040
1302
  settled = true;
1041
1303
  clearTimeout(timeoutId);
1042
- this.videoRef.onloadedmetadata = null;
1043
- this.videoRef.onerror = null;
1304
+ this.debugLog('initializeVideoStream() onloadedmetadata fired', {
1305
+ videoRef: this.getVideoElementSnapshot(),
1306
+ stream: this.getStreamSnapshot(stream),
1307
+ });
1308
+ cleanupTrackListeners();
1044
1309
  try {
1045
1310
  await finalizeStreamInitialization();
1311
+ cleanup();
1046
1312
  resolve();
1047
1313
  }
1048
1314
  catch (err) {
1315
+ cleanup();
1049
1316
  reject(err);
1050
1317
  }
1051
1318
  };
@@ -1054,8 +1321,13 @@ export class JaakStamps {
1054
1321
  return;
1055
1322
  settled = true;
1056
1323
  clearTimeout(timeoutId);
1057
- this.videoRef.onloadedmetadata = null;
1058
- this.videoRef.onerror = null;
1324
+ this.debugWarn('initializeVideoStream() video element emitted error', {
1325
+ event,
1326
+ videoRef: this.getVideoElementSnapshot(),
1327
+ stream: this.getStreamSnapshot(stream),
1328
+ });
1329
+ cleanup();
1330
+ cleanupTrackListeners();
1059
1331
  reject(new Error(`Video element error: ${event}`));
1060
1332
  };
1061
1333
  });
@@ -1281,7 +1553,16 @@ export class JaakStamps {
1281
1553
  isCapturing: false
1282
1554
  };
1283
1555
  const cameraInfo = this.cameraInfoWithAutofocus;
1284
- return (h("div", { key: 'd9e8fef8a096429937860396347148e6bdd06340', class: "detector-container" }, !this.licenseValid && this.licenseError && (h("div", { key: '7eb2f885adab60a4b8b1667bf0a362510d537794', class: "license-error-container" }, h("div", { key: 'af1079677131e33d5cc7698d4da03b2ba012c4bf', class: "license-error-card" }, h("svg", { key: 'f968e7c76c710dd8a4b6b407a026346d524740fe', class: "license-error-icon", width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: '39f44307ef8a1ef1fb786dc925fb753f647aa94c', d: "M12 22C12 22 20 18 20 12V5L12 2L4 5V12C4 18 12 22 12 22Z", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { key: '64fe021fadc616f46e69b35a7950ad503f55028a', d: "M12 8V12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }), h("circle", { key: 'aa86c48652cce218d24f78f49c0f32f8485184ca', cx: "12", cy: "16", r: "0.5", fill: "currentColor", stroke: "currentColor", "stroke-width": "1" })), h("h2", { key: 'b49877aa248f90ef207cc26a294f3f7f4f5f8e03', class: "license-error-title" }, "Licencia Requerida"), h("p", { key: 'fe929456590ae3ee78804613072df9c873732f99', class: "license-error-message" }, this.licenseError), h("p", { key: '1177a85cdd98a3dfd120043ce58256a33095d720', class: "license-error-help" }, "Contacte a soporte: ", h("a", { key: 'd5fe21ac8932ab88907e59775d459597cbd3b51f', href: "mailto:support@jaak.ai" }, "support@jaak.ai")), h("div", { key: '3b764654f555dae41581f5d855548b8f3382e0f2', class: "license-error-footer" }, "JAAK Stamps")))), this.licenseValid && (h("div", { key: '4051657a840db456841fdea943409f75c4c72985', class: "video-container" }, h("video", { key: 'aa7fb4da7f721a511f4e8bbd9d2727ff2a3c350f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'a9f5839bd5f7543f5e3e37701c95c3528d233161', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
1556
+ return (h("div", { key: '958fb0a94dcab5e15b2da9b2f6e1219d85e1f03a', class: "detector-container" }, !this.licenseValid && this.licenseError && (h("div", { key: 'b455ba5f14b5415d2b49c8ace3ead469cd2a7acc', class: "license-error-container" }, h("div", { key: '4c1e7f980319cf125b5186e26158c37a7a97842c', class: "license-error-card" }, h("svg", { key: '4c411ef193d03c543fe9298bc3aa077dcaf6b7ea', class: "license-error-icon", width: "64", height: "64", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg" }, h("path", { key: '24f0eb292901ee9cef24afdc2161bc69f6a51cd0', d: "M12 22C12 22 20 18 20 12V5L12 2L4 5V12C4 18 12 22 12 22Z", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { key: 'e5e61abf14d9422633d1c3de9efb7d1a909c941c', d: "M12 8V12", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round" }), h("circle", { key: 'f5612a5748a26d33fbfa9a60ea084614f53197f9', cx: "12", cy: "16", r: "0.5", fill: "currentColor", stroke: "currentColor", "stroke-width": "1" })), h("h2", { key: '5ba51ab22b3dc341a5a59661a5f4f87d6dcc4f96', class: "license-error-title" }, "Licencia Requerida"), h("p", { key: 'd162830f9b888ff9d179abd4d532287c3edd2b44', class: "license-error-message" }, this.licenseError), h("p", { key: '8ac80117990849f03c4cac091a9642160b49c109', class: "license-error-help" }, "Contacte a soporte: ", h("a", { key: 'df4b871006f2d4e5f95be43b4a2c3699288d3773', href: "mailto:support@jaak.ai" }, "support@jaak.ai")), h("div", { key: 'aa1aacb0dc56c76cce5d6bdac373f2e64d7b3f18', class: "license-error-footer" }, "JAAK Stamps")))), this.licenseValid && (h("div", { key: '0a5a3ad11c511d0d8f1b33a4a2fff32aede47277', class: "video-container" }, h("video", { key: 'db9c667c035c27aec76625385bb7b1ec0624736e', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: {
1557
+ // Keep the element rendered (display: block) so the browser
1558
+ // loads stream metadata and fires 'loadedmetadata'. Hiding it
1559
+ // with display:none prevents metadata loading in Chrome and
1560
+ // causes initializeVideoStream to hang waiting for the event.
1561
+ // Use opacity + visibility to hide visually while keeping the
1562
+ // render context alive.
1563
+ opacity: captureState.isVideoActive ? '1' : '0',
1564
+ visibility: captureState.isVideoActive ? 'visible' : 'hidden',
1565
+ } }), h("div", { key: '424765f58b596d8459ae903245ee8e0717eaf14a', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index) => (h("div", { key: index, class: "detection-box", style: {
1285
1566
  position: 'absolute',
1286
1567
  left: `${box.x}px`,
1287
1568
  top: `${box.y}px`,
@@ -1290,9 +1571,9 @@ export class JaakStamps {
1290
1571
  border: '2px solid #32406C',
1291
1572
  pointerEvents: 'none',
1292
1573
  boxSizing: 'border-box'
1293
- } })))), this.isMaskReady && (h("div", { key: 'ad737b47a608ba2d6e43a8c41aee15ea00012966', class: "overlay-mask" }, h("div", { key: '70c5b2591dd75742eae22e444eab4d4368fc659d', class: "card-outline" }, h("div", { key: '0962f587354f4b300f6f6a1f5b9652441c3aeb5c', class: "side side-top" }), h("div", { key: '955d5346898f560e1e3fc854c4c339074613116c', class: "side side-right" }), h("div", { key: '0388ccc214b045e392c07e6e8fffe2c8d67d3582', class: "side side-bottom" }), h("div", { key: '718600bfad1eff4298628402190ffb34b7988afa', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '70603563d303912248bf60e1d39e06d78b3adbaf', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'a3639b10d52b29da6f0490356f7cdae789d62f88', class: "back-capture-section" }, h("div", { key: 'dd430526d6e096396d10367fd216b1c6568ce19b', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (h("button", { key: '04b0afd7e6adc890287749add773910445ff5255', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: '69022991921cbc04ab20dee2148bb95d5dd371f8', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: 'dd698341642e875e8f252746db747ca77b2f37b0', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: 'c33706bf76eeb1ca401ea3df042e858e23d3a929', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1574
+ } })))), this.isMaskReady && (h("div", { key: '26b11d2375ac441dbd8b5eaec4f5976df4d1f0aa', class: "overlay-mask" }, h("div", { key: '51d95dab401719ed84533a76e58ce00c12d4f72a', class: "card-outline" }, h("div", { key: 'd2b87cb68858e74340611a46ea7e3efc1691f46e', class: "side side-top" }), h("div", { key: '55ff7658fcc123535c9710133ef4d8973bca1d2e', class: "side side-right" }), h("div", { key: '0dfa4f296ff756afa2003eb4fb368d208128c47e', class: "side side-bottom" }), h("div", { key: '9aefe3fef283ee27cbe412d27a1a873cd871eb1a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'ae93e17580fe8d6efa460857011ebf36d4b162b6', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'a9835585d26c8819ffb12e5a278d19c89d17ec6a', class: "back-capture-section" }, h("div", { key: '7563c10750eb649c337379bcd1966ebbe4c0a7be', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (h("button", { key: '0c44faf4123197765b0bad30f5674ce7b1cc4f00', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (h("span", { key: 'efed4645446e3ac5189dcc909998bda49771e61d', class: "button-spinner" })), "Capturar Reverso")), h("button", { key: '7f21e533d151507cb8aefeb232e2e6924ec4a935', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (h("span", { key: '2634e6953b71e30b658dc632d853124f5ce5bb91', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
1294
1575
  ? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
1295
- : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'f9c95646d564518b75d7511ef69049e82760c687', class: "camera-controls" }, h("button", { key: 'ff21667ee09360e13e0b7fa190eb58d4e9ab2955', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '1f5d7ced9311037a22402797075862c66b541201', class: "camera-selector-dropdown" }, h("div", { key: '38ccad6dfb46613519efac337a68ad8ed68ca585', class: "camera-selector-header" }, h("span", { key: 'efd67097b98bf82455703888a0294bba18084b58' }, "Seleccionar C\u00E1mara"), h("button", { key: 'efea962578d1ff9f4dc76e40f98b6763ab32fabd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'dc7fcc35ea20505547b9980f0793aab21635815f', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '450b9819604b9490c17759eb53468832abdf239c', class: "device-info" }, h("small", { key: '9423cb4f0f6eb767b9a9bf86412dcc74c40e3c9b' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'fc02b6ac27b7d239d07ea5df46136b5941a09020', class: "manual-capture-section" }, h("button", { key: '0e8bc7d6b2c44b66f62083bb9682880109ce1b50', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: 'd4067363057c004774322ae6ac4c6aac8ac4c1a7', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '79a88a6cf21fb0d8c2883c44fe181dc34773dae6', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '2e8f2d1441ea6ae7e62d6cb22fb8afb25308ae5b', class: "flip-animation" }, h("div", { key: '449282679041e4ad1b1f48e26185b1ab1ab3be60', class: "id-card-icon" }), h("div", { key: 'eac0b9728eefcde854ddb1675811f91ee3161176', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '521094652be1010ebb740ef3278649bc0077fbfa', class: "success-animation" }, h("div", { key: 'e9b425d4c039f7267ecb7e3005506f07706f9054', class: "check-icon" }), h("div", { key: '4859b32d3219e325afa326651f21ce3663d9c0b8', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '8fff23c16b531dda94a087a3ef3fe4ef0b6a51e4', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: 'e7139e242d3be20012efe25d808990fc9631492f', class: "status-spinner" })), h("div", { key: '935075ef8c1a7321e445578cdf918471e0f6e01c', class: "status-content" }, h("div", { key: '0f4910657f11df16d6cec635fdeb3864888ed55c', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: 'c362a69bed2503ecb4456b183876a5ea0645cc71', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '04d2a4d664742ca110f562fa599b118fa5e7a282', class: "performance-monitor" }, h("div", { key: '30c37005f87500d487e23cb87dd66973ce30bcfa', class: "performance-expanded" }, h("div", { key: '647c87a140f7ea8b39348402e0355b3b15ac9616', class: "metrics-row" }, h("div", { key: '99f886b025473adcd69b42aca61f7aa8158d13cf', class: "metric-compact" }, h("span", { key: '69215c74430c502fa51402e1fcf46d31b89b34f2', class: "metric-label" }, "FPS"), h("span", { key: '27283246a143119a4a8f0e63a8ee335f76868c46', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '6843f5f79d3e54643c6515e509364b4fa3fc032f', class: "metric-compact" }, h("span", { key: '20e83d94bf0a76e89a47b3d682e5c5c27ee94792', class: "metric-label" }, "MEM"), h("span", { key: '68922415fd8ba5ebf797f52026ecd0cf2abf861e', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'be6bb0a65bfc5bd16d6948f347756732918f80d1', class: "metrics-row" }, h("div", { key: 'c07a3723b79743697230a11267a6b2c8a847a86b', class: "metric-compact" }, h("span", { key: '2f2c75418ed31ba22f80112f6c02bdc32a1bc7e3', class: "metric-label" }, "INF"), h("span", { key: '788aa1a4220f0150c5ce77127d75be64104591c1', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '23bdf32e7ee8533c3327b271bd0cec93551f7c8e', class: "metric-compact" }, h("span", { key: '1d63525671ef0e99072d472f6bbe45919694ca98', class: "metric-label" }, "FRAME"), h("span", { key: '834f075061da75860a481142defddcd3973a2641', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '2767844f849c3a8217dce6d44180b5db604284d9', class: "metrics-row" }, h("div", { key: '78e4ae2297c867c7036186bb9871800b57e6cd50', class: "metric-compact" }, h("span", { key: '9fd42d1a4c18ead68ee81bc72dddb98dcd09952c', class: "metric-label" }, "DET"), h("span", { key: '0e3c0bbf718b21c3c20ae2e68c6f1e8f5dea0624', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '41be4df95b8745706e9b98d28cefb758e9e6b65f', class: "metric-compact" }, h("span", { key: '520b7e3d4841c0865c3643c5db33f0a762e550e7', class: "metric-label" }, "RATE"), h("span", { key: 'fe56fdd018002bce4e0efc5059cbc6562edee17f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '3c7b83cf7bfd1f42198edd1029f03b7c68f7766f', class: "watermark" }, h("img", { key: '5684517b58bafeffdbe6533c4dcc3ef36bec0b6c', src: "https://static.jaak.ai/commons/powered-by-jaak.png", alt: "Powered by Jaak" }))))));
1576
+ : 'Saltar reverso')))), captureState.isVideoActive && (h("div", { key: 'ed8cf74950d02b58d4c8628663ec692d964757f0', class: "camera-controls" }, h("button", { key: 'd309850ed774ac08e63e1eb1ea0019f7c80d3db6', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (h("div", { key: '4a70996d233bffbeac7badc52537d94453db30d3', class: "camera-selector-dropdown" }, h("div", { key: '7f893af0afeff55fe63e4971d0afc24516d2b6a6', class: "camera-selector-header" }, h("span", { key: 'd20382742b5ee5d90f678988d0d2c1c964f615d6' }, "Seleccionar C\u00E1mara"), h("button", { key: '1542d189db50740dd10e2ea843f7c5dccc5e24f2', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: 'e7907a23b4c7571c455f906333ccbd8e4172d3c9', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '8615ca9b81f57bbf5d52d40fd827b79a637f5e4f', class: "device-info" }, h("small", { key: '6f07c1bfb1a2b3824ccdfae4ebca11b0fa0297fe' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'c1c18723517003f2bcb9f68c527a13c4e6f30095', class: "manual-capture-section" }, h("button", { key: '94d21c348697cd28ecb44a5144e1e105f10e377c', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (h("span", { key: '74333b317328ad70da763e5d127635f3b6dc13d1', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (h("div", { key: '6761b16e393c5d496c5fde729b6b36580cfede0c', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '7556dddec2de2c134f9b09fbd38569366c35da48', class: "flip-animation" }, h("div", { key: 'baa3cd8993ef761ebfb94ccda6d66896959f32ee', class: "id-card-icon" }), h("div", { key: '3c5027ada509290b9abbf599849dbd19e50113af', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '1826a908ea94637428d9e800668d9ee02f0f03de', class: "success-animation" }, h("div", { key: '60d8e894ceed0c5198edb67a1e3e0fa24b9aaa3b', class: "check-icon" }), h("div", { key: '106d6ccec79f64b94273baeb8984f23e71987076', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '804e4fe62de056dca22964283282e32336f8eb00', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '108d6cab9eb7826cbe396806652a6b096ce0736d', class: "status-spinner" })), h("div", { key: '4abe0717c6b66b2bf44eb2da513fb6c22af31725', class: "status-content" }, h("div", { key: '77fb79d89df1f9e25c3c4962235b0605c7b6237f', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '2b24b09eabbbbbedd1270ebbf72f32f19ee063cb', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '7c6bf594cdb26334bfe62ee09d860b19399b4fd2', class: "performance-monitor" }, h("div", { key: '06f2b94e8a08e8afe94a2947d928a4c3c1ec260f', class: "performance-expanded" }, h("div", { key: 'c9302538e10e53e81a5685226c89a293ffd1608b', class: "metrics-row" }, h("div", { key: '7499bee1d362bf9910b401c2f85961847facc9f8', class: "metric-compact" }, h("span", { key: 'f005aab118350af46520ece3615544976b5d533f', class: "metric-label" }, "FPS"), h("span", { key: '69102a7df1fdc559883f807a8d26ae9268e56b01', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '0a63031beb60eeeb19cd50637f506661cdb1da16', class: "metric-compact" }, h("span", { key: '0231014ed6f077d780252c3b822a269148675d2f', class: "metric-label" }, "MEM"), h("span", { key: '859eda6914fdab077635f983fd4ca01daaf47ce7', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '102218bc7cf2d29c3c8ef7f91febf924ecb418a3', class: "metrics-row" }, h("div", { key: '205e50b3dbc43c541b76c21ef8e19a3defe065ce', class: "metric-compact" }, h("span", { key: '42eace007731c4642040e33f3e20462d7e150494', class: "metric-label" }, "INF"), h("span", { key: 'ff2d169c660011b24fa3340479d91befc9c2d06c', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'ec3ec57e442b3aefb38acf774873b0b181731523', class: "metric-compact" }, h("span", { key: '27d6d74dcdffbdafb4382c40b60f032b20dc5ade', class: "metric-label" }, "FRAME"), h("span", { key: 'e55795508f97a27c0b03736de2e5960560970378', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '60e80821ed57f1a724409e26a232fa2792886011', class: "metrics-row" }, h("div", { key: '7deb1504fb3705affaab105782ed7107d6e1a501', class: "metric-compact" }, h("span", { key: '0658ce3204ce120557caa248505def48a8a205dd', class: "metric-label" }, "DET"), h("span", { key: '6b14fafc3903912380edda27e5024e1221917db7', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'ec602c3b4d7d8ab0ffbf165d98e8b2e7d7d0b0ec', class: "metric-compact" }, h("span", { key: '9615ae1d383d383f116d2953f05ddb252336f7be', class: "metric-label" }, "RATE"), h("span", { key: 'eca7277e9185b9ff4232e556e707a3f1ce80365f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: '3b6b9368797965d5795c9752d2b4a08a9934b441', class: "watermark" }, h("img", { key: 'b40ffaeb6f88d1a744bffeb64ac11d4f19be6220', src: "https://static.jaak.ai/commons/powered-by-jaak.png", alt: "Powered by Jaak" }))))));
1296
1577
  }
1297
1578
  // Utility methods
1298
1579
  updateDetectionBoxes(boxes) {