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",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "farma-finder-frontend",
"name": "farma-clic-frontend",
"version": "1.0.0",
"dependencies": {
"@capacitor-mlkit/barcode-scanning": "^8.1.0",
@@ -17,7 +17,8 @@
"@grafana/faro-transport-otlp-http": "^1.7.0",
"@grafana/faro-web-sdk": "^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",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -3467,11 +3468,6 @@
"@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": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -3696,6 +3692,37 @@
"dev": true,
"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": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
@@ -3943,14 +3970,6 @@
"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": {
"version": "2.9.11",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
@@ -7490,17 +7509,6 @@
"dev": true,
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -7635,6 +7643,14 @@
"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": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -8564,32 +8580,6 @@
"funding": {
"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-web-sdk": "^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",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+61 -34
View File
@@ -1,10 +1,11 @@
import React, { useState, useCallback, useRef } from 'react';
import { BarcodeScanner, BarcodeFormat } from '@capacitor-mlkit/barcode-scanning';
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';
const CIP_REGEX = /^[A-Z0-9]{16}$/i;
const CIP_REGEX = /^[A-Z0-9]{8,30}$/i;
function playBeep() {
try {
@@ -29,6 +30,12 @@ function ScannerView({ onClose, onSelectMedicine }) {
const rafRef = 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 [manualCip, setManualCip] = useState('');
const [prescriptions, setPrescriptions] = useState([]);
@@ -112,6 +119,7 @@ function ScannerView({ onClose, onSelectMedicine }) {
async function handleWebScan() {
setErrorMsg('');
console.log('[Scanner] Iniciando escaneo web...');
try {
if (!navigator.mediaDevices?.getUserMedia) {
setErrorMsg('Cámara no disponible en este navegador.');
@@ -119,58 +127,77 @@ function ScannerView({ onClose, onSelectMedicine }) {
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({
video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 720 } },
});
console.log('[Scanner] Stream de cámara obtenido');
streamRef.current = stream;
detectorRef.current = detector;
setPhase('scanning');
await new Promise((resolve) => {
requestAnimationFrame(() => {
const waitForVideo = () => {
if (videoRef.current) {
videoRef.current.srcObject = stream;
videoRef.current.play().then(resolve).catch(resolve);
} else {
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 {
requestAnimationFrame(waitForVideo);
}
};
requestAnimationFrame(waitForVideo);
});
let found = false;
async function scanFrame() {
if (found || !videoRef.current || !detectorRef.current) return;
try {
const barcodes = await detectorRef.current.detect(videoRef.current);
if (barcodes.length > 0) {
const rawValue = barcodes[0].rawValue;
if (rawValue && CIP_REGEX.test(rawValue)) {
found = true;
console.log('[Scanner] Usando zxing local (@zxing/browser)...');
const hints = new Map();
hints.set(DecodeHintType.POSSIBLE_FORMATS, [
ZxingFormat.CODE_128,
ZxingFormat.CODE_39,
ZxingFormat.CODE_93,
ZxingFormat.PDF_417,
ZxingFormat.QR_CODE,
ZxingFormat.EAN_13,
ZxingFormat.EAN_8,
ZxingFormat.UPC_A,
ZxingFormat.UPC_E,
ZxingFormat.ITF,
]);
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(rawValue);
setScannedCip(cleaned);
setPhase('prescriptions');
fetchPrescriptions(rawValue);
return;
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) {
console.error('[Scanner] Error general:', err);
stopCamera();
if (err.name === 'NotAllowedError') {
setErrorMsg('Permiso de cámara denegado. Permite el acceso e intenta de nuevo.');