Files
FarmaFinder/.opencode/plans/tsi-barcode-scanning.md
T
Antoni Nuñez Romeu 2c1b3cfca6
Build & Push Docker Images / test-backend (push) Successful in 24s
Build & Push Docker Images / test-frontend (push) Successful in 23s
Build & Push Docker Images / build-backend (push) Successful in 2m22s
Build & Push Docker Images / build-frontend (push) Successful in 57s
Documentos + Homepage rediseñada
2026-06-26 16:59:20 +02:00

8.1 KiB

FarmaFinder — TSI Barcode Scanning + Capacitor 8 Upgrade

Overview

Two sequential phases:

  1. Phase A — Upgrade Capacitor 6 → 8 (prerequisite for latest barcode scanner plugin)
  2. Phase B — Implement real TSI barcode scanning with manual CIP entry fallback

Phase A: Upgrade Capacitor 6 → 8

A1. Upgrade Capacitor packages (root)

npm install @capacitor/cli@latest @capacitor/core@latest \
            @capacitor/android@latest @capacitor/ios@latest \
            @capacitor/app@latest @capacitor/splash-screen@latest \
            @capacitor/status-bar@latest

This bumps all @capacitor/* from ^6.x to ^8.x in package.json.

A2. Update android/variables.gradle

Replace all 13 version variables to meet Capacitor 8 minimums:

Variable Current New
minSdkVersion 22 24
compileSdkVersion 34 36
targetSdkVersion 34 36
androidxActivityVersion 1.8.0 1.11.0
androidxAppCompatVersion 1.6.1 1.7.1
androidxCoordinatorLayoutVersion 1.2.0 1.3.0
androidxCoreVersion 1.12.0 1.17.0
androidxFragmentVersion 1.6.2 1.8.9
coreSplashScreenVersion 1.0.1 1.2.0
androidxWebkitVersion 1.9.0 1.14.0
junitVersion 4.13.2 4.13.2 (same)
androidxJunitVersion 1.1.5 1.3.0
androidxEspressoCoreVersion 3.5.1 3.7.0
cordovaAndroidVersion 10.1.1 14.0.1

A3. Update ios/App/Podfile

  • Line 3: Change platform :ios, '13.0'platform :ios, '15.0'

A4. Update android/app/src/main/AndroidManifest.xml

  • Add density to android:configChanges on line 13:
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|density"
    

A5. Update android/build.gradle (Gradle plugin)

  • Update Android Gradle Plugin from 8.2.18.13.0
  • Update Google Services plugin from 4.4.04.4.4
  • Update Gradle wrapper to 8.14.3 in android/gradle/wrapper/gradle-wrapper.properties

A6. Run Capacitor sync

npx cap sync

A7. Verify build

  • Open android/ in Android Studio — confirm Gradle sync succeeds
  • Open ios/App in Xcode — confirm pod install succeeds and build compiles

Phase B: Real TSI Barcode Scanning

B1. Install barcode scanning plugin

From frontend/:

npm install @capacitor-mlkit/barcode-scanning@^8.1.0
npm install barcode-detector   # web polyfill for PWA browser fallback

From root:

npx cap sync

Plugin: @capacitor-mlkit/barcode-scanning v8.1.0 — Google ML Kit engine, 91K+ weekly npm downloads, supports Code 128 (TSI physical card barcode) and QR Code (virtual TSI card).

B2. Native manifest verification (no changes expected)

Platform File Required Status
Android AndroidManifest.xml:44 CAMERA permission Already present
iOS Info.plist:52-53 NSCameraUsageDescription Already present

B3. Rewrite frontend/src/views/ScannerView.jsx

Remove:

  • MOCK_TSI_CARDS array and all mock card state/UI
  • navigator.mediaDevices.getUserMedia() camera logic (lines 40-73)
  • Auto-scan setTimeout simulation (lines 98-105)
  • Simulated scan fallback panel (lines 170-198)
  • selectedMockCard state

Add:

  • Import BarcodeScanner, BarcodeFormat, LensFacing from @capacitor-mlkit/barcode-scanning
  • Import Capacitor from @capacitor/core (platform detection)
  • Import barcode-detector/polyfill at top (web fallback)

New scan flow:

1. Component mounts → show "Start Scanning" button + manual CIP input
2. User taps "Start Scanning":
   a. Check BarcodeScanner.isSupported() → error if not
   b. Check/request camera permission via BarcodeScanner.checkPermissions() / requestPermissions()
   c. Call BarcodeScanner.scan({ formats: [BarcodeFormat.Code128, BarcodeFormat.QrCode], autoZoom: true })
   d. Extract barcodes[0].rawValue (NOTE: can be undefined per v8 breaking change — handle gracefully)
   e. Validate CIP format: /^[A-Z0-9]{16}$/i
   f. If valid → fetchPrescriptions(cip) → show prescriptions
   g. If invalid → show error "Invalid card barcode. Try manual entry."
3. Manual CIP input:
   - Text input field with placeholder "Enter CIP code manually"
   - "Submit" button → validates format → fetchPrescriptions(cip)
4. PWA/browser fallback:
   - Detect: !Capacitor.isNativePlatform()
   - Show message: "Barcode scanning requires the mobile app. Enter your CIP code manually below."
   - Show only the manual CIP input (no scan button)

Phases (simplified):

  • idle — initial state, scan button + manual input visible
  • scanning — ML Kit native UI active (camera takes over)
  • prescriptions — results display (keep existing prescription card UI)
  • error — permission denied / unsupported / invalid barcode

Keep unchanged:

  • playBeep() function
  • fetchPrescriptions(cip) function
  • handlePickPrescription(rx) function
  • handleBack() function
  • Prescription display UI (lines 201-249)
  • onSelectMedicine callback prop

B4. Rewrite frontend/src/views/ScannerView.css

Remove:

  • .scanner-viewport-wrap, .scanner-camera-container, .scanner-video, .scanner-frame, .scanner-laser, .scanner-hint (old camera overlay)
  • .scanner-simulate-panel, .mock-cards-list, .mock-card-btn, .simulate-scan-btn, .simulate-label, .mock-card-icon, .mock-card-info (mock card panel)
  • .scanner-placeholder with spinner (old loading state)

Add:

  • .scan-start-btn — large centered button with camera icon, teal background (#0f766e), full-width
  • .manual-cip-section — input group with text field + submit button
  • .cip-input — text input styled to match existing dark theme, monospace font
  • .cip-submit-btn — teal button matching scan button style
  • .pwa-notice — info banner for browser users
  • .scan-error-panel — error state with icon and retry button

B5. No changes to other files

  • HomeView.jsx — "Scan TSI Card" button still navigates to ScannerView
  • PublicView.jsx — screen routing and onSelectMedicine handoff unchanged
  • backend/server.js — user handles /api/tsi/:cip/prescriptions
  • capacitor.config.json — no scanner-specific config needed

Files Modified (summary)

File Phase Change
package.json A1 Bump all @capacitor/* to ^8.x
frontend/package.json B1 Add @capacitor-mlkit/barcode-scanning@^8.1.0 + barcode-detector
android/variables.gradle A2 Update all 13 SDK/library versions
ios/App/Podfile A3 iOS deployment target 13.0 → 15.0
android/app/src/main/AndroidManifest.xml A4 Add density to configChanges
android/build.gradle A5 AGP 8.2.1 → 8.13.0, Google Services 4.4.0 → 4.4.4
android/gradle/wrapper/gradle-wrapper.properties A5 Gradle wrapper → 8.14.3
frontend/src/views/ScannerView.jsx B3 Full rewrite — real barcode scanning + manual input
frontend/src/views/ScannerView.css B4 Remove old camera/mock styles, add new scan UI

Known Issues

  • Android NullPointerException bug (GitHub capawesome-team/capacitor-mlkit #160/#324) — affects startScan() in v8.0.1. Workaround: use scan() method (built-in native UI) instead of startScan() (custom WebView mode). Plan uses scan(), so this shouldn't hit us.
  • rawValue can be undefined in v8.x — handle with null check before CIP validation.
  • Capacitor 8 requires Xcode 26+ — must have latest Xcode installed.
  • Gradle 8.14.3 requires JDK 21+ — confirm JDK 21 is available.
  • @capacitor-mlkit/barcode-scanning v6.x.x is deprecated — this is why we upgrade to Capacitor 8 first (v8.x.x plugin requires Cap 8).

Implementation Order

Step Phase Estimated Time
A1-A6 Capacitor upgrade ~30 min
A7 Verify build (Android + iOS) ~30 min
B1 Install barcode plugin ~5 min
B3 Rewrite ScannerView.jsx ~2 hrs
B4 Update ScannerView.css ~30 min
B5 Test on Android device ~30 min
B5 Test on iOS device ~30 min

Total: ~4-5 hours