§ NOTA TÉCNICA · WEB NFC

Web NFC en producción: limitaciones reales

En aGo QR experimentamos con la Web NFC API para escribir tags físicos desde el navegador. Funciona bien, pero solo en un rincón muy concreto del ecosistema: Chrome sobre Android, contexto HTTPS, gesto del usuario y un tag virginal en la mano. Estas son nuestras notas de lo que aprendimos llevándolo a producción, con código real y limitaciones honestas.

El contexto: por qué queríamos NFC en una tool cliente puro

aGo QR genera códigos QR sin servidor. Una extensión natural es generar también enlaces impresos en stickers o tarjetas NFC. Un cliente nos pidió un flujo donde el usuario escribe una URL al tag desde el mismo navegador, sin instalar nada. El producto era una mesa de atención presencial: el operador apoya el teléfono sobre la tarjeta y carga la URL del cliente.

El requisito era el mismo de cualquier tool de aGo lab: cero servidor, cero envío de datos del usuario, todo en el cliente. Web NFC encaja con esa filosofía porque la API expone directamente el chip NFC del dispositivo sin intermediarios remotos.

Antes de implementar evaluamos tres caminos y elegimos el más simple. El resto de esta nota documenta la decisión, el código real y las limitaciones que descubrimos al testear con dispositivos reales.

Opciones evaluadas

Opción A · Web NFC API nativa

Usar NDEFReader directamente desde el navegador.

Pros: cero dependencias, control total, encaja con la promesa cliente puro.

Contras: solo Chrome Android, no iOS, no desktop, no Firefox.

Opción B · Librería externa tipo NDEFAdapter

Capa de abstracción que internamente usa Web NFC y promete polyfill para otros entornos.

Pros: API más amigable, fallbacks declarados en docs.

Contras: en la práctica el polyfill no existe. Solo envuelve la misma API nativa. Agrega bundle sin ganar compatibilidad.

Opción C · App móvil custom + dispositivo físico

Una app nativa Android o iOS con permisos completos para NFC, comunicada con la tool web por deep link.

Pros: funciona en iOS, soporte profundo, control granular.

Contras: rompe la filosofía cliente puro, requiere distribución por store, mantención continua.

La decisión: Web NFC nativa con detección de soporte

Elegimos la opción A. La razón es que el caso de uso era muy acotado (mesa de atención con teléfono Android Chrome conocido) y el costo de una app nativa no se justificaba para escribir una URL en un tag. La tool detecta el soporte al cargar y muestra un mensaje claro si el dispositivo no es compatible, sin romper el resto del flujo.

En aGo lab tratamos Web NFC como un progressive enhancement: si está disponible se habilita el botón "Escribir tag NFC"; si no, el usuario puede igual generar el QR e imprimirlo, que es el flujo principal de aGo QR. Esto evita que el 80% del público (iPhone, desktop, Firefox) choque con una pantalla rota.

Implementación: código real

El siguiente componente React es una versión simplificada del que está hoy en aGo QR. Ilustra los tres puntos críticos: detección de soporte, escritura con manejo de errores y feedback al usuario. Está pensado para hidratarse como isla con client:visible.

import { useEffect, useState } from 'react';

type SoporteEstado = 'desconocido' | 'soportado' | 'no-soportado';
type EscrituraEstado = 'inactivo' | 'esperando-tag' | 'escribiendo' | 'exito' | 'error';

interface NfcWriteResult {
  ok: boolean;
  mensaje: string;
}

export default function NfcWriter({ url }: { url: string }) {
  const [soporte, setSoporte] = useState<SoporteEstado>('desconocido');
  const [estado, setEstado] = useState<EscrituraEstado>('inactivo');
  const [mensaje, setMensaje] = useState('');

  useEffect(() => {
    // Detección de soporte. NDEFReader solo existe en Chrome Android sobre HTTPS.
    if (typeof window !== 'undefined' && 'NDEFReader' in window) {
      setSoporte('soportado');
    } else {
      setSoporte('no-soportado');
    }
  }, []);

  async function escribirTag(): Promise<NfcWriteResult> {
    // Validación previa: si NDEFReader no existe, abortar limpio.
    if (!('NDEFReader' in window)) {
      return { ok: false, mensaje: 'Tu navegador no soporta Web NFC.' };
    }

    try {
      // @ts-expect-error NDEFReader aún no está en los tipos estándar de TypeScript.
      const writer = new NDEFReader();

      // El método write() inicia la espera del tag físico. Debe llamarse
      // desde un handler de evento originado por el usuario (click, touch).
      await writer.write({
        records: [
          { recordType: 'url', data: url }
        ]
      });

      return { ok: true, mensaje: 'Tag escrito correctamente.' };
    } catch (err) {
      // Errores comunes documentados:
      // NotAllowedError: el usuario no concedió permiso o no hubo user gesture.
      // NotSupportedError: el tag físico no acepta escritura o el chip no es compatible.
      // AbortError: el usuario canceló la operación antes de acercar un tag.
      // NetworkError: tag perdido durante la escritura.
      const error = err as Error;
      const tipo = error.name ?? 'Error';
      const detalle = error.message ?? 'Sin detalle.';

      const mensajes: Record<string, string> = {
        NotAllowedError: 'El navegador no concedió permiso. Verifica que abriste el botón con un toque.',
        NotSupportedError: 'El tag físico no acepta escritura o está bloqueado.',
        AbortError: 'Cancelaste antes de acercar el tag. Vuelve a intentar.',
        NetworkError: 'Se perdió el contacto con el tag durante la escritura.'
      };

      return { ok: false, mensaje: mensajes[tipo] ?? `Error ${tipo}: ${detalle}` };
    }
  }

  async function handleClick() {
    setEstado('esperando-tag');
    setMensaje('Acerca el teléfono al tag NFC y mantenlo quieto.');

    const resultado = await escribirTag();

    if (resultado.ok) {
      setEstado('exito');
      setMensaje(resultado.mensaje);
    } else {
      setEstado('error');
      setMensaje(resultado.mensaje);
    }
  }

  if (soporte === 'desconocido') {
    return <p>Detectando soporte de Web NFC...</p>;
  }

  if (soporte === 'no-soportado') {
    return (
      <div className="nfc-aviso">
        <p>
          Web NFC solo funciona hoy en Chrome sobre Android con HTTPS. Tu dispositivo o
          navegador no la expone. Puedes seguir usando el QR generado más arriba; cualquier
          lector NFC físico con app dedicada permite escribir el tag desde ese código.
        </p>
      </div>
    );
  }

  return (
    <div className="nfc-writer">
      <button
        type="button"
        onClick={handleClick}
        disabled={estado === 'esperando-tag' || estado === 'escribiendo'}
      >
        {estado === 'inactivo' ? 'Escribir URL en tag NFC' : 'Reintentar'}
      </button>
      {mensaje && <p className={`nfc-msg nfc-${estado}`}>{mensaje}</p>}
    </div>
  );
}

Tres detalles del código que merecen mención. Primero, la detección de soporte está dentro de useEffect y no en el render directo, porque en build estático Astro evalúa el componente en Node y window no existe. Segundo, el botón se deshabilita durante la espera del tag para evitar dobles clicks que inicien una segunda operación mientras la primera sigue viva. Tercero, los mensajes de error están normalizados a un diccionario porque las cadenas crudas del navegador son poco amigables y a veces vienen en inglés dependiendo del idioma del sistema.

Limitaciones honestas que descubrimos

Después de varias horas con tags reales en la mano, estas son las paredes que existen:

1. Solo Chrome Android, en serio

Web NFC vive como propuesta de la WICG. Edge Android la heredó porque comparte motor con Chrome, pero Firefox Android no la implementa y no figura en su roadmap público al 2026-05-24. Safari iOS no la expone y Apple no ha anunciado intención de hacerlo. En desktop ningún navegador la incluye, ni siquiera con chips NFC presentes en algunos portátiles. Si tu producto necesita NFC fuera de Chrome Android, planea ruta nativa.

2. HTTPS obligatorio sin excepciones reales

El objeto NDEFReader ni siquiera se expone en HTTP plano. En desarrollo Chrome considera localhost como contexto seguro, lo que te permite iterar local sin certificado. Para staging usa Cloudflare Tunnel, ngrok con HTTPS, o un certificado local con mkcert. Subir a producción sin HTTPS no es opción.

3. User gesture obligatorio para write()

Una llamada a writer.write() fuera de un handler iniciado por el usuario falla con NotAllowedError. En la práctica eso significa que no puedes precargar una escritura ni iniciarla desde un setTimeout. La operación nace de un click, touchend o keydown, y dispara la espera del tag físico. Si el usuario tarda demasiado en acercar el tag, el navegador eventualmente cancela.

4. No se puede testear en simulador

Los emuladores Android no proveen chip NFC virtual. La única manera de validar el flujo es con un teléfono físico con NFC habilitado y un tag virginal en la mano. En aGo lab tenemos una caja con tags NTAG213, NTAG215 y NTAG216 etiquetados para este propósito. Sin esa caja, cualquier prueba es teórica.

5. Tags bloqueables y formato NDEF

Muchos tags vienen con la posibilidad de lock permanente. Una vez bloqueados, ningún write futuro funciona. Si compras tags en volumen, pide al proveedor confirmación de que vienen sin lock y verifica con uno antes de procesar el resto. El formato NDEF también impone overhead por record: una URL de 50 caracteres puede ocupar 70 bytes reales en el tag después del encoding.

6. La permiso prompt es agresivo

La primera vez que el navegador encuentra una operación NFC, Chrome muestra un permiso modal estándar. Si el usuario lo rechaza, futuras llamadas fallan silenciosamente con NotAllowedError hasta que reinicie permisos del sitio. En aGo QR documentamos este paso en el flujo: si la primera escritura falla, mostramos un enlace al panel de permisos del sitio.

Cuándo NO usar Web NFC

  • Producto target iOS. Apple no expone NDEFReader en Safari. Si la mayoría de tus usuarios son iPhone, la inversión en Web NFC paga muy poco. Considera ruta nativa con Capacitor o React Native.
  • Entorno corporativo con dispositivos legacy. Muchas empresas distribuyen teléfonos Android viejos sin actualizaciones recientes de Chrome. Verifica antes de asumir compatibilidad. Web NFC requiere Chrome 89 o superior y un chip NFC físico habilitado.
  • Operaciones masivas batch. Web NFC procesa un tag a la vez con espera de usuario. Si necesitas grabar 500 tags por hora, una estación dedicada con lector USB NFC y software desktop rinde mucho más que un teléfono.
  • Casos con requerimientos de seguridad o auditoría estrictos. Web NFC no ofrece firmas criptográficas ni logs detallados por defecto. Si tu caso lo exige, mira tags con soporte NTAG424 DNA o equivalente y software dedicado.

Lecciones que nos llevamos

La principal lección práctica fue invertir tiempo en el fallback antes que en el happy path. La detección de soporte y el mensaje claro para iPhone son la diferencia entre una tool útil y una tool que se siente rota para la mitad de los visitantes. El happy path en Chrome Android es relativamente sencillo una vez que el flujo de permisos y user gesture están claros.

La segunda lección es presupuestar tiempo para testing con dispositivos físicos. En aGo lab dedicamos una tarde completa a probar con tres modelos de tag y dos modelos de teléfono antes de declarar la feature lista. Saltarse ese paso es la receta para descubrir en producción que el chip de tu cliente es justo el que NDEFReader no reconoce.

La tercera lección, más estratégica: Web NFC es un complemento, no un producto. Como feature secundaria en aGo QR sirve muy bien. Como producto principal expondría a un rincón muy estrecho del público y la mantención del happy path no compensaría el churn de los rechazos.

Recursos consultados

  • Especificación WICG de Web NFC, versión draft al 2026.
  • Documentación de Chrome Developers sobre NDEFReader y casos de uso comunes.
  • can-i-use al 2026-05-24 confirmando ausencia de soporte en iOS, Firefox y desktop.
  • Pruebas internas en aGo lab con NTAG213, NTAG215, NTAG216 y dos modelos Android.
  • Documentación de la WICG sobre user gestures y contextos seguros.
  • Issue tracker de Chromium para limitaciones específicas de cada chip.
  • Repositorio público de ejemplos de Google Chrome Labs sobre NDEFReader.

Cinco preguntas que recibimos y la respuesta corta

¿Puedo escribir tags BLE con la misma API?

No. Web NFC es solo para tags NFC pasivos. Para BLE existe Web Bluetooth, una API distinta con su propio set de limitaciones (también solo Chrome, también HTTPS, también user gesture, pero con un alcance distinto y un permiso más complejo).

¿Cómo manejas el formato del record si el usuario quiere texto y URL?

Un mismo NDEF message puede contener múltiples records. En la práctica, los lectores nativos del sistema priorizan el primer record que entiendan. Si pones URL primero, Android abrirá esa URL al hacer tap. Si pones texto primero, mostrará el texto en una notificación. Define el orden según el caso de uso.

¿Y si el usuario quiere leer el tag, no escribirlo?

NDEFReader también ofrece scan(). El flujo es similar pero menos restrictivo: no requiere user gesture en algunos contextos, aunque sí permisos. Para lectura los tags no necesitan ser virginales. En aGo lab no implementamos lectura porque el caso de uso del cliente era solo escritura.

¿Cuánto cuesta un tag NFC?

Al 2026-05-24 un tag NTAG213 ronda los 50 a 150 pesos chilenos por unidad en compras sobre 100 unidades. NTAG216 ronda los 250 a 500 pesos. Los formatos sticker con adhesivo listo son ligeramente más caros que los discos pelados. Verifica con tu proveedor local.

¿Pierdo accesibilidad al ofrecer NFC?

No, mientras NFC sea un complemento. El QR generado por la tool sigue siendo accesible con cualquier lector de cámara, incluido VoiceOver y TalkBack. Si NFC fuera la única ruta de uso, sí excluirías a usuarios con dispositivos sin chip o con discapacidades motoras para hacer el gesto de acercamiento. En aGo QR el QR sigue siendo ciudadano de primera clase y NFC es opcional.

¿Quieres probarlo?

Genera un código QR sin servidor en aGo QR. La integración NFC vive donde aplica.

Abrir aGo QR

¿Necesitas un sistema a medida?

aGo lab construye software para empresas con foco en privacidad y cumplimiento. Si tu caso necesita NFC con app nativa, integraciones específicas o un flujo completo, conversemos.

Conversemos

§ CÓMO CITAR ESTE ARTÍCULO

aGo lab. (2026). "Web NFC API en producción, estado real en Chrome Android". tools.ago.cl/notas/web-nfc-produccion. Recuperado el 2026-05-25.

Esta nota es contenido propio de aGo lab y se publica bajo política de atribución requerida. Si la citas o referencias, incluye el link a la URL original.

§ aGo lab estudio

¿Quieres que aGo lab implemente esto para ti?

Si esta decisión técnica te resuelve un problema y necesitas aplicarla a un sistema propio, conversemos.

§ COMMAND

↑↓ navega · Enter abre · Esc cierra