diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 40e08c0..ac4a2f0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 143fbea..11f2bef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/views/ScannerView.jsx b/frontend/src/views/ScannerView.jsx index af8f107..e761c95 100644 --- a/frontend/src/views/ScannerView.jsx +++ b/frontend/src/views/ScannerView.jsx @@ -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); + 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 { - resolve(); + 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; - stopCamera(); - playBeep(); - setScannedCip(rawValue); - setPhase('prescriptions'); - fetchPrescriptions(rawValue); - return; - } + 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(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) { + console.error('[Scanner] Error general:', err); stopCamera(); if (err.name === 'NotAllowedError') { setErrorMsg('Permiso de cámara denegado. Permite el acceso e intenta de nuevo.');