Saltar al contenido
AZ Tools

Normalización Unicode y caracteres invisibles

El texto que parece igual no siempre lo es. Unicode permite construir un mismo carácter visible de varias formas, admite caracteres que no dibujan nada y contiene parejas indistinguibles en pantalla pero distintas por dentro. Esta guía explica de dónde vienen esas diferencias y qué hacer con ellas antes de que se conviertan en un inicio de sesión fallido, un registro duplicado o un dominio suplantado.

Puntos de código, bytes y qué significa "longitud"

Una cadena tiene al menos tres longitudes plausibles, y no coinciden. "héllo" puede ser 5 caracteres para quien lee, 5 o 6 puntos de código según cómo esté construida la é, y 6 o 7 bytes en UTF-8. Los emoji amplían la brecha: un emoji de familia es una sola imagen ensamblada a partir de varias personas unidas por conectores invisibles, y la mayoría de los lenguajes informan de su longitud como algo entre 2 y 11.

Elige la unidad que responde a tu pregunta. La longitud en bytes es lo que miden el límite de una columna y una cabecera HTTP. El recuento de puntos de código es lo que devuelven casi todas las API de cadenas. Lo que una persona entiende por "caracteres" es el número de grupos de grafemas — lo que mueve el cursor un paso — y que un recorte caiga en esa frontera es la diferencia entre un corte limpio y un emoji roto.

El mismo texto, dos codificaciones: NFC y NFD

"é" puede ser un único punto de código (U+00E9) o dos: una "e" simple seguida de un acento agudo combinante (U+0301). Se dibujan igual, pero son secuencias distintas, así que una comprobación de igualdad ingenua dice que son cadenas diferentes, un índice único de base de datos acepta las dos y una búsqueda de una no encuentra la otra. Normalizar es elegir una forma canónica: NFC compone en el punto de código único, NFD descompone en base más marca.

No es un caso exótico. El texto escrito en una plataforma y pegado desde otra discrepa a diario — macOS ha almacenado históricamente los nombres de archivo descompuestos mientras que la mayor parte de la web envía la forma compuesta — y el hangul coreano tiene la misma división entre sílabas precompuestas y jamo separados. NFC es el valor por defecto correcto para almacenar y transmitir; lo importante es elegir uno y aplicarlo en el borde, no espolvorearlo allí donde una comparación falle.

NFKC y NFKD: cuando "casi igual" debe contar como igual

Las formas con K pliegan también las diferencias de compatibilidad. Con NFKC, la ligadura fi se convierte en "fi", la A de ancho completo en "A", el superíndice ² en "2" y el espacio duro en uno normal. Es justo lo que quieres al comparar identificadores, nombres de usuario o términos de búsqueda, donde dos grafías no deberían poder reclamar la misma plaza.

Y es justo lo que no quieres para mostrar o almacenar texto arbitrario, porque la transformación pierde información y es irreversible: las letras matemáticas estilizadas se derrumban en ASCII llano y el formato que eligió el autor desaparece. Usa las formas con K para derivar una clave de comparación y conserva el original para devolvérselo al usuario.

Los caracteres que no puedes ver

Muchos puntos de código no dibujan nada. Una marca de orden de bytes al principio de un archivo rompe la primera clave de un documento JSON o la primera cabecera de un CSV. Los espacios y unidores de ancho cero sobreviven a un copiar y pegar desde un documento y rompen en silencio una coincidencia exacta. Los espacios duros parecen espacios pero no separan al dividir por espacios en blanco, y los guiones suaves solo aparecen cuando la palabra se parte al final de la línea.

Algunos son peores que un desorden. Los caracteres de anulación bidireccional pueden reordenar cómo se muestra el código fuente sin cambiar lo que lee el compilador — el truco "Trojan Source", en el que lo que en pantalla es un comentario es código ejecutable por debajo. Y los homoglifos — la а cirílica, la ο griega, las letras de ancho completo — permiten que un dominio o un nombre de usuario se vea exactamente igual que uno conocido. Si una cadena cruza una frontera de confianza, inspecciona sus puntos de código en lugar de fiarte de tus ojos.

Reglas que mantienen el texto a salvo

Normaliza en el borde, una sola vez. Convierte el texto entrante a NFC al entrar en el sistema y guárdalo así; a partir de ahí las comparaciones internas comparan lo mismo con lo mismo. Para cualquier cosa usada como identidad — nombres de usuario, parte local del correo, slugs, claves de búsqueda — deriva una clave de comparación aparte con NFKC más plegado de mayúsculas, y exige unicidad sobre esa clave, no sobre el valor que se muestra.

Después decide, explícitamente, qué permites. Quita la marca de orden de bytes, rechaza o elimina los caracteres de ancho cero y los controles bidireccionales en campos que no tienen por qué contenerlos, y trata un identificador con escrituras mezcladas como sospechoso antes que como ingenioso. Nada de esto es caro, y todo junto sale mucho más barato que depurar por qué un usuario concreto no puede entrar con una contraseña que escribe correctamente.

  • NFC para almacenar y transmitir; NFKC + plegado de mayúsculas para claves de comparación.
  • Normaliza una vez en el borde, no en cada comparación.
  • Quita la BOM antes de analizar JSON, CSV o archivos de configuración.
  • Inspecciona los puntos de código cuando una cadena se ve bien pero se comporta mal.

Herramientas relacionadas