Para crear un escáner de códigos de barras de Ionic con Capacitor, crea una aplicación de Ionic con la integración Capacitor, instala el paquete oficial @capacitor/barcode-scanner plugin, aumentar Android's minSdkVersion agregue la descripción de uso de la cámara de iOS 26 y llame CapacitorBarcodeScanner.scanBarcode() desde un botón. El plugin abre un escáner de pantalla completa nativo en iOS y Android, un escáner de navegador en la web, y devuelve el texto decodificado y el formato.
Esta guía construye una aplicación de escáner funcional en Ionic Angular en Capacitor 8, luego cubre la filtración de formatos, el manejo de errores, los motores de escaneo de Android, el escaneo continuo, los escáneres de hardware de empresa y las fallas comunes.
¿Qué vas a construir?
Una aplicación de Ionic de una sola página con un botón de escaneo que:
- abre el escáner de cámara
- muestra cada resultado con su formato
- te permite copiar o abrir el valor escaneado
- maneja errores de cancelación y permisos de manera limpia
El code utiliza componentes de Angular independientes. Las llamadas al plugin son idénticas en React o Vue, por lo que solo cambia la sintaxis de la plantilla.
Requisitos previos
- Node.js LTS y Bun.
- Para Android: Android Studio con un SDK y un dispositivo o emulador con una cámara.
- Para iOS: un Mac con Xcode 26, que App Store Connect requiere para subir desde abril de 2026.
- Un dispositivo físico es lo mejor. Los emuladores pueden escanear con una cámara virtual, pero la iluminación y el enfoque reales importan para los códigos de barras.
Step 1: Crea la aplicación Ionic
bunx @ionic/cli start barcode-scanner blank --type=angular-standalone --capacitor
cd barcode-scanner
La Ionic CLI configura la aplicación Angular con @capacitor/core y @capacitor/cli ya instalado. Verifica capacitor.config.ts: appId debe ser tu ID de dominio inverso, por ejemplo com.example.scanner, y webDir debe coincidir con la salida de compilación (www para Ionic Angular).
Construye una vez para que las plataformas nativas tengan algo para copiar:
bun run build
Step 2: Agrega Android e iOS
bun add @capacitor/android @capacitor/ios
bunx cap add android
bunx cap add ios
Capacitor 8 crea nuevos proyectos de iOS con Swift Package Manager. Si está actualizando una aplicación más antigua, siga las instrucciones de actualización. Capacitor 8 guía de actualización primero.
Step 3: Instale el plugin de escáner de códigos de barras
bun add @capacitor/barcode-scanner
bunx cap sync
@capacitor/barcode-scanner Es el escáner oficial del equipo de Capacitor. La versión 3.x admite Capacitor 8.
Configuración de Android
El plugin requiere Android API 26 o superior. Capacitor 8 se establece en 24, por lo que edita android/variables.gradle:
ext {
minSdkVersion = 26
// keep the other values Capacitor generated
}
On Android you can choose the decoding engine per scan: ZXing (default, supports every format the plugin lists) or Google ML Kit (fast on difficult codes, but no MaxiCode, RSS-14, RSS Expanded, or UPC/EAN extension).
Configuración de iOS
Agregue una descripción de uso de la cámara ios/App/App/Info.plist:
<key>NSCameraUsageDescription</key>
<string>The camera is used to scan barcodes and QR codes.</string>
Without it, iOS terminates the app on the first camera access. Write the sentence for the user, since App Review reads it too. iOS uses Apple’s Vision framework, which supports every listed format except MaxiCode and UPC/EAN extension, and does not distinguish UPC-A from EAN-13.
Paso 4: Escribe el servicio de escaneo
Para mantener las llamadas a plugins en un servicio, las hace fácil de reutilizar y probar.
// src/app/services/scanner.service.ts
import { Injectable } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import {
CapacitorBarcodeScanner,
CapacitorBarcodeScannerAndroidScanningLibrary,
CapacitorBarcodeScannerCameraDirection,
CapacitorBarcodeScannerScanOrientation,
CapacitorBarcodeScannerTypeHint,
CapacitorBarcodeScannerTypeHintALLOption,
} from '@capacitor/barcode-scanner';
export interface ScanResult {
value: string;
format: string;
scannedAt: Date;
}
export type ScanOutcome =
| { status: 'ok'; result: ScanResult }
| { status: 'cancelled' }
| { status: 'denied' }
| { status: 'error'; message: string };
@Injectable({ providedIn: 'root' })
export class ScannerService {
async scan(onlyQr = false): Promise<ScanOutcome> {
try {
const { ScanResult, format } = await CapacitorBarcodeScanner.scanBarcode({
hint: onlyQr
? CapacitorBarcodeScannerTypeHint.QR_CODE
: CapacitorBarcodeScannerTypeHintALLOption.ALL,
scanInstructions: 'Point the camera at a barcode',
cameraDirection: CapacitorBarcodeScannerCameraDirection.BACK,
scanOrientation: CapacitorBarcodeScannerScanOrientation.ADAPTIVE,
cancelButtonAccessibilityLabel: 'Cancel scanning',
torchButtonOnAccessibilityLabel: 'Turn flashlight off',
torchButtonOffAccessibilityLabel: 'Turn flashlight on',
android: {
scanningLibrary: CapacitorBarcodeScannerAndroidScanningLibrary.MLKIT,
},
web: {
showCameraSelection: true,
},
});
return {
status: 'ok',
result: {
value: ScanResult,
format: CapacitorBarcodeScannerTypeHint[format] ?? String(format),
scannedAt: new Date(),
},
};
} catch (err: any) {
const code: string = err?.code ?? '';
if (code.endsWith('0006')) return { status: 'cancelled' };
if (code.endsWith('0007')) return { status: 'denied' };
return { status: 'error', message: err?.message ?? 'Scan failed' };
}
}
get platform() {
return Capacitor.getPlatform();
}
}
El plugin devuelve errores con códigos en la forma OS-PLUG-BARC-XXXX. En iOS, 0006 significa que el usuario canceló y 0007 significa que se denegó el acceso a la cámara. Trata a cancel como un resultado normal, no como un error. Registra el valor raw code en Android durante la prueba para confirmar la misma correspondencia en tus dispositivos objetivo.
El format en el resultado es un valor numérico de enumeración. CapacitorBarcodeScannerTypeHint[format] Lo convierte nuevamente en un nombre como QR_CODE o EAN_13.
Paso 5: Construye la página
// src/app/home/home.page.ts
import { Component, inject, signal } from '@angular/core';
import { DatePipe } from '@angular/common';
import {
IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem, IonLabel,
IonFab, IonFabButton, IonIcon, IonToggle, IonNote, ToastController,
} from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { scanOutline } from 'ionicons/icons';
import { ScannerService, ScanResult } from '../services/scanner.service';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
imports: [
DatePipe, IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonItem,
IonLabel, IonFab, IonFabButton, IonIcon, IonToggle, IonNote,
],
})
export class HomePage {
private scanner = inject(ScannerService);
private toast = inject(ToastController);
results = signal<ScanResult[]>([]);
onlyQr = signal(false);
scanning = signal(false);
constructor() {
addIcons({ scanOutline });
}
async scan() {
if (this.scanning()) return;
this.scanning.set(true);
const outcome = await this.scanner.scan(this.onlyQr());
this.scanning.set(false);
if (outcome.status === 'ok') {
this.results.update((list) => [outcome.result, ...list]);
} else if (outcome.status === 'denied') {
await this.show('Camera access is off. Enable it in Settings to scan.');
} else if (outcome.status === 'error') {
await this.show(outcome.message);
}
}
private async show(message: string) {
const t = await this.toast.create({ message, duration: 3000 });
await t.present();
}
}
<!-- src/app/home/home.page.html -->
<ion-header>
<ion-toolbar>
<ion-title>Barcode Scanner</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-toggle [checked]="onlyQr()" (ionChange)="onlyQr.set($event.detail.checked)">
QR codes only
</ion-toggle>
</ion-item>
@for (r of results(); track r.scannedAt) {
<ion-item>
<ion-label>
<h2>{{ r.value }}</h2>
<p>{{ r.format }} · {{ r.scannedAt | date: 'shortTime' }}</p>
</ion-label>
</ion-item>
} @empty {
<ion-item lines="none">
<ion-note>No scans yet. Tap the button to scan.</ion-note>
</ion-item>
}
</ion-list>
<ion-fab slot="fixed" vertical="bottom" horizontal="end">
<ion-fab-button (click)="scan()" [disabled]="scanning()" aria-label="Scan a barcode">
<ion-icon name="scan-outline" aria-hidden="true"></ion-icon>
</ion-fab-button>
</ion-fab>
</ion-content>
Paso 5: Construye la página
bun run build
bunx cap sync
bunx cap run android
bunx cap run ios
Para la versión web, bun run start Sirve la aplicación en localhost, donde los navegadores permiten el acceso a la cámara. Sobre una IP de red local necesitas HTTPS.
Para iterar más rápido, utiliza el recarga en vivo en un dispositivo: bunx cap run android --livereload --external recarga la aplicación cada vez que guardas.
Formatos soportados
| Formato | Android ZXing | Kit de desarrollo de Android ML | iOS |
|---|---|---|---|
| QR Code, Azteco, Data Matrix, PDF417 | Sí | Sí | Sí |
| EAN-13, EAN-8, UPC-A, UPC-E | Sí | Sí | Sí (UPC-A y EAN-13 tratados de la misma manera) |
| Code 128, Code 39, Code 93, Codabar, Código ITF | Sí | Sí | Sí |
| RSS-14, RSS Expandido | Sí | No | Sí |
| MaxiCode | Sí | No | No |
| Extensión UPC/EAN | Sí | No | No |
Si pasas un formato no soportado como hintManipulación de valores escaneados de manera segura
Manejo de valores escaneados de manera segura
Un código de barras es una entrada no confiable. Antes de actuar sobre él:
- Verifique las URL antes de abrirlas y muestre el dominio al usuario primero.
- No ejecute cadenas escaneadas como code o inyecte el contenido en HTML.
- Valida el dígito de control antes de llamar a tu backend.
- Limitar consultas de backend si los usuarios pueden escanear rápidamente.
Más allá del escáner básico
scanBarcode is a one-shot, full-screen flow. That covers most apps. Other needs call for other tools:
- Escaneo continuo en su propio UI. Run a camera feed behind your web UI with @capgo/preview de cámara, capturar frames con
captureSample, y decodificarlos en JavaScript con una biblioteca comozxing-wasm. - Dispositivos de empresa robustos. Zebra handhelds have hardware scanners. @capgo/capacitor-zebra-datawedge gestiona perfiles y disparadores de escaneo de DataWedge, lo que es más rápido y confiable que la cámara en esos dispositivos.
- Captura de documentos. Para recibos y formularios de papel, un plugin de escáner de documentos se ajusta mejor que un lector de códigos de barras. Ver el directorio de plugins.
¿Vienes de Cordova? Nuestra guía del lector de códigos de barras de Cordova cubre los antiguos plugins y un camino de migración.
Solución de problemas
iOS app closes when scanning starts. NSCameraUsageDescription is missing from Info.plist.
El compilado de Android falla con un error de fusión de manifiesto o minSdk. minSdkVersion está todavía en 24. Establezca en 26 en android/variables.gradle y sincronice.
El escáner se abre pero nunca lee el code. Pruebe el otro motor de Android, mejore la iluminación, utilice la linterna y restrinja hint A la forma en que esperas. Códigos muy pequeños o dañados requieren la cámara más cerca.
Se deniega el permiso cada vez. El usuario denegó el acceso a la cámara anteriormente. Muestre un mensaje que explique cómo habilitarlo en la configuración del sistema. El sistema no mostrará el prompt de nuevo.
El escáner web no arranca. La página no se sirve sobre HTTPS o localhost, o otra pestaña tiene la cámara.
Se ignora la configuración de orientación en tabletas. Comenzando con aplicaciones que se dirigen a Android SDK 36, Android 16 ignora orientaciones fijas en pantallas grandes. Utilice ADAPTIVE.
Envíe correcciones sin esperar a la revisión
Once the scanner is live, most changes, such as new validation rules, UI tweaks, or a different backend lookup, are web code. Actualizaciones en vivo de Capgo pushenlas a aplicaciones instaladas en minutos, mientras se actualizan los plugins y Info.plist Cambios aún pasan por las tiendas. Cuando necesites una nueva compilación nativa sin una configuración de Mac local, Capgo Construcción genera binarios firmados iOS y Android en la nube.
Wrap-up
Un escáner de códigos de barras de Ionic en Capacitor 8 requiere un plugin oficial, dos cambios de configuración nativa (Android) minSdkVersion 26 y la cadena de uso de la cámara de iOS (también scanBarcode call. Restrict formats when you can, treat cancel and permission errors as normal outcomes, validate everything you scan, and reach for a camera preview or DataWedge when the one-shot flow is not enough.