feat: replace barcode-detector polyfill with local @zxing/browser for cross-browser barcode scanning #6

Merged
Ichitux merged 1 commits from fixing_barcode into main 2026-07-03 10:10:04 +00:00
3 changed files with 109 additions and 91 deletions
+43 -53
View File
@@ -1,11 +1,11 @@
{ {
"name": "farma-finder-frontend", "name": "farma-clic-frontend",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "farma-finder-frontend", "name": "farma-clic-frontend",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@capacitor-mlkit/barcode-scanning": "^8.1.0", "@capacitor-mlkit/barcode-scanning": "^8.1.0",
@@ -17,7 +17,8 @@
"@grafana/faro-transport-otlp-http": "^1.7.0", "@grafana/faro-transport-otlp-http": "^1.7.0",
"@grafana/faro-web-sdk": "^1.7.0", "@grafana/faro-web-sdk": "^1.7.0",
"@grafana/faro-web-tracing": "^1.7.0", "@grafana/faro-web-tracing": "^1.7.0",
"barcode-detector": "^3.2.0", "@zxing/browser": "^0.2.0",
"@zxing/library": "^0.22.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -3467,11 +3468,6 @@
"@babel/types": "^7.28.2" "@babel/types": "^7.28.2"
} }
}, },
"node_modules/@types/emscripten": {
"version": "1.41.5",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q=="
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -3696,6 +3692,37 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@zxing/browser": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.2.0.tgz",
"integrity": "sha512-+ORhrLva0vm6ck74NDCmvYNW3XLoAG81Mu90qfcssN1PBKJjQadxZGeMCcIk+BdJbD/zEAjjHDXOwEK1QCmRtw==",
"optionalDependencies": {
"@zxing/text-encoding": "^0.9.0"
},
"peerDependencies": {
"@zxing/library": "^0.22.0"
}
},
"node_modules/@zxing/library": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.22.0.tgz",
"integrity": "sha512-BmInervZV7NwaZWX1LW64sZ4Lh4wxXYFZwGmj98ArPOkRXCtO9b8Gog0Xyh82dsYYGOeRxX+aAhLSq+hQ2XLZQ==",
"dependencies": {
"ts-custom-error": "^3.3.1"
},
"engines": {
"node": ">= 24.0.0"
},
"optionalDependencies": {
"@zxing/text-encoding": "~0.9.0"
}
},
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"optional": true
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.16.0", "version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -3943,14 +3970,6 @@
"node": "18 || 20 || >=22" "node": "18 || 20 || >=22"
} }
}, },
"node_modules/barcode-detector": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-3.2.0.tgz",
"integrity": "sha512-MrT5TT058ptG5YB157pHLfXKVpp0BKEfQBOb8QvzTbatzmLDu85JJ0Gd/sCYwbwdwStJvxsYflrSN6D6E4Ndyw==",
"dependencies": {
"zxing-wasm": "3.1.0"
}
},
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.9.11", "version": "2.9.11",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
@@ -7490,17 +7509,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
"integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/temp-dir": { "node_modules/temp-dir": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -7635,6 +7643,14 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/ts-custom-error": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -8564,32 +8580,6 @@
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
},
"node_modules/zxing-wasm": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-3.1.0.tgz",
"integrity": "sha512-5+3V1wPRx4gvbeLH2jB7n2cKrYJ1q4i3QgjnBUtrDPeqxJSi6BdzKJg4y6aF6bgW8zfntnYJyrkqFMevDhL2NA==",
"dependencies": {
"@types/emscripten": "^1.41.5",
"type-fest": "^5.7.0"
},
"peerDependencies": {
"@types/emscripten": ">=1.39.6"
}
},
"node_modules/zxing-wasm/node_modules/type-fest": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz",
"integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==",
"dependencies": {
"tagged-tag": "^1.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
} }
} }
} }
+2 -1
View File
@@ -18,7 +18,8 @@
"@grafana/faro-transport-otlp-http": "^1.7.0", "@grafana/faro-transport-otlp-http": "^1.7.0",
"@grafana/faro-web-sdk": "^1.7.0", "@grafana/faro-web-sdk": "^1.7.0",
"@grafana/faro-web-tracing": "^1.7.0", "@grafana/faro-web-tracing": "^1.7.0",
"barcode-detector": "^3.2.0", "@zxing/browser": "^0.2.0",
"@zxing/library": "^0.22.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
+64 -37
View File
@@ -1,10 +1,11 @@
import React, { useState, useCallback, useRef } from 'react'; import React, { useState, useCallback, useRef } from 'react';
import { BarcodeScanner, BarcodeFormat } from '@capacitor-mlkit/barcode-scanning'; import { BarcodeScanner, BarcodeFormat } from '@capacitor-mlkit/barcode-scanning';
import { Capacitor } from '@capacitor/core'; import { Capacitor } from '@capacitor/core';
import 'barcode-detector/polyfill'; import { BrowserMultiFormatReader } from '@zxing/browser';
import { BarcodeFormat as ZxingFormat, DecodeHintType } from '@zxing/library';
import './ScannerView.css'; import './ScannerView.css';
const CIP_REGEX = /^[A-Z0-9]{16}$/i; const CIP_REGEX = /^[A-Z0-9]{8,30}$/i;
function playBeep() { function playBeep() {
try { try {
@@ -29,6 +30,12 @@ function ScannerView({ onClose, onSelectMedicine }) {
const rafRef = useRef(null); const rafRef = useRef(null);
const detectorRef = useRef(null); const detectorRef = useRef(null);
// Log de diagnóstico al montar
React.useEffect(() => {
console.log('[Scanner] BarcodeDetector global:', typeof BarcodeDetector);
console.log('[Scanner] Navegador:', navigator.userAgent);
}, []);
const [phase, setPhase] = useState('idle'); const [phase, setPhase] = useState('idle');
const [manualCip, setManualCip] = useState(''); const [manualCip, setManualCip] = useState('');
const [prescriptions, setPrescriptions] = useState([]); const [prescriptions, setPrescriptions] = useState([]);
@@ -112,6 +119,7 @@ function ScannerView({ onClose, onSelectMedicine }) {
async function handleWebScan() { async function handleWebScan() {
setErrorMsg(''); setErrorMsg('');
console.log('[Scanner] Iniciando escaneo web...');
try { try {
if (!navigator.mediaDevices?.getUserMedia) { if (!navigator.mediaDevices?.getUserMedia) {
setErrorMsg('Cámara no disponible en este navegador.'); setErrorMsg('Cámara no disponible en este navegador.');
@@ -119,58 +127,77 @@ function ScannerView({ onClose, onSelectMedicine }) {
return; return;
} }
let detector;
try {
detector = new BarcodeDetector({ formats: ['pdf417', 'code_128', 'qr_code'] });
} catch {
setErrorMsg('La detección de códigos no está soportada en este navegador.');
setPhase('error');
return;
}
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 720 } }, video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 720 } },
}); });
console.log('[Scanner] Stream de cámara obtenido');
streamRef.current = stream; streamRef.current = stream;
detectorRef.current = detector;
setPhase('scanning'); setPhase('scanning');
await new Promise((resolve) => { await new Promise((resolve) => {
requestAnimationFrame(() => { const waitForVideo = () => {
if (videoRef.current) { if (videoRef.current) {
videoRef.current.srcObject = stream; videoRef.current.srcObject = stream;
videoRef.current.play().then(resolve).catch(resolve); videoRef.current.play().then(() => {
console.log('[Scanner] Video reproduciéndose');
console.log('[Scanner] Video dims:', videoRef.current.videoWidth, 'x', videoRef.current.videoHeight);
resolve();
}).catch((e) => {
console.error('[Scanner] Error al reproducir video:', e);
resolve();
});
} else { } else {
resolve(); requestAnimationFrame(waitForVideo);
} }
}); };
requestAnimationFrame(waitForVideo);
}); });
let found = false; console.log('[Scanner] Usando zxing local (@zxing/browser)...');
async function scanFrame() { const hints = new Map();
if (found || !videoRef.current || !detectorRef.current) return; hints.set(DecodeHintType.POSSIBLE_FORMATS, [
try { ZxingFormat.CODE_128,
const barcodes = await detectorRef.current.detect(videoRef.current); ZxingFormat.CODE_39,
if (barcodes.length > 0) { ZxingFormat.CODE_93,
const rawValue = barcodes[0].rawValue; ZxingFormat.PDF_417,
if (rawValue && CIP_REGEX.test(rawValue)) { ZxingFormat.QR_CODE,
found = true; ZxingFormat.EAN_13,
stopCamera(); ZxingFormat.EAN_8,
playBeep(); ZxingFormat.UPC_A,
setScannedCip(rawValue); ZxingFormat.UPC_E,
setPhase('prescriptions'); ZxingFormat.ITF,
fetchPrescriptions(rawValue); ]);
return; hints.set(DecodeHintType.TRY_HARDER, true);
} const zxingReader = new BrowserMultiFormatReader(hints);
detectorRef.current = zxingReader;
const formatNames = { 0:'AZTEC', 1:'CODABAR', 2:'CODE_39', 3:'CODE_93', 4:'CODE_128', 5:'DATA_MATRIX', 6:'EAN_8', 7:'EAN_13', 8:'ITF', 10:'PDF_417', 11:'QR_CODE', 12:'RSS_14', 14:'UPC_A', 15:'UPC_E' };
console.log('[Scanner] Iniciando decodeFromVideoElement...');
const controls = await zxingReader.decodeFromVideoElement(videoRef.current, (result, error) => {
if (result) {
const rawValue = result.getText();
const cleaned = rawValue.replace(/[^A-Z0-9]/gi, '');
const format = result.getBarcodeFormat();
console.log('[Scanner] zxing detectó! raw:', rawValue, '| limpio:', cleaned, '| formato:', formatNames[format] || format);
if (cleaned && CIP_REGEX.test(cleaned)) {
console.log('[Scanner] CIP válido ✓');
controls.stop();
stopCamera();
playBeep();
setScannedCip(cleaned);
setPhase('prescriptions');
fetchPrescriptions(cleaned);
} else if (rawValue) {
console.log('[Scanner] Código no cumple regex:', cleaned);
} }
} catch { }
if (!found) {
rafRef.current = requestAnimationFrame(scanFrame);
} }
} });
scanFrame(); console.log('[Scanner] Escaneando...');
} catch (err) { } catch (err) {
console.error('[Scanner] Error general:', err);
stopCamera(); stopCamera();
if (err.name === 'NotAllowedError') { if (err.name === 'NotAllowedError') {
setErrorMsg('Permiso de cámara denegado. Permite el acceso e intenta de nuevo.'); setErrorMsg('Permiso de cámara denegado. Permite el acceso e intenta de nuevo.');