Merge pull request 'feat: replace barcode-detector polyfill with local @zxing/browser for cross-browser barcode scanning' (#6) from fixing_barcode into main
Build & Push Docker Images / test-backend (push) Successful in 33s
Build & Push Docker Images / test-frontend (push) Successful in 31s
Build & Push Docker Images / deploy (push) Successful in 7s
Build & Push Docker Images / build-backend (push) Successful in 22s
Build & Push Docker Images / build-frontend (push) Successful in 54s
Build & Push Docker Images / test-backend (push) Successful in 33s
Build & Push Docker Images / test-frontend (push) Successful in 31s
Build & Push Docker Images / deploy (push) Successful in 7s
Build & Push Docker Images / build-backend (push) Successful in 22s
Build & Push Docker Images / build-frontend (push) Successful in 54s
Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
Generated
+43
-53
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
} else {
|
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();
|
resolve();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
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();
|
stopCamera();
|
||||||
playBeep();
|
playBeep();
|
||||||
setScannedCip(rawValue);
|
setScannedCip(cleaned);
|
||||||
setPhase('prescriptions');
|
setPhase('prescriptions');
|
||||||
fetchPrescriptions(rawValue);
|
fetchPrescriptions(cleaned);
|
||||||
return;
|
} else if (rawValue) {
|
||||||
|
console.log('[Scanner] Código no cumple regex:', cleaned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch { }
|
});
|
||||||
if (!found) {
|
console.log('[Scanner] Escaneando...');
|
||||||
rafRef.current = requestAnimationFrame(scanFrame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scanFrame();
|
|
||||||
} 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.');
|
||||||
|
|||||||
Reference in New Issue
Block a user