Saltar al contenido principal

developer-tools

Unicode, UTF-8 y Codificación de Caracteres: Lo que Todo Desarrollador Debe Saber

robinhood-projects15 min de lectura

Codificar / Decodificar Base64

Codifica texto o datos binarios a Base64 y decodifica cadenas Base64 a texto plano, al instante en tu navegador.

Usar herramienta →

Visor, Formateador y Editor de JSON

Visualiza, edita y formatea JSON en un árbol interactivo en vivo. Edición inline de valores, renombrado de claves, agregar/eliminar nodos, soporte para comentarios JSONC y exportación con un clic — todo en tu navegador.

Usar herramienta →

Codificar / Decodificar URL

Codifica URLs con porcentaje para uso seguro en cadenas de consulta, o decodifica URLs codificadas a texto plano.

Usar herramienta →

Unicode, UTF-8 y Codificación de Caracteres: Lo que Todo Desarrollador Debe Saber

Copias un nombre de un formulario web y se guarda en tu base de datos como é. Abres un CSV en Excel y todos los caracteres acentuados están mal. Un usuario pega un emoji en un campo de texto y tu insert de MySQL falla con un error críptico. Añades un BOM a un archivo para que Excel lo lea correctamente, y ahora tu script de shell se rompe en la primera línea.

Estos son errores de codificación de caracteres. Se encuentran entre los problemas más desconcertantes que puede enfrentar un desarrollador porque los síntomas parecen aleatorios — la misma cadena puede mostrarse correctamente en un contexto y convertirse en basura en otro. La causa es casi siempre la misma: dos programas no están de acuerdo en cómo los bytes se mapean a caracteres.

Esta guía explica ese mapeo desde los fundamentos: qué son las codificaciones de caracteres, cómo funcionan Unicode y UTF-8, y cómo diagnosticar y solucionar los errores más comunes del mundo real.

La Era Pre-Unicode: ASCII y el Caos del ASCII Extendido

Cada carácter almacenado en un ordenador es en última instancia un número. Una codificación de caracteres es la tabla de consulta que mapea números a caracteres y viceversa.

La primera codificación ampliamente adoptada fue ASCII (American Standard Code for Information Interchange), publicada en 1963. ASCII usa 7 bits, dándole 128 ranuras (0–127). Eso cubre el alfabeto inglés en mayúsculas y minúsculas, los diez dígitos, la puntuación común y 33 caracteres de control no imprimibles como nueva línea y tabulación.

128 caracteres es suficiente para el inglés. No está ni cerca de ser suficiente para el francés, alemán, español, árabe, chino, japonés u otras miles de lenguas escritas en el mundo.

El Problema del ASCII Extendido

El 8.° bit de un byte estaba disponible, lo que significaba 128 ranuras más (128–255). Diferentes organizaciones y sistemas operativos se apresuraron a llenar esas ranuras de manera diferente:

  • Windows-1252 (también llamado CP1252): Usado por Microsoft Windows en Europa Occidental. Coloca caracteres como é, ñ y € en la mitad superior.
  • ISO-8859-1 (Latin-1): Un estándar internacional para idiomas de Europa Occidental. Se superpone significativamente con Windows-1252 pero no es idéntico — el rango 128–159 son caracteres de control en Latin-1 pero caracteres imprimibles en Windows-1252.
  • ISO-8859-5: Cubre el cirílico.
  • ISO-8859-6: Cubre el árabe.
  • Shift JIS, EUC-JP, Big5: Esquemas multi-byte completamente diferentes para idiomas del Este Asiático.

El resultado fue que un archivo creado en un sistema se mostraría como basura en otro a menos que ambos acordaran la misma codificación — y no había una forma confiable de señalar qué codificación usaba un archivo. El software adivinaba, y adivinaba mal constantemente.

Este es el problema que Unicode fue creado para resolver.

Unicode: Un Conjunto de Caracteres para Todos los Idiomas

Unicode no es una codificación. Es un conjunto de caracteres — una tabla de consulta masiva que asigna un número único a cada carácter en cada sistema de escritura en uso, más miles de símbolos, emojis y escrituras históricas.

Cada entrada en esa tabla se llama code point, escrito como U+ seguido de un número hexadecimal. Algunos ejemplos:

CarácterCode pointDescripción
AU+0041Letra latina mayúscula A
éU+00E9Letra latina minúscula e con acento agudo
中U+4E2DIdeógrafo CJK unificado (zhōng)
😀U+1F600Cara sonriente
𝄞U+1D11ESímbolo musical clave de sol

El estándar Unicode actualmente define más de 140.000 caracteres y se actualiza regularmente.

Planos Unicode

Los code points de Unicode van de U+0000 a U+10FFFF, dando algo más de 1,1 millones de ranuras posibles. Ese espacio se divide en 17 planos de 65.536 code points cada uno.

  • Plano 0 — Plano Multilingüe Básico (BMP): U+0000 a U+FFFF. Contiene prácticamente todos los caracteres en uso común hoy: latino, griego, cirílico, árabe, hebreo, CJK y más.
  • Plano 1 — Plano Multilingüe Suplementario (SMP): U+10000 a U+1FFFF. Emojis, escrituras históricas, notación musical.
  • Planos 2–16: Ideógrafos CJK unificados adicionales, escrituras históricas raras y espacio sin asignar.

Los emojis que rompen las tablas de MySQL viven en el Plano 1. Ese detalle importa, como veremos en breve.

UTF-8: Cómo se Almacena Realmente Unicode

Unicode define qué número obtiene cada carácter. No especifica cómo almacenar esos números como bytes en disco o en memoria. Ese es el trabajo de una codificación Unicode.

UTF-8 es la codificación Unicode más ampliamente utilizada en la web y en el software moderno. Es de ancho variable, lo que significa que diferentes caracteres ocupan diferentes cantidades de bytes:

Rango de code pointBytes usadosQuién vive aquí
U+0000 – U+007F1 byteASCII (los 128 caracteres)
U+0080 – U+07FF2 bytesLatin extendido, griego, cirílico, árabe, hebreo
U+0800 – U+FFFF3 bytesLa mayoría de caracteres CJK, resto del BMP
U+10000 – U+10FFFF4 bytesEmojis, escrituras raras

Cómo Funcionan las Secuencias de Bytes UTF-8

UTF-8 es auto-sincronizante. El primer byte de cualquier carácter te dice cuántos bytes siguen:

  • 0xxxxxxx — Carácter de 1 byte (ASCII). Cualquier archivo ASCII válido es UTF-8 válido.
  • 110xxxxx 10xxxxxx — Carácter de 2 bytes.
  • 1110xxxx 10xxxxxx 10xxxxxx — Carácter de 3 bytes.
  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx — Carácter de 4 bytes.

Los bytes de continuación siempre comienzan con 10, por lo que puedes determinar inmediatamente si cualquier byte dado es el inicio de un nuevo carácter o el medio de uno. Esto hace posible resincronizarse después de leer un stream parcial.

Ejemplo concreto — codificando é (U+00E9):

U+00E9 en binario es 11101001. Eso cae en el rango de 2 bytes. La plantilla 110xxxxx 10xxxxxx produce 11000011 10101001 — bytes 0xC3 0xA9 — que es exactamente lo que UTF-8 almacena para é.

Por Qué UTF-8 Domina

UTF-8 tiene tres propiedades que lo llevaron a ganar:

  1. Compatibilidad con ASCII. Cualquier archivo solo con ASCII es UTF-8 válido. Migrar sistemas existentes no requirió tocar archivos que no tenían contenido no ASCII.
  2. Eficiencia de espacio para scripts latinos. El texto en inglés usa un byte por carácter, igual que ASCII. Solo los caracteres fuera de ASCII cuestan más bytes.
  3. Sin ambigüedad de orden de bytes. La estructura de bytes es inequívoca sin ningún encabezado. (Compara esto con UTF-16, que requiere una marca de orden de bytes.)

UTF-16 y UTF-32: Cuándo los Encontrarás

UTF-8 no es la única codificación Unicode, y algunos entornos usan otras.

UTF-16 codifica los caracteres BMP como dos bytes (una unidad de código de 16 bits) y los caracteres de plano suplementario como cuatro bytes (dos unidades de código de 16 bits, llamadas un par sustituto). Los internos de Windows, las cadenas de Java, las cadenas de JavaScript (a nivel del motor) y las cadenas de .NET usan UTF-16 internamente.

UTF-32 usa exactamente cuatro bytes por carácter, siempre. Desperdicia espacio pero hace que el acceso aleatorio por índice de carácter sea trivialmente rápido. Algunos internos de bases de datos lo usan.

La Marca de Orden de Bytes (BOM)

UTF-16 tiene un problema de endianness. Una secuencia de dos bytes puede almacenarse como big-endian (0x00 0x41 para A) o little-endian (0x41 0x00). Un receptor necesita saber qué orden se usó.

La solución es la Marca de Orden de Bytes: code point U+FEFF colocado al principio del archivo. En UTF-16 big-endian, aparece como los bytes FE FF. En UTF-16 little-endian, aparece como FF FE. Un lector que ve uno u otro conoce el orden de bytes.

UTF-8 técnicamente también tiene un BOM (EF BB BF) pero es insignificante para UTF-8 porque UTF-8 no tiene ambigüedad de orden de bytes. Aquí es donde causa problemas:

  • Excel y archivos CSV. Excel usa el BOM UTF-8 como señal para interpretar un CSV como UTF-8. Sin él, Excel usa por defecto la codificación del locale del sistema (a menudo Windows-1252), mutilando los caracteres acentuados. Muchos desarrolladores añaden el BOM específicamente para que Excel funcione bien.
  • Scripts de shell. Un script de shell que comienza con #!/bin/bash debe tener esa secuencia como los primeros bytes. Si un BOM UTF-8 precede al shebang, el kernel ve EF BB BF 23 21 y no reconoce el shebang, causando que el script falle o se ejecute con el intérprete incorrecto.

La regla general: añade el BOM para archivos CSV dirigidos a Excel, nunca lo añadas a scripts de shell ni archivos JSON.

Errores de Codificación Comunes en el Mundo Real

Mojibake: Leer UTF-8 como Latin-1

Mojibake (文字化け) es el término japonés para el texto distorsionado causado por una discrepancia de codificación. La variante más común en la web moderna: una cadena UTF-8 se lee como si fuera Latin-1 (ISO-8859-1 o Windows-1252).

El carácter é es U+00E9. En UTF-8 se almacena como los dos bytes 0xC3 0xA9. Si un programa lee esos dos bytes como Latin-1, ve dos caracteres separados: 0xC3 es à y 0xA9 es ©. La cadena café se convierte en café.

Ese patrón de corrupción específico — é para é, è para è, à para à — es la señal inequívoca de UTF-8 siendo interpretado como Latin-1. Lo verás más frecuentemente en:

  • Bases de datos donde el charset de la conexión no se estableció en UTF-8 al insertar.
  • Correos electrónicos enviados a través de servidores de correo más antiguos.
  • Archivos copiados entre sistemas con configuraciones de locale incompatibles.

La solución es identificar dónde ocurre la discrepancia y corregir la declaración de codificación en esa capa — no parchear la cadena corrompida después del hecho.

El Carácter de Reemplazo (U+FFFD)

Cuando un decodificador encuentra una secuencia de bytes que no es válida en la codificación esperada, tiene que hacer algo. El estándar Unicode recomienda sustituir el carácter de reemplazo U+FFFD, mostrado como <0xFFFD>. Si ves cajas ? o el carácter <0xFFFD> en la salida, un decodificador encontró bytes que no pudo interpretar.

Causas comunes:

  • Truncar una secuencia UTF-8 multi-byte. Si cortas una cadena en un límite de byte que cae dentro de un carácter CJK de 3 bytes, la secuencia parcial es inválida.
  • Pasar un archivo codificado en Latin-1 a un decodificador UTF-8 estricto.
  • Recibir datos de una fuente que mezcla codificaciones.

MySQL's utf8 vs utf8mb4: El Problema Emoji

Este es uno de los errores de codificación más conocidos en el desarrollo web. MySQL tiene dos conjuntos de caracteres llamados utf8 y utf8mb4.

El utf8 de MySQL no es UTF-8 real. Solo soporta code points hasta U+FFFF — exactamente el Plano Multilingüe Básico — usando un máximo de tres bytes por carácter. Los emojis viven en el Plano 1 (U+10000 y superior) y requieren cuatro bytes. Cuando intentas insertar un carácter UTF-8 de cuatro bytes en una columna definida como utf8, MySQL silenciosamente trunca el valor o lanza un error.

utf8mb4 es UTF-8 real con soporte completo de cuatro bytes. Fue introducido específicamente para manejar emojis y otros caracteres del Plano Suplementario.

El enfoque correcto: usa siempre utf8mb4 para nuevas tablas y bases de datos. También establece el charset y la collation de la conexión explícitamente:

ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Y establece el parámetro de conexión en la configuración de tu aplicación:

charset=utf8mb4

Usar utf8 en MySQL es una trampa heredada. Trátalo como obsoleto.

Longitud de Cadena vs. Conteo de Caracteres en Python

Python 2 almacenaba las cadenas como secuencias de bytes por defecto. len("café") devolvía 5 para una cadena UTF-8 — porque é ocupa dos bytes — no 4. El manejo correcto de multi-byte requería literales unicode explícitos u"" y una disciplina cuidadosa de encode/decode.

Python 3 solucionó esto: str es una secuencia de code points Unicode, por lo que len("café") devuelve 4. Los datos de bytes se movieron al tipo bytes con llamadas explícitas .encode() y .decode().

Una sutileza permanece. El len() de Python 3 cuenta code points, no clusters de grafemas. El carácter é puede representarse como un único code point (U+00E9) o como e (U+0065) más un acento agudo combinante (U+0301). Ambos se renderizan idénticamente pero producen diferentes valores de len(). Las aplicaciones multilingüe que exponen un conteo de caracteres a los usuarios deben usar una biblioteca de segmentación Unicode en lugar de len() directamente.

Cuando inspeccionas datos JSON que contienen cadenas multilingüe, puedes notar esto — lo que parece un único carácter podría ser dos code points internamente.

Tabla de Comparación de Codificaciones

CodificaciónMáx. caracteresBytes por ASCIIBytes por emojiContexto común
ASCII1281N/ASistemas heredados, encabezados de protocolo
Latin-1 (ISO-8859-1)2561N/ATexto heredado Europa Occidental
Windows-12522561N/ADocumentos Windows heredados
UTF-81.114.11214Web, Linux, macOS, APIs modernas
UTF-161.114.11224Windows, Java, .NET, internos del motor JavaScript
UTF-321.114.11244Algunos internos de BD, procesamiento de texto
MySQL utf8~65.5361RotoColumnas MySQL heredadas — evitar
MySQL utf8mb41.114.11214MySQL — usar esto

Cómo Detectar y Solucionar Problemas de Codificación

Detección

En Python:

import chardet
 
with open("mystery_file.txt", "rb") as f:
    raw = f.read()
 
result = chardet.detect(raw)
print(result)  # {'encoding': 'Windows-1252', 'confidence': 0.73, 'language': ''}

La biblioteca chardet usa análisis estadístico para adivinar la codificación a partir de patrones de bytes. La puntuación de confianza es honesta — la detección de codificación es heurística, no determinista.

En el navegador: Abre DevTools, ve a la pestaña Network, encuentra la respuesta de una página HTML y comprueba el encabezado Content-Type. Debe leer text/html; charset=utf-8. Si está ausente o es incorrecto, el navegador adivinará, a menudo incorrectamente.

En una base de datos: Comprueba el conjunto de caracteres a nivel de base de datos, tabla y columna:

SHOW VARIABLES LIKE 'character_set%';
SHOW CREATE TABLE posts;

Reparar una Base de Datos con Mojibake

Si los datos se insertaron como UTF-8 pero se almacenaron como Latin-1 (un error de migración común), a veces puedes recuperarlos:

-- Lee los datos corruptos como binario, luego interpreta como UTF-8
UPDATE posts
SET body = CONVERT(CAST(CONVERT(body USING latin1) AS BINARY) USING utf8mb4)
WHERE body REGEXP '[Ã]';

Esto solo funciona si los bytes originales están intactos y simplemente fueron mal etiquetados. Si los datos estaban verdaderamente corrompidos — se eliminaron o sustituyeron bytes — la recuperación no es posible sin una copia de seguridad.

Prevención

  • Siempre declara la codificación explícitamente. Nunca dependas de los valores predeterminados.
  • Establece <meta charset="UTF-8"> en cada documento HTML.
  • Establece Content-Type: application/json; charset=utf-8 en las respuestas de API.
  • Especifica el charset de la conexión al abrir conexiones de base de datos.
  • Usa UTF-8 en tu editor y configura tu sistema de control de versiones para que lo imponga en archivos de texto.

Para tareas de codificación más allá de los conjuntos de caracteres — como convertir datos binarios a representaciones seguras para texto — la herramienta codificar y decodificar texto de FileCrank maneja la conversión Base64 sin configuración. El artículo de codificación existente cubre Base64, codificación URL y entidades HTML si los necesitas. Si estás trabajando con archivos de configuración y necesitas verificar que las cadenas multilingüe se vean correctas, puedes inspeccionar datos JSON directamente en el navegador.

Lo que Debes Llevarte

Los errores de codificación de caracteres se sienten misteriosos porque el modo de fallo — caracteres distorsionados o faltantes — aparece lejos de la causa real. Pero el mecanismo subyacente es simple: dos programas están usando tablas de consulta diferentes para convertir bytes a caracteres.

Mantén estas tres reglas presentes:

  1. Usa siempre UTF-8 en todas partes a menos que un sistema específico fuerce otra cosa. Establécelo explícitamente; nunca dependas de los valores predeterminados.
  2. En MySQL, usa utf8mb4, no utf8. La diferencia importa en el momento en que un usuario envía un emoji.
  3. Cuando veas mojibake, encuentra el límite. Busca dónde los bytes cruzan un sistema que no conoce su codificación. Corrige la declaración en ese punto, no los síntomas aguas abajo.

Entender la mecánica — code points, secuencias de bytes, declaraciones de codificación — significa que puedes analizar un nuevo error de codificación de forma sistemática en lugar de adivinar. La próxima vez que veas é en tu salida, sabrás de inmediato qué sucedió y dónde buscar.


Todas las herramientas de FileCrank se ejecutan completamente en tu navegador. No se suben archivos a un servidor. Si estás trabajando con texto que contiene contenido multilingüe, prueba el Visor de JSON o la herramienta de Codificación/Decodificación Base64 — ambas manejan Unicode correctamente sin ninguna configuración.