@jaak.ai/stamps 2.0.0-dev.42 → 2.0.0-dev.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +216 -218
  2. package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
  3. package/dist/cjs/jaak-stamps.cjs.entry.js +14 -652
  4. package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
  5. package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
  6. package/dist/cjs/loader.cjs.js +1 -1
  7. package/dist/collection/components/my-component/my-component.css +40 -0
  8. package/dist/collection/components/my-component/my-component.js +35 -343
  9. package/dist/collection/components/my-component/my-component.js.map +1 -1
  10. package/dist/collection/services/DetectionService.js +1 -164
  11. package/dist/collection/services/DetectionService.js.map +1 -1
  12. package/dist/collection/services/ServiceContainer.js +1 -6
  13. package/dist/collection/services/ServiceContainer.js.map +1 -1
  14. package/dist/collection/services/interfaces/IDetectionService.js.map +1 -1
  15. package/dist/collection/types/component-types.js +2 -0
  16. package/dist/collection/types/component-types.js.map +1 -0
  17. package/dist/components/jaak-stamps.js +15 -663
  18. package/dist/components/jaak-stamps.js.map +1 -1
  19. package/dist/esm/jaak-stamps-webcomponent.js +1 -1
  20. package/dist/esm/jaak-stamps.entry.js +14 -652
  21. package/dist/esm/jaak-stamps.entry.js.map +1 -1
  22. package/dist/esm/loader.js +1 -1
  23. package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
  24. package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
  25. package/dist/jaak-stamps-webcomponent/p-3074405d.entry.js +2 -0
  26. package/dist/jaak-stamps-webcomponent/p-3074405d.entry.js.map +1 -0
  27. package/dist/types/components/my-component/my-component.d.ts +4 -45
  28. package/dist/types/components.d.ts +11 -62
  29. package/dist/types/services/DetectionService.d.ts +2 -22
  30. package/dist/types/services/ServiceContainer.d.ts +0 -6
  31. package/dist/types/services/interfaces/IDetectionService.d.ts +0 -13
  32. package/dist/types/types/component-types.d.ts +36 -0
  33. package/package.json +1 -1
  34. package/dist/collection/services/ImageQualityService.js +0 -329
  35. package/dist/collection/services/ImageQualityService.js.map +0 -1
  36. package/dist/collection/services/interfaces/IImageQualityService.js +0 -2
  37. package/dist/collection/services/interfaces/IImageQualityService.js.map +0 -1
  38. package/dist/jaak-stamps-webcomponent/p-47f37982.entry.js +0 -2
  39. package/dist/jaak-stamps-webcomponent/p-47f37982.entry.js.map +0 -1
  40. package/dist/types/services/ImageQualityService.d.ts +0 -31
  41. package/dist/types/services/interfaces/IImageQualityService.d.ts +0 -58
@@ -718,346 +718,15 @@ class DeviceStrategyFactory {
718
718
  }
719
719
  }
720
720
 
721
- class ImageQualityService {
722
- logger;
723
- // Thresholds for quality assessment
724
- BLUR_THRESHOLD = 80; // Reduced from 100 to 80 for better acceptance
725
- FOCUS_THRESHOLD = 40; // Reduced from 50 to 40 for better acceptance
726
- REFLECTION_THRESHOLD = 240; // Pixel brightness threshold for reflections
727
- MIN_BRIGHTNESS = 50; // Minimum acceptable brightness
728
- MAX_BRIGHTNESS = 200; // Maximum acceptable brightness
729
- MIN_CONTRAST = 30; // Minimum acceptable contrast
730
- constructor(logger) {
731
- this.logger = logger;
732
- }
733
- analyzeImageQuality(canvas, documentBounds) {
734
- const ctx = canvas.getContext('2d');
735
- if (!ctx) {
736
- throw new Error('Unable to get canvas context');
737
- }
738
- // Get image data for the document area or full canvas
739
- let imageData;
740
- if (documentBounds) {
741
- imageData = ctx.getImageData(documentBounds.x, documentBounds.y, documentBounds.w, documentBounds.h);
742
- }
743
- else {
744
- imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
745
- }
746
- // Perform all quality assessments
747
- const blur = this.detectBlur(imageData);
748
- const reflection = this.detectReflections(imageData);
749
- const focus = this.measureFocus(imageData);
750
- const brightness = this.measureBrightness(imageData);
751
- const contrast = this.measureContrast(imageData);
752
- // Calculate overall quality score (0-100)
753
- const qualityScore = this.calculateOverallQuality(blur, reflection, focus, brightness, contrast);
754
- const overallQuality = this.getQualityLevel(qualityScore);
755
- // Identify issues and recommendations
756
- const issues = this.identifyIssues(blur, reflection, focus, brightness, contrast);
757
- const recommendations = this.getQualityRecommendations({
758
- blur, reflection, focus, brightness, contrast,
759
- overallQuality, qualityScore, issues, recommendations: []
760
- });
761
- const result = {
762
- blur,
763
- reflection,
764
- focus,
765
- brightness,
766
- contrast,
767
- overallQuality,
768
- qualityScore,
769
- issues,
770
- recommendations
771
- };
772
- this.logger.state('CALIDAD_IMAGEN_ANALIZADA', {
773
- qualityScore,
774
- overallQuality,
775
- issues: issues.length,
776
- hasBlur: !blur.isAcceptable,
777
- hasReflection: reflection.hasReflection,
778
- isFocused: focus.isFocused
779
- });
780
- return result;
781
- }
782
- detectBlur(imageData) {
783
- const { data, width, height } = imageData;
784
- let totalVariance = 0;
785
- let pixelCount = 0;
786
- // Calculate Laplacian variance for blur detection
787
- for (let y = 1; y < height - 1; y++) {
788
- for (let x = 1; x < width - 1; x++) {
789
- const idx = (y * width + x) * 4;
790
- // Convert to grayscale
791
- const gray = 0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2];
792
- // Calculate Laplacian (edge detection)
793
- const neighbors = [
794
- this.getGrayscale(data, x - 1, y - 1, width),
795
- this.getGrayscale(data, x, y - 1, width),
796
- this.getGrayscale(data, x + 1, y - 1, width),
797
- this.getGrayscale(data, x - 1, y, width),
798
- this.getGrayscale(data, x + 1, y, width),
799
- this.getGrayscale(data, x - 1, y + 1, width),
800
- this.getGrayscale(data, x, y + 1, width),
801
- this.getGrayscale(data, x + 1, y + 1, width)
802
- ];
803
- const laplacian = 8 * gray - neighbors.reduce((sum, val) => sum + val, 0);
804
- totalVariance += laplacian * laplacian;
805
- pixelCount++;
806
- }
807
- }
808
- const blurScore = Math.sqrt(totalVariance / pixelCount);
809
- const isAcceptable = blurScore > this.BLUR_THRESHOLD;
810
- return {
811
- blurScore,
812
- isAcceptable,
813
- threshold: this.BLUR_THRESHOLD
814
- };
815
- }
816
- detectReflections(imageData) {
817
- const { data, width, height } = imageData;
818
- let reflectionPixels = 0;
819
- const totalPixels = width * height;
820
- const reflectionAreas = [];
821
- // Track bright regions that could be reflections
822
- const brightRegions = Array(height).fill(null).map(() => Array(width).fill(false));
823
- for (let y = 0; y < height; y++) {
824
- for (let x = 0; x < width; x++) {
825
- const idx = (y * width + x) * 4;
826
- const brightness = (data[idx] + data[idx + 1] + data[idx + 2]) / 3;
827
- if (brightness > this.REFLECTION_THRESHOLD) {
828
- brightRegions[y][x] = true;
829
- reflectionPixels++;
830
- }
831
- }
832
- }
833
- // Find connected bright regions (potential reflections)
834
- const visited = Array(height).fill(null).map(() => Array(width).fill(false));
835
- for (let y = 0; y < height; y++) {
836
- for (let x = 0; x < width; x++) {
837
- if (brightRegions[y][x] && !visited[y][x]) {
838
- const region = this.findConnectedRegion(brightRegions, visited, x, y, width, height);
839
- if (region.size > 100) { // Minimum size for a reflection
840
- reflectionAreas.push({
841
- x: region.minX,
842
- y: region.minY,
843
- width: region.maxX - region.minX,
844
- height: region.maxY - region.minY
845
- });
846
- }
847
- }
848
- }
849
- }
850
- const reflectionScore = (reflectionPixels / totalPixels) * 100;
851
- const hasReflection = reflectionScore > 5 || reflectionAreas.length > 0; // 5% threshold
852
- return {
853
- reflectionScore,
854
- hasReflection,
855
- threshold: 5,
856
- reflectionAreas
857
- };
858
- }
859
- measureFocus(imageData) {
860
- const { data, width, height } = imageData;
861
- let edgeStrength = 0;
862
- let edgeCount = 0;
863
- // Sobel edge detection for focus measurement
864
- for (let y = 1; y < height - 1; y++) {
865
- for (let x = 1; x < width - 1; x++) {
866
- const gx = this.sobelX(data, x, y, width);
867
- const gy = this.sobelY(data, x, y, width);
868
- const magnitude = Math.sqrt(gx * gx + gy * gy);
869
- edgeStrength += magnitude;
870
- edgeCount++;
871
- }
872
- }
873
- const focusScore = edgeCount > 0 ? edgeStrength / edgeCount : 0;
874
- const isFocused = focusScore > this.FOCUS_THRESHOLD;
875
- return {
876
- focusScore,
877
- isFocused,
878
- threshold: this.FOCUS_THRESHOLD,
879
- edgeStrength
880
- };
881
- }
882
- measureBrightness(imageData) {
883
- const { data } = imageData;
884
- let totalBrightness = 0;
885
- const pixelCount = data.length / 4;
886
- for (let i = 0; i < data.length; i += 4) {
887
- const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;
888
- totalBrightness += brightness;
889
- }
890
- const brightness = totalBrightness / pixelCount;
891
- const isAcceptable = brightness >= this.MIN_BRIGHTNESS && brightness <= this.MAX_BRIGHTNESS;
892
- return {
893
- brightness,
894
- isAcceptable,
895
- minThreshold: this.MIN_BRIGHTNESS,
896
- maxThreshold: this.MAX_BRIGHTNESS
897
- };
898
- }
899
- measureContrast(imageData) {
900
- const { data } = imageData;
901
- let min = 255;
902
- let max = 0;
903
- for (let i = 0; i < data.length; i += 4) {
904
- const brightness = (data[i] + data[i + 1] + data[i + 2]) / 3;
905
- min = Math.min(min, brightness);
906
- max = Math.max(max, brightness);
907
- }
908
- const contrast = max - min;
909
- const isAcceptable = contrast > this.MIN_CONTRAST;
910
- return {
911
- contrast,
912
- isAcceptable,
913
- threshold: this.MIN_CONTRAST
914
- };
915
- }
916
- getQualityRecommendations(result) {
917
- const recommendations = [];
918
- if (!result.blur.isAcceptable) {
919
- recommendations.push('Mantenga el dispositivo estable para reducir el desenfoque');
920
- recommendations.push('Acérquese al documento para mejorar la nitidez');
921
- }
922
- if (result.reflection.hasReflection) {
923
- recommendations.push('Cambie el ángulo del dispositivo para evitar reflejos');
924
- recommendations.push('Mejore la iluminación indirecta del área');
925
- }
926
- if (!result.focus.isFocused) {
927
- recommendations.push('Permita que la cámara enfoque automáticamente');
928
- recommendations.push('Toque la pantalla sobre el documento para enfocar');
929
- }
930
- if (!result.brightness.isAcceptable) {
931
- if (result.brightness.brightness < result.brightness.minThreshold) {
932
- recommendations.push('Aumente la iluminación del área');
933
- recommendations.push('Muévase a un lugar con mejor luz');
934
- }
935
- else {
936
- recommendations.push('Reduzca la iluminación directa sobre el documento');
937
- recommendations.push('Evite la luz solar directa');
938
- }
939
- }
940
- if (!result.contrast.isAcceptable) {
941
- recommendations.push('Mejore la iluminación para aumentar el contraste');
942
- recommendations.push('Asegúrese de que el fondo contraste con el documento');
943
- }
944
- if (recommendations.length === 0) {
945
- recommendations.push('La calidad de la imagen es óptima para la captura');
946
- }
947
- return recommendations;
948
- }
949
- calculateOverallQuality(blur, reflection, focus, brightness, contrast) {
950
- let score = 0;
951
- // Blur contribution (25%)
952
- score += blur.isAcceptable ? 25 : Math.max(0, (blur.blurScore / this.BLUR_THRESHOLD) * 25);
953
- // Reflection contribution (20%)
954
- score += reflection.hasReflection ? Math.max(0, 20 - reflection.reflectionScore * 2) : 20;
955
- // Focus contribution (25%)
956
- score += focus.isFocused ? 25 : Math.max(0, (focus.focusScore / this.FOCUS_THRESHOLD) * 25);
957
- // Brightness contribution (15%)
958
- if (brightness.isAcceptable) {
959
- score += 15;
960
- }
961
- else {
962
- const brightnessPenalty = Math.abs(brightness.brightness - (brightness.minThreshold + brightness.maxThreshold) / 2);
963
- score += Math.max(0, 15 - brightnessPenalty / 10);
964
- }
965
- // Contrast contribution (15%)
966
- score += contrast.isAcceptable ? 15 : Math.max(0, (contrast.contrast / this.MIN_CONTRAST) * 15);
967
- return Math.round(Math.max(0, Math.min(100, score)));
968
- }
969
- getQualityLevel(score) {
970
- if (score >= 85)
971
- return 'excellent';
972
- if (score >= 70)
973
- return 'good';
974
- if (score >= 50)
975
- return 'acceptable';
976
- return 'poor';
977
- }
978
- identifyIssues(blur, reflection, focus, brightness, contrast) {
979
- const issues = [];
980
- if (!blur.isAcceptable) {
981
- issues.push('Imagen desenfocada por movimiento');
982
- }
983
- if (reflection.hasReflection) {
984
- issues.push('Reflejos detectados en el documento');
985
- }
986
- if (!focus.isFocused) {
987
- issues.push('Documento fuera de foco');
988
- }
989
- if (!brightness.isAcceptable) {
990
- if (brightness.brightness < brightness.minThreshold) {
991
- issues.push('Iluminación insuficiente');
992
- }
993
- else {
994
- issues.push('Imagen sobreexpuesta');
995
- }
996
- }
997
- if (!contrast.isAcceptable) {
998
- issues.push('Contraste insuficiente');
999
- }
1000
- return issues;
1001
- }
1002
- getGrayscale(data, x, y, width) {
1003
- const idx = (y * width + x) * 4;
1004
- return 0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2];
1005
- }
1006
- findConnectedRegion(brightRegions, visited, startX, startY, width, height) {
1007
- const stack = [{ x: startX, y: startY }];
1008
- let size = 0;
1009
- let minX = startX, maxX = startX, minY = startY, maxY = startY;
1010
- while (stack.length > 0) {
1011
- const { x, y } = stack.pop();
1012
- if (x < 0 || x >= width || y < 0 || y >= height || visited[y][x] || !brightRegions[y][x]) {
1013
- continue;
1014
- }
1015
- visited[y][x] = true;
1016
- size++;
1017
- minX = Math.min(minX, x);
1018
- maxX = Math.max(maxX, x);
1019
- minY = Math.min(minY, y);
1020
- maxY = Math.max(maxY, y);
1021
- // Add adjacent pixels
1022
- stack.push({ x: x + 1, y }, { x: x - 1, y }, { x, y: y + 1 }, { x, y: y - 1 });
1023
- }
1024
- return { size, minX, maxX, minY, maxY };
1025
- }
1026
- sobelX(data, x, y, width) {
1027
- const kernel = [[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]];
1028
- let sum = 0;
1029
- for (let ky = -1; ky <= 1; ky++) {
1030
- for (let kx = -1; kx <= 1; kx++) {
1031
- const gray = this.getGrayscale(data, x + kx, y + ky, width);
1032
- sum += gray * kernel[ky + 1][kx + 1];
1033
- }
1034
- }
1035
- return sum;
1036
- }
1037
- sobelY(data, x, y, width) {
1038
- const kernel = [[-1, -2, -1], [0, 0, 0], [1, 2, 1]];
1039
- let sum = 0;
1040
- for (let ky = -1; ky <= 1; ky++) {
1041
- for (let kx = -1; kx <= 1; kx++) {
1042
- const gray = this.getGrayscale(data, x + kx, y + ky, width);
1043
- sum += gray * kernel[ky + 1][kx + 1];
1044
- }
1045
- }
1046
- return sum;
1047
- }
1048
- }
1049
-
1050
721
  class DetectionService {
1051
722
  logger;
1052
723
  debug;
1053
724
  useDocumentClassification;
1054
- qualityThresholds;
1055
725
  session;
1056
726
  mobileNetSession;
1057
727
  mobileNetClassMap;
1058
728
  modelLoaded = false;
1059
729
  deviceStrategy;
1060
- imageQualityService;
1061
730
  MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/ddmyp-v2.onnx";
1062
731
  MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.onnx";
1063
732
  MOBILENET_CLASSES_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.json";
@@ -1067,13 +736,11 @@ class DetectionService {
1067
736
  preprocessCanvas;
1068
737
  preprocessCtx;
1069
738
  captureCanvas;
1070
- constructor(logger, debug = false, useDocumentClassification = false, qualityThresholds = {}) {
739
+ constructor(logger, debug = false, useDocumentClassification = false) {
1071
740
  this.logger = logger;
1072
741
  this.debug = debug;
1073
742
  this.useDocumentClassification = useDocumentClassification;
1074
- this.qualityThresholds = qualityThresholds;
1075
743
  this.deviceStrategy = DeviceStrategyFactory.createStrategy();
1076
- this.imageQualityService = new ImageQualityService(logger);
1077
744
  this.initializeCanvasPool();
1078
745
  }
1079
746
  async loadModel() {
@@ -1417,164 +1084,6 @@ class DetectionService {
1417
1084
  }
1418
1085
  return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
1419
1086
  }
1420
- // New methods for image quality validation
1421
- validateImageQuality(video, detectedBox) {
1422
- // Create a canvas to capture the current frame
1423
- const canvas = document.createElement('canvas');
1424
- canvas.width = video.videoWidth;
1425
- canvas.height = video.videoHeight;
1426
- const ctx = canvas.getContext('2d');
1427
- ctx.drawImage(video, 0, 0);
1428
- // If we have a detected document, focus quality analysis on that area
1429
- let documentBounds;
1430
- if (detectedBox) {
1431
- const scaleX = video.videoWidth / this.INPUT_SIZE;
1432
- const scaleY = video.videoHeight / this.INPUT_SIZE;
1433
- documentBounds = {
1434
- x: Math.max(0, detectedBox.x * scaleX),
1435
- y: Math.max(0, detectedBox.y * scaleY),
1436
- w: Math.min(detectedBox.w * scaleX, video.videoWidth),
1437
- h: Math.min(detectedBox.h * scaleY, video.videoHeight)
1438
- };
1439
- }
1440
- return this.imageQualityService.analyzeImageQuality(canvas, documentBounds);
1441
- }
1442
- isImageQualityAcceptable(qualityResult) {
1443
- // Use configurable thresholds with fallback defaults
1444
- const minQualityScore = this.qualityThresholds.minQualityScore ?? 45;
1445
- const minFocusScore = this.qualityThresholds.minFocusScore ?? 16;
1446
- const minBlurScore = this.qualityThresholds.minBlurScore ?? 22;
1447
- const maxReflectionScore = this.qualityThresholds.maxReflectionScore ?? 15;
1448
- // Check overall quality score - primary metric
1449
- if (qualityResult.qualityScore < minQualityScore) {
1450
- this.logger.state('QUALITY_REJECTION_SCORE', {
1451
- qualityScore: qualityResult.qualityScore,
1452
- minQualityScore,
1453
- reason: 'score_too_low'
1454
- });
1455
- return false;
1456
- }
1457
- // Check focus score
1458
- if (qualityResult.focus.focusScore < minFocusScore) {
1459
- this.logger.state('QUALITY_REJECTION_FOCUS', {
1460
- focusScore: qualityResult.focus.focusScore,
1461
- minFocusScore,
1462
- reason: 'severe_focus_issues'
1463
- });
1464
- return false;
1465
- }
1466
- // Check blur score
1467
- if (qualityResult.blur.blurScore < minBlurScore) {
1468
- this.logger.state('QUALITY_REJECTION_BLUR', {
1469
- blurScore: qualityResult.blur.blurScore,
1470
- minBlurScore,
1471
- reason: 'severe_blur_issues'
1472
- });
1473
- return false;
1474
- }
1475
- // Check reflection score
1476
- if (qualityResult.reflection.hasReflection && qualityResult.reflection.reflectionScore > maxReflectionScore) {
1477
- this.logger.state('QUALITY_REJECTION_REFLECTION', {
1478
- hasReflection: qualityResult.reflection.hasReflection,
1479
- reflectionScore: qualityResult.reflection.reflectionScore,
1480
- maxReflectionScore,
1481
- reason: 'severe_reflection_issues'
1482
- });
1483
- return false;
1484
- }
1485
- this.logger.state('QUALITY_ACCEPTANCE', {
1486
- qualityScore: qualityResult.qualityScore,
1487
- focusScore: qualityResult.focus.focusScore,
1488
- blurScore: qualityResult.blur.blurScore,
1489
- reflectionScore: qualityResult.reflection.reflectionScore,
1490
- issues: qualityResult.issues,
1491
- reason: 'quality_acceptable'
1492
- });
1493
- return true;
1494
- }
1495
- updateQualityThresholds(thresholds) {
1496
- this.qualityThresholds = { ...this.qualityThresholds, ...thresholds };
1497
- this.logger.state('QUALITY_THRESHOLDS_UPDATED', {
1498
- newThresholds: this.qualityThresholds
1499
- });
1500
- }
1501
- getQualityThresholds() {
1502
- return { ...this.qualityThresholds };
1503
- }
1504
- getQualityFeedback(qualityResult) {
1505
- if (qualityResult.qualityScore >= 85) {
1506
- return 'Calidad excelente - listo para capturar';
1507
- }
1508
- else if (qualityResult.qualityScore >= 70) {
1509
- return 'Buena calidad - puede capturar';
1510
- }
1511
- else if (qualityResult.qualityScore >= 50) {
1512
- const primaryIssue = qualityResult.issues[0];
1513
- return primaryIssue ? `Calidad aceptable - ${primaryIssue.toLowerCase()}` : 'Calidad aceptable';
1514
- }
1515
- else {
1516
- const mainIssues = qualityResult.issues.slice(0, 2).join(', ').toLowerCase();
1517
- return `Calidad insuficiente - ${mainIssues}`;
1518
- }
1519
- }
1520
- // Enhanced detection method that includes quality validation
1521
- async runInferenceWithQuality(inputTensor, video) {
1522
- const detections = await this.runInference(inputTensor);
1523
- // Get the best detection for quality analysis
1524
- const bestDetection = detections.length > 0 ?
1525
- detections.reduce((best, current) => current.score > best.score ? current : best) :
1526
- undefined;
1527
- const qualityResult = this.validateImageQuality(video, bestDetection);
1528
- const canCapture = this.isImageQualityAcceptable(qualityResult);
1529
- const feedback = this.getQualityFeedback(qualityResult);
1530
- this.logger.state('DETECCION_CON_CALIDAD', {
1531
- detectionsCount: detections.length,
1532
- qualityScore: qualityResult.qualityScore,
1533
- canCapture,
1534
- issues: qualityResult.issues.length,
1535
- bestDetectionScore: bestDetection?.score || 0
1536
- });
1537
- return {
1538
- detections,
1539
- qualityResult,
1540
- canCapture,
1541
- feedback
1542
- };
1543
- }
1544
- // Method to get real-time quality feedback without full analysis
1545
- getQuickQualityFeedback(video) {
1546
- const canvas = document.createElement('canvas');
1547
- canvas.width = Math.min(video.videoWidth, 640); // Reduced resolution for performance
1548
- canvas.height = Math.min(video.videoHeight, 480);
1549
- const ctx = canvas.getContext('2d');
1550
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
1551
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
1552
- // Quick blur detection
1553
- const blurMetrics = this.imageQualityService.detectBlur(imageData);
1554
- // Quick reflection detection
1555
- const reflectionMetrics = this.imageQualityService.detectReflections(imageData);
1556
- // Quick focus measurement
1557
- const focusMetrics = this.imageQualityService.measureFocus(imageData);
1558
- let feedback = 'Analizando calidad...';
1559
- if (!blurMetrics.isAcceptable) {
1560
- feedback = 'Estabilice el dispositivo';
1561
- }
1562
- else if (reflectionMetrics.hasReflection) {
1563
- feedback = 'Evite reflejos en el documento';
1564
- }
1565
- else if (!focusMetrics.isFocused) {
1566
- feedback = 'Permitiendo enfoque automático...';
1567
- }
1568
- else {
1569
- feedback = 'Calidad óptima';
1570
- }
1571
- return {
1572
- hasBlur: !blurMetrics.isAcceptable,
1573
- hasReflections: reflectionMetrics.hasReflection,
1574
- isFocused: focusMetrics.isFocused,
1575
- feedback
1576
- };
1577
- }
1578
1087
  }
1579
1088
 
1580
1089
  class ServiceContainer {
@@ -1588,12 +1097,7 @@ class ServiceContainer {
1588
1097
  const logger = new LoggerService(config.debug);
1589
1098
  const stateManager = new StateManagerService(eventBus);
1590
1099
  const cameraService = new CameraService(logger, eventBus, config.preferredCamera);
1591
- const detectionService = new DetectionService(logger, config.debug, config.useDocumentClassification, {
1592
- minQualityScore: config.minQualityScore,
1593
- minFocusScore: config.minFocusScore,
1594
- minBlurScore: config.minBlurScore,
1595
- maxReflectionScore: config.maxReflectionScore
1596
- });
1100
+ const detectionService = new DetectionService(logger, config.debug, config.useDocumentClassification);
1597
1101
  // Register services
1598
1102
  this.services.set('eventBus', eventBus);
1599
1103
  this.services.set('logger', logger);
@@ -1637,7 +1141,7 @@ class ServiceContainer {
1637
1141
  }
1638
1142
  }
1639
1143
 
1640
- 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(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;z-index:20}.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-button{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;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:translateX(-50%) translateY(0)}.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}}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}";
1144
+ 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(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;z-index:20}.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-button{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;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:translateX(-50%) translateY(0)}.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}}";
1641
1145
 
1642
1146
  const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H {
1643
1147
  constructor() {
@@ -1649,18 +1153,12 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1649
1153
  }
1650
1154
  get el() { return this; }
1651
1155
  debug = false;
1652
- alignmentTolerance = 10;
1653
- maskSize = 90;
1654
- cropMargin = 0;
1156
+ alignmentTolerance = 20;
1157
+ maskSize = 80;
1158
+ cropMargin = 20;
1655
1159
  useDocumentClassification = false;
1656
1160
  preferredCamera = 'auto';
1657
1161
  captureDelay = 1000;
1658
- enableQualityValidation = true;
1659
- qualityThreshold = 60;
1660
- minQualityScore = 45;
1661
- minFocusScore = 16;
1662
- minBlurScore = 22;
1663
- maxReflectionScore = 15;
1664
1162
  captureCompleted;
1665
1163
  isReady;
1666
1164
  // State derived from services
@@ -1679,12 +1177,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1679
1177
  type: 'initializing',
1680
1178
  isInitialized: false
1681
1179
  };
1682
- qualityFeedback = {
1683
- message: 'Analizando calidad...',
1684
- score: 0,
1685
- hasIssues: false,
1686
- canCapture: false
1687
- };
1688
1180
  performanceData = {
1689
1181
  fps: 0,
1690
1182
  inferenceTime: 0,
@@ -1753,13 +1245,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1753
1245
  cropMargin: this.cropMargin,
1754
1246
  useDocumentClassification: this.useDocumentClassification,
1755
1247
  preferredCamera: this.preferredCamera,
1756
- captureDelay: this.captureDelay,
1757
- enableQualityValidation: this.enableQualityValidation,
1758
- qualityThreshold: this.qualityThreshold,
1759
- minQualityScore: this.minQualityScore,
1760
- minFocusScore: this.minFocusScore,
1761
- minBlurScore: this.minBlurScore,
1762
- maxReflectionScore: this.maxReflectionScore
1248
+ captureDelay: this.captureDelay
1763
1249
  };
1764
1250
  this.serviceContainer = new ServiceContainer(config);
1765
1251
  this.logger = this.serviceContainer.getLogger();
@@ -1819,26 +1305,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
1819
1305
  this.logger.warn(`Propiedad captureDelay inválida. Valor: ${this.captureDelay}, esperado: 0-10000. Usando valor por defecto: 1000`);
1820
1306
  this.captureDelay = 1000;
1821
1307
  }
1822
- if (this.qualityThreshold < 0 || this.qualityThreshold > 100) {
1823
- this.logger.warn(`Propiedad qualityThreshold inválida. Valor: ${this.qualityThreshold}, esperado: 0-100. Usando valor por defecto: 60`);
1824
- this.qualityThreshold = 60;
1825
- }
1826
- if (this.minQualityScore < 0 || this.minQualityScore > 100) {
1827
- this.logger.warn(`Propiedad minQualityScore inválida. Valor: ${this.minQualityScore}, esperado: 0-100. Usando valor por defecto: 45`);
1828
- this.minQualityScore = 45;
1829
- }
1830
- if (this.minFocusScore < 0 || this.minFocusScore > 100) {
1831
- this.logger.warn(`Propiedad minFocusScore inválida. Valor: ${this.minFocusScore}, esperado: 0-100. Usando valor por defecto: 16`);
1832
- this.minFocusScore = 16;
1833
- }
1834
- if (this.minBlurScore < 0 || this.minBlurScore > 200) {
1835
- this.logger.warn(`Propiedad minBlurScore inválida. Valor: ${this.minBlurScore}, esperado: 0-200. Usando valor por defecto: 22`);
1836
- this.minBlurScore = 22;
1837
- }
1838
- if (this.maxReflectionScore < 0 || this.maxReflectionScore > 100) {
1839
- this.logger.warn(`Propiedad maxReflectionScore inválida. Valor: ${this.maxReflectionScore}, esperado: 0-100. Usando valor por defecto: 15`);
1840
- this.maxReflectionScore = 15;
1841
- }
1842
1308
  }
1843
1309
  async loadOnnxRuntime() {
1844
1310
  if (!window.ort) {
@@ -2085,63 +1551,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2085
1551
  coordinates: { x, y }
2086
1552
  };
2087
1553
  }
2088
- async getImageQuality() {
2089
- if (!this.videoRef || !this.detectionService.isModelLoaded()) {
2090
- return null;
2091
- }
2092
- try {
2093
- const qualityResult = this.detectionService.validateImageQuality(this.videoRef);
2094
- return {
2095
- qualityScore: qualityResult.qualityScore,
2096
- overallQuality: qualityResult.overallQuality,
2097
- issues: qualityResult.issues,
2098
- recommendations: qualityResult.recommendations,
2099
- canCapture: this.detectionService.isImageQualityAcceptable(qualityResult)
2100
- };
2101
- }
2102
- catch (error) {
2103
- this.logger.error('Error al analizar calidad de imagen:', error);
2104
- return null;
2105
- }
2106
- }
2107
- async setQualityThresholds(thresholds) {
2108
- // Validate thresholds
2109
- if (thresholds.minQualityScore !== undefined && (thresholds.minQualityScore < 0 || thresholds.minQualityScore > 100)) {
2110
- throw new Error('minQualityScore must be between 0 and 100');
2111
- }
2112
- if (thresholds.minFocusScore !== undefined && (thresholds.minFocusScore < 0 || thresholds.minFocusScore > 100)) {
2113
- throw new Error('minFocusScore must be between 0 and 100');
2114
- }
2115
- if (thresholds.minBlurScore !== undefined && (thresholds.minBlurScore < 0 || thresholds.minBlurScore > 200)) {
2116
- throw new Error('minBlurScore must be between 0 and 200');
2117
- }
2118
- if (thresholds.maxReflectionScore !== undefined && (thresholds.maxReflectionScore < 0 || thresholds.maxReflectionScore > 100)) {
2119
- throw new Error('maxReflectionScore must be between 0 and 100');
2120
- }
2121
- // Update component properties
2122
- if (thresholds.minQualityScore !== undefined)
2123
- this.minQualityScore = thresholds.minQualityScore;
2124
- if (thresholds.minFocusScore !== undefined)
2125
- this.minFocusScore = thresholds.minFocusScore;
2126
- if (thresholds.minBlurScore !== undefined)
2127
- this.minBlurScore = thresholds.minBlurScore;
2128
- if (thresholds.maxReflectionScore !== undefined)
2129
- this.maxReflectionScore = thresholds.maxReflectionScore;
2130
- // Update detection service
2131
- this.detectionService.updateQualityThresholds(thresholds);
2132
- return {
2133
- success: true,
2134
- thresholds: this.detectionService.getQualityThresholds()
2135
- };
2136
- }
2137
- async getQualityThresholds() {
2138
- return {
2139
- minQualityScore: this.minQualityScore,
2140
- minFocusScore: this.minFocusScore,
2141
- minBlurScore: this.minBlurScore,
2142
- maxReflectionScore: this.maxReflectionScore
2143
- };
2144
- }
2145
1554
  // DETECTION METHODS
2146
1555
  async startDetection() {
2147
1556
  this.logger.state('INICIANDO_DETECCION');
@@ -2243,36 +1652,8 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2243
1652
  // Measure preprocessing and inference time
2244
1653
  const inferenceStartTime = performance.now();
2245
1654
  const inputTensor = this.detectionService.preprocess(this.videoRef);
2246
- // Enhanced detection with quality validation if enabled
2247
- let detections;
2248
- let qualityResult = null;
2249
- let canCapture = true;
2250
- if (this.enableQualityValidation) {
2251
- // Use enhanced detection method with quality validation
2252
- const enhancedResult = await this.detectionService.runInferenceWithQuality(inputTensor, this.videoRef);
2253
- detections = enhancedResult.detections;
2254
- qualityResult = enhancedResult.qualityResult;
2255
- canCapture = enhancedResult.canCapture;
2256
- // Update quality feedback state
2257
- this.qualityFeedback = {
2258
- message: enhancedResult.feedback,
2259
- score: qualityResult.qualityScore,
2260
- hasIssues: qualityResult.issues.length > 0,
2261
- canCapture: canCapture
2262
- };
2263
- }
2264
- else {
2265
- // Standard detection without quality validation
2266
- detections = await this.detectionService.runInference(inputTensor);
2267
- // Quick quality feedback for real-time display
2268
- const quickFeedback = this.detectionService.getQuickQualityFeedback(this.videoRef);
2269
- this.qualityFeedback = {
2270
- message: quickFeedback.feedback,
2271
- score: 0, // Not calculated in quick mode
2272
- hasIssues: quickFeedback.hasBlur || quickFeedback.hasReflections || !quickFeedback.isFocused,
2273
- canCapture: true // Allow capture without strict validation
2274
- };
2275
- }
1655
+ // Standard detection without quality validation
1656
+ const detections = await this.detectionService.runInference(inputTensor);
2276
1657
  const inferenceTime = performance.now() - inferenceStartTime;
2277
1658
  // Record ONNX performance metrics
2278
1659
  if (this.debug) {
@@ -2280,7 +1661,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2280
1661
  }
2281
1662
  // Update best score logic
2282
1663
  if (this.startTime && Date.now() - this.startTime < 5000) {
2283
- detections.forEach(detection => {
1664
+ detections.forEach((detection) => {
2284
1665
  const isWellPositioned = this.detectionService.isCardInFrame(detection);
2285
1666
  const effectiveScore = isWellPositioned ? detection.score * 1.2 : detection.score;
2286
1667
  if (effectiveScore > captureState.bestScore) {
@@ -2366,7 +1747,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2366
1747
  availableCameras: [],
2367
1748
  selectedCameraId: null,
2368
1749
  deviceType: 'desktop'};
2369
- return (h("div", { key: 'd71fc140c7fc87160ceaf479f9b9beae7685e9bc', class: "detector-container" }, h("div", { key: 'd83347ab6ccacfe940fb66d17adc21cff126d875', class: "video-container" }, h("video", { key: 'fb69de65e8133c7936cfb03ec86acdf89d2a8112', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'c957341c1a750a1732afd6bb1045c1ba9bb2993d', 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: {
1750
+ return (h("div", { key: '7af79a0ff25b51790a9477cd7b338a0584172fc2', class: "detector-container" }, h("div", { key: 'ebf6a3811e0e599f29e98325b975a645e3bfb5e0', class: "video-container" }, h("video", { key: '5208fc9574fa78b1149be5839bc26d04bf9bcdfc', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), h("div", { key: 'b507c786eccbd21acdd884ef50bc6ec280b9d437', 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: {
2370
1751
  position: 'absolute',
2371
1752
  left: `${box.x}px`,
2372
1753
  top: `${box.y}px`,
@@ -2375,9 +1756,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2375
1756
  border: '2px solid #32406C',
2376
1757
  pointerEvents: 'none',
2377
1758
  boxSizing: 'border-box'
2378
- } })))), this.isMaskReady && (h("div", { key: 'cf81504f7f129a2bb2789dd7addfe8d365fd7973', class: "overlay-mask" }, h("div", { key: 'b0f8ab1e02d1731295b95cbca4b44be430c6104d', class: "card-outline" }, h("div", { key: 'fadb33b8dc3c2cbfbb78aa0ff4d273b2b26a3565', class: "side side-top" }), h("div", { key: '3f3ca187195c78f2c3daffa481cc275f970c68a5', class: "side side-right" }), h("div", { key: '1e6bac145a85bbee2dab24da81b8064bc4ac282b', class: "side side-bottom" }), h("div", { key: '9bef1cd7afcef6cb1975a1103ec57d33d89f7640', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '9198d8b8fb1f4a17ec615943a09844f47a639048', class: "guide-text" }, this.enableQualityValidation && this.hasDocumentDetected && this.qualityFeedback.message ?
2379
- this.qualityFeedback.message :
2380
- 'Alinee su identificación con el marco')), this.enableQualityValidation && this.hasDocumentDetected && captureState.isVideoActive && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: '04afa661c1d988d9bedb48c78672de233dd49f9b', class: `quality-indicator ${this.qualityFeedback.hasIssues ? 'warning' : 'good'}` }, h("div", { key: 'bc789e19894de1d3dfd4a232e05e612d804e0e6c', class: "quality-score" }, this.qualityFeedback.score > 0 ? `${this.qualityFeedback.score}%` : ''), h("div", { key: '0750f5e5dfdb7a7c054d01965146d34a5bb4485a', class: `quality-status ${this.qualityFeedback.canCapture ? 'ready' : 'not-ready'}` }, this.qualityFeedback.canCapture ? '✓' : '!')))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: 'cbe391726b5d62acbb466c36b135e0556fc488f7', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: '929f8fe054b30e3c30bf066319e957a633fa587c', class: "camera-controls" }, h("button", { key: 'b4f68dd3a6865cba49c3f5d38c43874b7b9b364d', 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: 'c9315ff70704fdc265af07bf765b6247814563e6', class: "camera-selector-dropdown" }, h("div", { key: 'f4950e7e3c3e279c48310a4049550f3143fb4d3f', class: "camera-selector-header" }, h("span", { key: '4cfacf909549b0e18bd8ac6e54e127ce09b75335' }, "Seleccionar C\u00E1mara"), h("button", { key: '13d7679c5235f41f0dbc38b9f397483cf4ec9ffd', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '6c97cb8ba9486c33a633901d9dd49765f87d92ed', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '35796316fc950b8491f6167303b549b098abbf58', class: "device-info" }, h("small", { key: '83c59a8cb0c0b67a37163fe75c1f1f383fcb00f4' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: 'be5b316abb21c06cf5bb69ed090d43d45537a9e6', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '43e64fcc325c7c96871a99838c5fa094c6774af2', class: "flip-animation" }, h("div", { key: '39223d9b593716e76912ade58b556c7212a7e05f', class: "id-card-icon" }), h("div", { key: '79463b43724c3c56c45bd5b41337117d7d415618', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: '7be8ca4d0bd59b1e4127254ff68e41321aa387cc', class: "success-animation" }, h("div", { key: '03a3e625f622c546a3016f85e029f9ea165f65c3', class: "check-icon" }), h("div", { key: '9c63faad517dad4b2ede8ec5a0998ad0b5c807d5', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '94aa38c67fdaf328e74a2c1ec476987bf8e483d5', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '52a460f2fe11505158331cc06d7121db80bdfed9', class: "status-spinner" })), h("div", { key: '1eabfed00df31ff30fdf818f3d33cd837a7f31a5', class: "status-content" }, h("div", { key: '4bdbadf18fc48f2fad5dafd9ddf4a78f0a81dbf5', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '14c41346fc9fc306315cdda1c54ec1e828c38f62', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '21fb0ca08547143648097f2aa4f3eb3ade73bce1', class: "performance-monitor" }, h("div", { key: '88c990f42b5c87c9bc971acd228fc84bacec861b', class: "performance-expanded" }, h("div", { key: '351215f4b8ab202b42a4c9b0bfa8665485469f64', class: "metrics-row" }, h("div", { key: '28a2c5386ba99850f7dc41add6fb4ef073a6b8cb', class: "metric-compact" }, h("span", { key: '7a2ab758f9baf1264cd4ad70ff30f42a35c41065', class: "metric-label" }, "FPS"), h("span", { key: '1c4370d016f2bdcf61275b4869bddbd8109a7b2a', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: 'e79c27b4cbad3e70f2df917a5147dd125d51304d', class: "metric-compact" }, h("span", { key: 'aee4c178ae751cfe94735ef9fcb78f273f9cb885', class: "metric-label" }, "MEM"), h("span", { key: '823e98dec413968ee5080b29839729e6ac9f91d2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: 'e29037f684c82579a711aff022060c97ab9d93cf', class: "metrics-row" }, h("div", { key: '2917f3c4b026e3106fad741944e582aff36e10e0', class: "metric-compact" }, h("span", { key: 'e8783e8e2b18b29b37342eea376a75f257a46850', class: "metric-label" }, "INF"), h("span", { key: '30cdc1db3189135bc772aab5c31e081294ebd431', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: 'c481ec26394e35efe38047e5fa1b835e520f49b8', class: "metric-compact" }, h("span", { key: 'c806cae91f3fcad896bd6053b6a0391ab518ab6f', class: "metric-label" }, "FRAME"), h("span", { key: 'af9996e2093c918ea903d036e637905baeef1aa2', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '48422447a6d00f78a9f861bfc50de4ef5a628a57', class: "metrics-row" }, h("div", { key: 'c88a03fbff56f68796df33187256fd5a5a68f763', class: "metric-compact" }, h("span", { key: '5e366c35865f8bf45aecac126ce98e0e17be0825', class: "metric-label" }, "DET"), h("span", { key: '123570ac9ae59cf5ef1b992ea6c4563b753990c3', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: 'dea3a496f84e80f9d6f27173c8fa2e4149bf8e0b', class: "metric-compact" }, h("span", { key: 'fe47d6c16ca84fff8824bef92d8ce9efe50dfa38', class: "metric-label" }, "RATE"), h("span", { key: '873846eb42c1ed97f4e9493871e51741766c852f', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'b485b1d8fafee0314ffbf1c7934ffc9a3d4c6902', class: "watermark" }, h("img", { key: 'f5fae340c3e96ecafe26aa819dbc2411038aa953', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
1759
+ } })))), this.isMaskReady && (h("div", { key: '53066c626b23daae351ea9a80409e8882434b0dd', class: "overlay-mask" }, h("div", { key: '77bb4048d43e328e57a5c7e1909e0a59c8957217', class: "card-outline" }, h("div", { key: 'ea0f2610d903f985286856ab69f8be24fd42148e', class: "side side-top" }), h("div", { key: 'dc671111ef4adf33aa41f4457fdf344ede23eb80', class: "side side-right" }), h("div", { key: '2475ec41577dca92b425542c8861c570ed7ecd3b', class: "side side-bottom" }), h("div", { key: 'a06171c8eb1bd6353a6464485604560d73ac04b7', class: "side side-left" }), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("div", { key: 'ecfd98f36d457fe7282b26f6a0efef5361380c76', class: "guide-text" }, "Alinee su identificaci\u00F3n con el marco"))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (h("button", { key: 'f3c7b66500110437e9e4a7442cb327f2fdc6d71f', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")), captureState.isVideoActive && (h("div", { key: 'b5b2a8dfc6dbcf381c32c4caa562c27ced2e3e56', class: "camera-controls" }, h("button", { key: '6b40d4649dfa22c5ea8ca90cacce35cd0ca53ff3', 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: '41feec90287510b08bd1090ad15586b323007c30', class: "camera-selector-dropdown" }, h("div", { key: '8310ebe3db842c463ad428b973f841053315a76f', class: "camera-selector-header" }, h("span", { key: '6693bdfa33643dfe73798d4f25b0c2d9de7aac7c' }, "Seleccionar C\u00E1mara"), h("button", { key: '8552d45e1482c570d7ff85588d4404eec7c369d7', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), h("div", { key: '5333ec0e5fb2c50a07a7c0b7d4dfb1bb4ee0df1c', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`), cameraInfo.selectedCameraId === camera.id && (h("span", { class: "selected-indicator" }, "\u2713")))))), h("div", { key: '8adf70b0e632c56a4784dec44621688809a3ff56', class: "device-info" }, h("small", { key: '2ea23922386ecda4a9011b50faba5efe4b4e60bb' }, "Dispositivo: ", cameraInfo.deviceType)))))), captureState.isCapturing && (h("div", { key: '8e693db67fe1e6ae8f11082b8993682b6d19b11d', class: "capture-animation" })), captureState.showFlipAnimation && (h("div", { key: '474489ed7848cdc163c492a5e19f5f13231e17b3', class: "flip-animation" }, h("div", { key: 'a9bd24ca21330882f001b7811077630eb1c30f50', class: "id-card-icon" }), h("div", { key: 'ffadb660a61e0f5e3e29bdc473d987e87dc4ad8a', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (h("div", { key: 'd2522ee33aa159d49f040a293775892fb1cb81b5', class: "success-animation" }, h("div", { key: 'e7f899fac58bc9a3d51cf2cb89ee33bb320b241f', class: "check-icon" }), h("div", { key: 'd5d0ee1f37dbc659ca625ad03cbce325be0cf0a6', class: "success-text" }, "\u00A1Proceso completado!"))), h("div", { key: '425d347666032c3982a660ebee13b47670b996cb', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (h("div", { key: '4d396fa107872f9a1e526fd59dc4edf279a05792', class: "status-spinner" })), h("div", { key: '232e9ae24c8cf5ef8ac0e3442e45a920799c724d', class: "status-content" }, h("div", { key: 'fe9dc0ca6d8e8276052bac7d2d44f1c7c9730364', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (h("div", { key: '6910c7ac78fd89e6fa1165446ec6d46fb356035c', class: "status-description" }, this.currentStatus.description)))), this.debug && (h("div", { key: '7282b8afaf03da53b01a23d04babd772528c3c14', class: "performance-monitor" }, h("div", { key: 'b582b3124fed13387cd70a02a34ab2bb541f3bfc', class: "performance-expanded" }, h("div", { key: '764bac2d61224b9a5f306f50934d410e1bd27787', class: "metrics-row" }, h("div", { key: '261563f8314a9f6069b1ac6362eb9c38936a9dc7', class: "metric-compact" }, h("span", { key: '4feea98eff86620c1bc90eb65d9799102423109d', class: "metric-label" }, "FPS"), h("span", { key: 'eb15f13b27b8aa50701ad3d11c4cf87200010b67', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), h("div", { key: '0813c35c2c33286f2276bfb5adce59b32c7bb597', class: "metric-compact" }, h("span", { key: '201e7bcb556e63b141e42a3ffc3419eb4284bfa8', class: "metric-label" }, "MEM"), h("span", { key: '4f6703cbe0128440d0f2f22f631035ab85422c64', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), h("div", { key: '6343c361c93138b9ea66e8ae8acd361b93333f8c', class: "metrics-row" }, h("div", { key: 'd3c81fe597004bf380fae2d32f3a8314317e7769', class: "metric-compact" }, h("span", { key: 'fce572af3e200a611974dceef4da02f6aecedffc', class: "metric-label" }, "INF"), h("span", { key: '18dd78d54dcecfb70ddf59b782f87f8c6f208b0f', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), h("div", { key: '4c31ebfd835b017fd3e41b23d6bd9b19add511aa', class: "metric-compact" }, h("span", { key: '66b1a380b9479a8fc1e8c2385cb72aa4158dc74e', class: "metric-label" }, "FRAME"), h("span", { key: '3c6e41f7f11123c524d3a9df409345929d63f352', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), h("div", { key: '3b522e2cdfe4bd9f1dd6eb0f91ee3577dc43e490', class: "metrics-row" }, h("div", { key: 'fa1e6f687b43aa2582b8b3426feb03a16c6493fc', class: "metric-compact" }, h("span", { key: 'a9355091e71a33a6a0d7cc6c11171dcda2e51b22', class: "metric-label" }, "DET"), h("span", { key: 'beca5a7ccf4853675cf8366bcf577ebfbd46c4f0', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), h("div", { key: '4b07fc9b3d35f99bc6e77c8726192cde0476a787', class: "metric-compact" }, h("span", { key: 'a1674b538f45328ee0afb00e582c07e0b4ccf553', class: "metric-label" }, "RATE"), h("span", { key: 'fd384a5137bc3df6c939d3d698a3ef80e9087030', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), h("div", { key: 'ebbaf15a0ad96e4da5bbf746c32d7a4abb51b973', class: "watermark" }, h("img", { key: '8e7d274b529d1f7020ec4a7225579792763f871a', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
2381
1760
  }
2382
1761
  // Utility methods
2383
1762
  updateDetectionBoxes(boxes) {
@@ -2449,19 +1828,14 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2449
1828
  if (allSidesAligned && bestBox) {
2450
1829
  cardOutline?.classList.add('perfect-match');
2451
1830
  corners?.forEach(corner => corner.classList.add('perfect-match'));
2452
- // Check quality validation before allowing capture
2453
- const qualityCheckPassed = !this.enableQualityValidation || this.qualityFeedback.canCapture;
2454
1831
  // Debug logging
2455
1832
  this.logger.state('CAPTURE_EVALUATION', {
2456
1833
  allSidesAligned: true,
2457
1834
  hasScreenshotTaken: this.hasScreenshotTaken,
2458
- qualityCheckPassed,
2459
- enableQualityValidation: this.enableQualityValidation,
2460
- qualityFeedback: this.qualityFeedback,
2461
1835
  captureDelay: this.captureDelay,
2462
1836
  alignmentStartTime: this.alignmentStartTime
2463
1837
  });
2464
- if (!this.hasScreenshotTaken && qualityCheckPassed) {
1838
+ if (!this.hasScreenshotTaken) {
2465
1839
  const currentTime = Date.now();
2466
1840
  // Initialize alignment start time if not set
2467
1841
  if (!this.alignmentStartTime) {
@@ -2478,8 +1852,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2478
1852
  if (alignmentDuration >= this.captureDelay) {
2479
1853
  this.logger.state('TRIGGERING_CAPTURE', {
2480
1854
  alignmentDuration,
2481
- captureDelay: this.captureDelay,
2482
- qualityScore: this.qualityFeedback.score
1855
+ captureDelay: this.captureDelay
2483
1856
  });
2484
1857
  this.lastDetectedBox = bestBox;
2485
1858
  this.takeScreenshot().catch(error => {
@@ -2492,17 +1865,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2492
1865
  }, 2000);
2493
1866
  }
2494
1867
  }
2495
- else if (!qualityCheckPassed) {
2496
- // Reset alignment timer if quality check fails
2497
- this.logger.state('QUALITY_CHECK_FAILED', {
2498
- enableQualityValidation: this.enableQualityValidation,
2499
- canCapture: this.qualityFeedback.canCapture,
2500
- qualityScore: this.qualityFeedback.score,
2501
- hasIssues: this.qualityFeedback.hasIssues
2502
- });
2503
- this.alignmentStartTime = undefined;
2504
- cardOutline?.classList.add('quality-warning');
2505
- }
2506
1868
  }
2507
1869
  else {
2508
1870
  cardOutline?.classList.remove('perfect-match');
@@ -2775,12 +2137,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2775
2137
  "useDocumentClassification": [4, "use-document-classification"],
2776
2138
  "preferredCamera": [1, "preferred-camera"],
2777
2139
  "captureDelay": [2, "capture-delay"],
2778
- "enableQualityValidation": [4, "enable-quality-validation"],
2779
- "qualityThreshold": [2, "quality-threshold"],
2780
- "minQualityScore": [2, "min-quality-score"],
2781
- "minFocusScore": [2, "min-focus-score"],
2782
- "minBlurScore": [2, "min-blur-score"],
2783
- "maxReflectionScore": [2, "max-reflection-score"],
2784
2140
  "detectionBoxes": [32],
2785
2141
  "sideAlignment": [32],
2786
2142
  "isMaskReady": [32],
@@ -2789,7 +2145,6 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2789
2145
  "isSwitchingCamera": [32],
2790
2146
  "hasDocumentDetected": [32],
2791
2147
  "currentStatus": [32],
2792
- "qualityFeedback": [32],
2793
2148
  "performanceData": [32],
2794
2149
  "getCapturedImages": [64],
2795
2150
  "isProcessCompleted": [64],
@@ -2804,10 +2159,7 @@ const JaakStamps$1 = /*@__PURE__*/ proxyCustomElement(class JaakStamps extends H
2804
2159
  "setCaptureDelay": [64],
2805
2160
  "getCaptureDelay": [64],
2806
2161
  "setTorchEnabled": [64],
2807
- "focusAtPoint": [64],
2808
- "getImageQuality": [64],
2809
- "setQualityThresholds": [64],
2810
- "getQualityThresholds": [64]
2162
+ "focusAtPoint": [64]
2811
2163
  }]);
2812
2164
  function defineCustomElement$1() {
2813
2165
  if (typeof customElements === "undefined") {