Crear APK gratis

API del puente de JavaScript

Escáner de QR y códigos de barras

La pantalla del escáner de Google con una sola promesa, AppMint.scanCode(): sin permiso de cámara, y con la URL, la red Wi-Fi, el teléfono o el contacto ya interpretados para los códigos QR comunes.

AppMint.scanCode ayudante#

AppMint.scanCode(options)

Ejemplo

Abre la pantalla del escáner de códigos QR y de barras de Google y te da lo escaneado. Una sola llamada: sin permiso de cámara, sin <video>, sin interfaz propia.

Devuelve: una Promise que siempre se resuelve: {ok: true, value, displayValue, format, type, …} o {ok: false, code, message}. Requiere: nada que activar. El teléfono necesita Google Play services.

Cualquier código:

async function scan() {
  if (!(window.AppMint && AppMint.scanCode)) { alert('Open this page in the app to scan'); return; }
  var r = await AppMint.scanCode();
  if (!r.ok) {
    if (r.code === 'cancelled') return;                          // the user closed the scanner
    if (r.code === 'unavailable') alert('The scanner is not available on this phone');
    else if (r.code === 'busy') alert('A scan is already open');
    else alert('Scan failed: ' + r.message);                     // 'failed'
    return;
  }
  console.log(r.value, r.format, r.type);   // "TICKET-4821", "qr_code", "text"
}

Solo algunos formatos, y una opción de "escríbelo" para etiquetas dañadas:

var r = await AppMint.scanCode({
  formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e'],   // shop barcodes
  manualInput: true,                                // lets the user type the code
  autoZoom: true                                    // default true
});
if (r.ok) lookUpProduct(r.value);

Partes ya hechas: solo está presente la parte que coincide con r.type:

var r = await AppMint.scanCode({ formats: ['qr_code'] });
if (r.ok) {
  if (r.type === 'url')     openLink(r.url.url);                      // url: { url, title }
  if (r.type === 'wifi')    showWifi(r.wifi.ssid, r.wifi.password, r.wifi.encryption); // 'wpa'|'wep'|'open'
  if (r.type === 'phone')   callNumber(r.phone);                      // a string
  if (r.type === 'email')   writeMail(r.email.address, r.email.subject, r.email.body);
  if (r.type === 'sms')     writeSms(r.sms.phoneNumber, r.sms.message);
  if (r.type === 'geo')     showMap(r.geo.lat, r.geo.lng);
  if (r.type === 'contact') addContact(r.contact.name, r.contact.organization, r.contact.phones, r.contact.emails);
}

Notas: nombres de formato: qr_code, aztec, codabar, code_39, code_93, code_128, data_matrix, ean_8, ean_13, itf, pdf417, upc_a, upc_e (los nombres desconocidos se ignoran; ninguno = todos los formatos). type es uno de url, wifi, text, phone, email, sms, geo, contact, calendar, isbn, product, driver_license, unknown. Códigos de error: cancelled (cerrado, también con Atrás), unavailable (sin Play services, o no se pudo descargar el escáner), busy (ya hay un escaneo abierto), failed. El primer escaneo en un teléfono puede tardar unos segundos mientras Play services descarga el escáner. Las apps creadas con IA usan scanCode() de @/lib/appmintNative, que también escanea en la web.

AppMint.textRecognition ayudante#

AppMint.textRecognition.recognize(image, {script}) / prepare(script, onProgress)

Ejemplo

Lee texto en scripts distintos del latino: devanagari (hindi, maratí, nepalí…), chino, japonés y coreano, o 'auto', que prueba cada script y se queda con la lectura que tenga más caracteres. La misma opción funciona en el detector estándar: new TextDetector({ script: 'devanagari' }). Sin la opción, todo se lee como latino exactamente igual que antes.

Devuelve: recognize() → una Promise de {script, text, blocks: [{text, box, corners, lines}]} (script = el usado); se rechaza con un DOMException: NotSupportedError para un script desconocido o sin Google Play services, OperationError en otro caso. prepare() → una Promise de {script, status: 'ready'}. Requiere: Google Play services. El reconocedor de cada script es una descarga de Play services: los scripts que nombra tu código se instalan con la app, y cualquier otro en el primer uso.

Texto en hindi a partir de una foto:

document.getElementById('photo').addEventListener('change', async function (e) {
  var file = e.target.files[0];
  if (!file || !(window.AppMint && AppMint.textRecognition)) return;
  try {
    var r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
    document.getElementById('out').textContent = r.text;
  } catch (err) {
    alert('Could not read the photo: ' + err.name);
  }
});

Descarga primero el modelo de un script, con progreso: de lo contrario, el primer uso en un teléfono espera a la descarga:

async function getReady() {
  var bar = document.getElementById('model-progress');
  await AppMint.textRecognition.prepare('japanese', function (s) {
    if (s.progress != null) bar.value = s.progress;      // 0..1 while 'downloading'
  });
  bar.hidden = true;
}

// The same states arrive as events, also when a recognize() call starts a download:
window.addEventListener('appmint:ocr-model', function (e) {
  console.log(e.detail.script, e.detail.status, e.detail.progress);   // 'downloading' | 'ready' | 'failed'
});

Documentos mixtos: deja que la app elija entre los scripts que esperas:

var r = await AppMint.textRecognition.recognize(img, { script: 'auto', scripts: ['latin', 'chinese', 'korean'] });
console.log('read as ' + r.script + ': ' + r.text);

var lines = await new TextDetector({ script: 'korean' }).detect(img);   // standard shape, one entry per line

Notas: scripts es la lista de AppMint: latin, devanagari, chinese, japanese, korean. El árabe, el cirílico, el tailandés y el hebreo no tienen reconocedor en el dispositivo. WebToApk.__ocrRecognizeWith(requestId, base64, optionsJson) es el transporte interno; usa el ayudante.

__ocrAvailable puente#

window.WebToApk.__ocrAvailable(): Boolean

TextDetector / recognizeText(): Google Play services está presente (el hogar del módulo de OCR).

Ejemplo

Transporte interno detrás de TextDetector: indica si Google Play services (de donde viene el reconocedor) está en este teléfono. Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.

Devuelve: true o false. Cuando es false, TextDetector no está instalado en absoluto. Requiere: nada.

Usa la API pública:

if ('TextDetector' in window) scanButton.hidden = false;

Elige entre el flujo de escaneo y un formulario escrito una sola vez, al iniciar. Cuando el teléfono no tiene Google Play services, el detector simplemente no existe, así que la página debe ofrecer otra vía de entrada:

function setUpReceiptEntry() {
  var canScan = typeof window.TextDetector === 'function';
  document.getElementById('scan-receipt').hidden = !canScan;
  document.getElementById('type-receipt').hidden = canScan;
  if (!canScan) document.getElementById('entry-hint').textContent =
    'Text scanning is not available on this phone. Type the total below.';
}
document.addEventListener('DOMContentLoaded', setUpReceiptEntry);

Un detector que existe aún puede responder que el reconocimiento no está disponible en el momento de la llamada. Distingue los dos fallos por el nombre del error:

async function readLabel(img) {
  if (typeof window.TextDetector !== 'function') return null;
  try {
    var lines = await new TextDetector().detect(img);
    return lines.map(function (l) { return l.rawValue; }).join('\n');
  } catch (e) {
    if (e.name === 'NotSupportedError') showMessage('Text recognition is not available right now.');
    else showMessage('Could not read the label: ' + e.message);
    return null;
  }
}

Notas: implementado en el shell de la app (TextRecognitionBridge.kt).

__ocrRecognize puente#

window.WebToApk.__ocrRecognize(requestId: String, base64: String)

Reconoce el texto en [base64] (PNG/JPEG); la respuesta llega como `appmint:ocr {requestId, blocks|error}`.

Ejemplo

Transporte interno detrás de TextDetector.detect(): reconoce el texto en un PNG/JPEG. Las páginas usan la API estándar; nunca llames a esto directamente: los métodos __ pueden cambiar sin previo aviso.

Devuelve: nada; la respuesta llega como el evento appmint:ocr {requestId, blocks:[{text, box, corners, lines}]} o {requestId, error} (unavailable, bad-image, failed: …). Requiere: nada.

Usa la API pública:

const lines = await new TextDetector().detect(document.getElementById('receipt'));
console.log(lines.map((l) => l.rawValue));

Lee el texto de una foto que elige el usuario. detect() acepta un Blob/File además de un <img>, <canvas> o ImageData:

<input type="file" id="doc" accept="image/*" capture="environment">
<pre id="out"></pre>
<script>
document.getElementById('doc').addEventListener('change', async function (e) {
  var file = e.target.files[0];
  if (!file || typeof window.TextDetector !== 'function') return;
  try {
    var lines = await new TextDetector().detect(file);
    document.getElementById('out').textContent = lines.map(function (l) { return l.rawValue; }).join('\n');
  } catch (err) {
    document.getElementById('out').textContent = 'Could not read the photo (' + err.name + ')';
  }
});
</script>

Resalta cada línea reconocida sobre la imagen. boundingBox está en los píxeles propios de la imagen, así que escálalo al tamaño mostrado. La imagen debe haber terminado de cargarse, o detect() se rechaza con InvalidStateError:

async function highlightLines(img, overlay) {
  if (!img.complete) await new Promise(function (r) { img.onload = r; });
  var lines = await new TextDetector().detect(img);
  var sx = img.clientWidth / img.naturalWidth, sy = img.clientHeight / img.naturalHeight;
  overlay.innerHTML = '';
  lines.forEach(function (l) {
    var b = l.boundingBox, box = document.createElement('div');
    box.className = 'ocr-box';
    box.title = l.rawValue;
    box.style.cssText = 'position:absolute;left:' + b.x * sx + 'px;top:' + b.y * sy +
      'px;width:' + b.width * sx + 'px;height:' + b.height * sy + 'px';
    overlay.appendChild(box);
  });
}

Extrae un importe de un recibo. Una imagen de otro origen que contamina el canvas se rechaza con SecurityError, así que sirve la imagen desde el propio origen de la app:

async function findTotal(img) {
  try {
    var lines = await new TextDetector().detect(img);
    var hit = lines.find(function (l) { return /total/i.test(l.rawValue); });
    var m = hit && hit.rawValue.match(/(\d+[.,]\d{2})/);
    return m ? parseFloat(m[1].replace(',', '.')) : null;
  } catch (e) {
    if (e.name === 'SecurityError') console.warn('Image is from another origin; load it from the app instead.');
    else console.warn('OCR failed:', e.name, e.message);
    return null;
  }
}

Notas: implementado en el shell de la app (TextRecognitionBridge.kt).

__ocrRecognizeWith puente#

window.WebToApk.__ocrRecognizeWith(requestId: String, base64: String, optionsJson: String)

[__ocrRecognize] con opciones [optionsJson] `{script: 'latin'|'devanagari'|'chinese'|'japanese'|'korean'|'auto', scripts?}`; la respuesta `appmint:ocr` también indica el `script` usado.

Ejemplo

Documentado junto con AppMint.textRecognition: el ejemplo de allí también muestra este.

Lee texto en scripts distintos del latino: devanagari (hindi, maratí, nepalí…), chino, japonés y coreano, o 'auto', que prueba cada script y se queda con la lectura que tenga más caracteres. La misma opción funciona en el detector estándar: new TextDetector({ script: 'devanagari' }). Sin la opción, todo se lee como latino exactamente igual que antes.

Devuelve: recognize() → una Promise de {script, text, blocks: [{text, box, corners, lines}]} (script = el usado); se rechaza con un DOMException: NotSupportedError para un script desconocido o sin Google Play services, OperationError en otro caso. prepare() → una Promise de {script, status: 'ready'}. Requiere: Google Play services. El reconocedor de cada script es una descarga de Play services: los scripts que nombra tu código se instalan con la app, y cualquier otro en el primer uso.

Texto en hindi a partir de una foto:

document.getElementById('photo').addEventListener('change', async function (e) {
  var file = e.target.files[0];
  if (!file || !(window.AppMint && AppMint.textRecognition)) return;
  try {
    var r = await AppMint.textRecognition.recognize(file, { script: 'devanagari' });
    document.getElementById('out').textContent = r.text;
  } catch (err) {
    alert('Could not read the photo: ' + err.name);
  }
});

Descarga primero el modelo de un script, con progreso: de lo contrario, el primer uso en un teléfono espera a la descarga:

async function getReady() {
  var bar = document.getElementById('model-progress');
  await AppMint.textRecognition.prepare('japanese', function (s) {
    if (s.progress != null) bar.value = s.progress;      // 0..1 while 'downloading'
  });
  bar.hidden = true;
}

// The same states arrive as events, also when a recognize() call starts a download:
window.addEventListener('appmint:ocr-model', function (e) {
  console.log(e.detail.script, e.detail.status, e.detail.progress);   // 'downloading' | 'ready' | 'failed'
});

Documentos mixtos: deja que la app elija entre los scripts que esperas:

var r = await AppMint.textRecognition.recognize(img, { script: 'auto', scripts: ['latin', 'chinese', 'korean'] });
console.log('read as ' + r.script + ': ' + r.text);

var lines = await new TextDetector({ script: 'korean' }).detect(img);   // standard shape, one entry per line

Notas: scripts es la lista de AppMint: latin, devanagari, chinese, japanese, korean. El árabe, el cirílico, el tailandés y el hebreo no tienen reconocedor en el dispositivo. WebToApk.__ocrRecognizeWith(requestId, base64, optionsJson) es el transporte interno; usa el ayudante.

prepareTextRecognition puente#

window.WebToApk.prepareTextRecognition(script: String): String

Descarga con antelación el reconocedor de texto para [script]. Devuelve "checking" (luego `appmint:ocr-model {script, status: 'downloading'|'ready'|'failed', progress?}`), "unsupported-script" o "unavailable" (sin Google Play services).

Ejemplo

Descarga con antelación el reconocedor de texto en el dispositivo para un script, para que el primer recognize() no tenga que esperar. Las páginas normalmente llaman a AppMint.textRecognition.prepare(script), que lo envuelve.

Devuelve: una cadena al instante: "checking" (el resultado llega después como eventos appmint:ocr-model {script, status: 'downloading' | 'ready' | 'failed', progress?, error?}), "unsupported-script" o "unavailable" (sin Google Play services). 'auto' prepara todos los scripts. Requiere: Google Play services.

if (window.WebToApk && typeof window.WebToApk.prepareTextRecognition === 'function') {
  window.addEventListener('appmint:ocr-model', function (e) {
    if (e.detail.script !== 'chinese') return;
    if (e.detail.status === 'downloading') console.log('downloading', Math.round((e.detail.progress || 0) * 100) + '%');
    if (e.detail.status === 'ready') document.getElementById('scan').disabled = false;
    if (e.detail.status === 'failed') console.warn(e.detail.error);
  });
  var state = window.WebToApk.prepareTextRecognition('chinese');
  if (state !== 'checking') console.warn('Chinese text recognition: ' + state);
}

Prepara y luego escanea con el envoltorio:

async function readSign(canvas) {
  if (!(window.AppMint && AppMint.textRecognition)) return null;
  try {
    await AppMint.textRecognition.prepare('japanese');
    return await AppMint.textRecognition.recognize(canvas, { script: 'japanese' });
  } catch (err) {
    console.warn('Text recognition:', err.name, err.message);   // e.g. NotSupportedError without Play services
    return null;
  }
}

Los scripts no admitidos y la falta de Play services se indican en el valor devuelto:

const W = window.WebToApk;
if (W && typeof W.prepareTextRecognition === 'function') {
  const state = W.prepareTextRecognition('arabic');
  if (state === 'unsupported-script') showMessage('Arabic text cannot be read on the phone.');
  else if (state === 'unavailable') showMessage('Text recognition needs Google Play services.');
}

Notas: scripts: latin, devanagari, chinese, japanese, korean, auto. Un script que ya está en el teléfono responde ready de inmediato.

scanCode puente#

window.WebToApk.scanCode(optionsJson: String, callbackId: String)

Abre el escáner de códigos de Google; [optionsJson] `{formats?, autoZoom?, manualInput?}`. Resuelve la promesa AppMint.scanCode [callbackId].

Ejemplo

Mecanismo interno detrás de AppMint.scanCode(): las páginas usan ese ayudante de Promise, que abre el escáner de QR/códigos de barras de Google y se resuelve con el resultado.

Devuelve: nada directamente; el resultado resuelve la promesa AppMint.scanCode(). Requiere: nada que activar (sin permiso de cámara). El teléfono necesita Google Play services.

La API pública:

async function scanTicket() {
  if (!(window.AppMint && AppMint.scanCode)) { alert('Open this page in the app to scan'); return; }
  var r = await AppMint.scanCode({ formats: ['qr_code'] });
  if (r.ok) checkTicket(r.value);                  // r.format, r.type, r.displayValue…
  else if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')');
}

Conéctate a una red Wi-Fi desde un código QR impreso. Un código de Wi-Fi vuelve con type: 'wifi' y un objeto wifi:

async function scanWifi() {
  if (!(window.AppMint && AppMint.scanCode)) return;
  var r = await AppMint.scanCode({ formats: ['qr_code'] });
  if (!r.ok) { if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')'); return; }
  if (r.type !== 'wifi') { alert('That is not a Wi-Fi code.'); return; }
  showWifiCard(r.wifi.ssid, r.wifi.password, r.wifi.encryption);   // encryption: 'wpa' | 'wep' | 'open'
}

Busca un producto por el código de barras de la tienda, dejando que el usuario escriba los dígitos cuando la etiqueta esté dañada (manualInput). El primer escaneo en un teléfono puede descargar el escáner, lo que falla con unavailable si no hay conexión:

async function scanProduct() {
  if (!(window.AppMint && AppMint.scanCode)) { showManualEntry(); return; }
  var r = await AppMint.scanCode({ formats: ['ean_13', 'ean_8', 'upc_a', 'upc_e'], manualInput: true });
  if (r.ok) return lookUpProduct(r.value, r.format);            // e.g. '4006381333931', 'ean_13'
  if (r.code === 'busy') return;                                  // a scan is already open
  if (r.code === 'unavailable') alert('The scanner is not available: ' + r.message);
  else if (r.code !== 'cancelled') alert('Scan failed (' + r.code + ')');
}

Abre lo que señale un código QR, a partir de su type:

async function scanAndOpen() {
  var r = await AppMint.scanCode({ formats: ['qr_code'], autoZoom: true });
  if (!r.ok) return;
  if (r.type === 'url') location.href = r.url.url;
  else if (r.type === 'phone') location.href = 'tel:' + r.phone;
  else if (r.type === 'email') location.href = 'mailto:' + r.email.address;
  else if (r.type === 'geo') showOnMap(r.geo.lat, r.geo.lng);
  else if (r.type === 'contact') saveContact(r.contact.name, r.contact.phones, r.contact.emails);
  else showText(r.displayValue);
}

Notas: el método directo recibe optionsJson ({formats?, autoZoom?, manualInput?} como cadena JSON) y un callbackId; la respuesta se entrega a la tabla de respuestas interna del ayudante, así que una página que lo llame directamente no recibe respuesta. Solo se ejecuta un escaneo a la vez: una segunda llamada responde code: 'busy'. Consulta AppMint.scanCode para ver todos los campos del resultado y los códigos de error.

Generado a partir del runtime de la app y sus archivos de ejemplo en cada compilación de la documentación. Léelo como Markdown · Todas las familias.

Comprobado con el puente publicado el 2026-10-22.