IntlPull
Guide
16 min read

Codificación de caracteres para desarrolladores: Explicación de UTF-8, Unicode y ASCII (2026)

Detenga los errores de codificación antes de que se produzcan. Aprenda UTF-8, Unicode, ASCII y cómo evitar mojibake, caracteres corruptos y problemas de codificación de bases de datos.

IntlPull Team
IntlPull Team
03 Feb 2026, 11:44 AM [PST]
On this page
Summary

Detenga los errores de codificación antes de que se produzcan. Aprenda UTF-8, Unicode, ASCII y cómo evitar mojibake, caracteres corruptos y problemas de codificación de bases de datos.

The Bug That Cost content: 00K

Eran las 2 de la madrugada. Llegó el correo electrónico del director general: "¿Por qué nuestro sitio en francés muestra un galimatías?"

En lugar de "Café", los usuarios veían "Café". En lugar de "Résumé", veían "Résumé".

Clásico mojibake. El desarrollador había configurado la base de datos en Latin-1, la API en UTF-8 y el frontend en ASCII. Tres codificaciones diferentes, una experiencia de usuario completamente rota.

¿La solución? Cinco minutos. ¿Los daños? Ventas perdidas, usuarios enfadados, 2 semanas de parches de emergencia.

Esta guía lo evita. Explicaremos qué es realmente la codificación de caracteres, por qué es importante y cómo no volver a meter la pata.

¿Qué es la codificación de caracteres?

Los ordenadores no entienden de letras. Entienden números.

La codificación de caracteres es el mapa: carácter → número.

Ejemplo:

  • La letra "A" tiene que convertirse en un número que los ordenadores puedan almacenar
  • ASCII dice: "A" = 65
  • Unicode dice: "A" = U+0041
  • UTF-8 dice: "A" = el byte 0x41

Simple, ¿verdad? Excepto que hay como 50 estándares de codificación diferentes, cada uno con reglas distintas.

Los tres que realmente necesitas saber

1. ASCII (El Antiguo)

Qué es: Código Estándar Americano para el Intercambio de Información Inventado: 1963 Caracteres: 128 (0-127)

Qué cubre:

  • Letras inglesas (A-Z, a-z)
  • Números (0-9)
  • Puntuación básica (.,!?)
  • Caracteres de control (nueva línea, tabulador)

**Lo que no incluye

  • Caracteres acentuados (é, ñ, ü)
  • Escrituras no latinas (中文, العربية, हिन्दी)
  • Emojis (💩)
  • Básicamente cualquier cosa útil para i18n

Cuándo usarlo: Nunca. Estamos en 2026. A menos que estés programando un terminal de los 80.

Ejemplo:

A → 65
B → 66
Z → 90
a → 97
0 → 48

2. Unicode (La Biblioteca)

Qué es: Un catálogo masivo de todos los caracteres de todos los idiomas Versión actual: Unicode 15.1 (2023), 149.813 caracteres Piénsalo como: La agenda, no el teléfono

Importante: Unicode NO es una codificación. Es un conjunto de caracteres.

Unicode asigna a cada carácter un punto de código (un número):

  • "A" = U+0041
  • "é" = U+00E9
  • "中" = U+4E2D
  • "🔥" = U+1F525

Pero no dice cómo almacenar esos números. Ahí es donde entra UTF-8.

Planos Unicode:

  • BMP (Plano Multilingüe Básico): U+0000 a U+FFFF (caracteres más comunes)
  • SMP (Supplementary Multilingual Plane): U+10000 a U+1FFFF (emojis, scripts poco comunes)
  • SIP, TIP, SSP: Escrituras antiguas, símbolos matemáticos, notación musical

3. UTF-8 (La única codificación verdadera)

Qué es: Una forma de codificar caracteres Unicode como bytes Inventado: 1992 por Ken Thompson y Rob Pike Cuota de mercado: 98% de todos los sitios web (a partir de 2026)

¿Por qué ganó?

  1. Compatible con ASCII: Los primeros 128 caracteres son idénticos
  2. Anchura variable: Utiliza de 1 a 4 bytes dependiendo del carácter
  3. Autosincronización: Si salta en medio de un flujo UTF-8, puede encontrar el siguiente límite de caracteres
  4. Eficiente: El texto en inglés tiene el mismo tamaño que ASCII, pero admite todos los idiomas

Cómo funciona:

Character | Code Point | UTF-8 Bytes | Size
----------|-----------|-------------|-----
A         | U+0041    | 0x41        | 1 byte
é         | U+00E9    | 0xC3 0xA9   | 2 bytes
中        | U+4E2D    | 0xE4 0xB8 0xAD | 3 bytes
🔥        | U+1F525   | 0xF0 0x9F 0x94 0xA5 | 4 bytes

Otras codificaciones UTF que verás:

  • UTF-16: Utiliza 2 ó 4 bytes. Común en Windows, Java, JavaScript internos
  • UTF-32: Siempre 4 bytes. Despilfarrador pero sencillo
  • UTF-7: Existe, pero nunca lo usarás

La regla: Usa UTF-8 en todas partes. Punto.

Desastres comunes de codificación

Desastre 1: Mojibake (文字化け)

Síntoma: El texto parece basura aleatoria.

Ejemplo:

Expected: "Café"
Actual:   "Café"

Qué ha pasado:

  1. El texto se codificó como UTF-8: Café0x43 0x61 0x66 0xC3 0xA9
  2. El lector lo interpretó como Latin-1 (ISO-8859-1)
  3. Latin-1 no entiende caracteres multibyte
  4. Cada byte se convertía en un carácter independiente: "C", "a", "f", "Ã", "©"

Fix:

JavaScript
1// Detect encoding (not 100% reliable, but helps)
2import jschardet from 'jschardet';
3
4const buffer = fs.readFileSync('file.txt');
5const detected = jschardet.detect(buffer);
6console.log(detected.encoding); // 'UTF-8', 'ISO-8859-1', etc.
7
8// Convert to UTF-8
9const iconv = require('iconv-lite');
10const utf8String = iconv.decode(buffer, detected.encoding);

Desastre 2: Desajuste en la codificación de la base de datos

Síntoma: Los datos se ven bien en el código, pero no en la base de datos (o viceversa).

Ejemplo (MySQL):

SQL
1-- ❌ Wrong: latin1 database
2CREATE DATABASE mydb CHARACTER SET latin1;
3
4-- ✅ Right: utf8mb4 (supports emojis)
5CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

La trampa UTF-8 de MySQL:

  • El conjunto de caracteres utf8 de MySQL NO es UTF-8 real (máximo 3 bytes)
  • Los emojis necesitan 4 bytes → utf8 no puede almacenarlos
  • Utilice siempre utf8mb4 (UTF-8, máx. 4 bytes)

Comprueba tu base de datos:

SQL
1SHOW VARIABLES LIKE 'character_set_%';
2SHOW VARIABLES LIKE 'collation_%';
3
4-- Should all be utf8mb4

La cadena de conexión debe coincidir:

JavaScript
1// Node.js MySQL
2const connection = mysql.createConnection({
3  host: 'localhost',
4  user: 'root',
5  database: 'mydb',
6  charset: 'utf8mb4' // ← CRITICAL
7});

Postgres:

SQL
1-- Check encoding
2SHOW SERVER_ENCODING;
3SHOW CLIENT_ENCODING;
4
5-- Set to UTF-8 (usually default)
6SET CLIENT_ENCODING TO 'UTF8';

Desastre 3: El infierno de la codificación JSON

Síntoma: Los caracteres especiales se convierten en secuencias de escape \uXXXX.

Ejemplo:

JavaScript
const data = { message: "Hello 世界" };
console.log(JSON.stringify(data));
// {"message":"Hello \u4e16\u754c"}

Por qué: JSON.stringify escapa no ASCII por defecto.

Fix:

JavaScript
1// Don't escape Unicode
2JSON.stringify(data, null, 2);
3// Still escapes... that's actually correct!
4
5// The real issue: receiving end must parse correctly
6const parsed = JSON.parse('{"message":"Hello \u4e16\u754c"}');
7console.log(parsed.message); // "Hello 世界" ✅

Problema real normalmente:

JavaScript
1// ❌ Wrong: Sending JSON as Latin-1
2res.setHeader('Content-Type', 'application/json; charset=ISO-8859-1');
3
4// ✅ Right: UTF-8
5res.setHeader('Content-Type', 'application/json; charset=UTF-8');

Desastre 4: Corrupción en la exportación de CSV

Síntoma: Exportar a CSV, abrir en Excel, todos los caracteres especiales rotos.

Por qué: Excel por defecto a la codificación de su sistema (a menudo Windows-1252, no UTF-8).

Fix: Añadir BOM (Byte Order Mark)

JavaScript
1// Add UTF-8 BOM so Excel knows it's UTF-8
2const BOM = '\uFEFF';
3const csv = BOM + 'Name,City\n' +
4             'José,São Paulo\n' +
5             '李明,北京\n';
6
7fs.writeFileSync('export.csv', csv, 'utf8');

O forzar UTF-8 en la importación: Excel → Datos → Desde texto → Origen del archivo: 65001 (UTF-8)

Desastre 5: Problemas de codificación de URL

Síntoma: Las URL con caracteres no ASCII se rompen.

Ejemplo:

Raw:     /search?q=café
Broken:  /search?q=caf�
Correct: /search?q=caf%C3%A9

Corrección: Codifique siempre las URL

JavaScript
1// ❌ Wrong
2const url = `/search?q=${query}`;
3
4// ✅ Right
5const url = `/search?q=${encodeURIComponent(query)}`;
6
7// Example
8encodeURIComponent('café'); // 'caf%C3%A9'
9encodeURIComponent('中文'); // '%E4%B8%AD%E6%96%87'

Descodificación:

JavaScript
const query = new URLSearchParams(window.location.search).get('q');
// Automatically decoded ✅

Cómo depurar problemas de codificación

Paso 1: Encontrar dónde se produce el error de codificación

Los problemas de codificación ocurren en los límites:

  • Lectura de archivos
  • Consultas a bases de datos
  • Peticiones/respuestas HTTP
  • Concatenación de cadenas de diferentes fuentes

Script de depuración:

JavaScript
1function debugEncoding(text) {
2  console.log('Text:', text);
3  console.log('Length:', text.length);
4  console.log('Bytes:', Buffer.from(text, 'utf8'));
5  console.log('Hex:', Buffer.from(text, 'utf8').toString('hex'));
6
7  // Check each character
8  for (let i = 0; i < text.length; i++) {
9    const char = text[i];
10    const code = text.charCodeAt(i);
11    const unicode = 'U+' + code.toString(16).toUpperCase().padStart(4, '0');
12    console.log(`[${i}] ${char}${code} (${unicode})`);
13  }
14}
15
16debugEncoding('Café');
17// [0] C → 67 (U+0043)
18// [1] a → 97 (U+0061)
19// [2] f → 102 (U+0066)
20// [3] é → 233 (U+00E9)

Paso 2: Inspeccionar secuencias de bytes

Si ve mojibake, compruebe los bytes:

JavaScript
1const broken = 'Café';
2console.log(Buffer.from(broken, 'utf8').toString('hex'));
3// 43 61 66 c3 83 c2 a9
4
5// Compare to correct:
6const correct = 'Café';
7console.log(Buffer.from(correct, 'utf8').toString('hex'));
8// 43 61 66 c3 a9

Fíjate en la doble codificación: c3 83 c2 a9 frente a c3 a9.

Lo que pasó:

  1. "é" = bytes UTF-8: c3 a9
  2. Esos bytes se interpretaron como Latin-1 → "é"
  3. "é" fue recodificado a UTF-8 → c3 83 c2 a9

Corrección: Doble decodificación

JavaScript
const broken = 'Café';
const fixed = Buffer.from(broken, 'latin1').toString('utf8');
console.log(fixed); // 'Café' ✅

Paso 3: Comprobar cada capa

Lista de comprobación de la aplicación web:

1. HTML:

HTML
<!-- ✅ Add this to every page -->
<meta charset="UTF-8">

2. Cabeceras HTTP:

JavaScript
// ✅ Server response
res.setHeader('Content-Type', 'text/html; charset=UTF-8');

3. Base de datos:

SQL
1-- ✅ MySQL
2CREATE TABLE users (
3  name VARCHAR(255)
4) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
5
6-- ✅ PostgreSQL (usually default)
7CREATE DATABASE mydb ENCODING 'UTF8';

4. Conexión a la base de datos:

JavaScript
1// ✅ Specify in connection string
2const pool = new Pool({
3  connectionString: 'postgres://user:pass@localhost/mydb?client_encoding=UTF8'
4});

5. E/S de archivos:

JavaScript
// ✅ Explicitly set encoding
fs.writeFileSync('file.txt', content, 'utf8');
fs.readFileSync('file.txt', 'utf8');

6. Solicitudes API:

JavaScript
1// ✅ Set Content-Type header
2fetch('/api/data', {
3  method: 'POST',
4  headers: {
5    'Content-Type': 'application/json; charset=UTF-8'
6  },
7  body: JSON.stringify({ text: 'Café' })
8});

Buenas prácticas

1. UTF-8 en todas partes

Mantra: UTF-8 desde el almacenamiento hasta la visualización.

Lista de comprobación de configuración:

  • ✅ Base de datos: UTF-8 / utf8mb4
  • ✅ Conexión de base de datos: charset=utf8mb4
  • ✅ Archivos: Guardar como UTF-8 (comprueba la configuración de tu editor)
  • ✅ HTML: <meta charset="UTF-8">
  • ✅ HTTP: Content-Type: ...; charset=UTF-8
  • ✅ Código: Leer/escribir archivos con encoding='utf8'

2. Validar entrada

Rechazar secuencias UTF-8 no válidas:

JavaScript
1function isValidUTF8(str) {
2  try {
3    // Try encoding round-trip
4    const encoded = new TextEncoder().encode(str);
5    const decoded = new TextDecoder('utf-8', { fatal: true }).decode(encoded);
6    return decoded === str;
7  } catch (e) {
8    return false;
9  }
10}
11
12// Usage in API
13app.post('/api/comment', (req, res) => {
14  const { text } = req.body;
15
16  if (!isValidUTF8(text)) {
17    return res.status(400).json({ error: 'Invalid UTF-8 encoding' });
18  }
19
20  // Continue...
21});

3. Normalizar Unicode

Problema: Múltiples formas de codificar el mismo carácter.

Ejemplo:

JavaScript
1// "é" can be:
2const composed = 'é';    // Single code point U+00E9
3const decomposed = 'é';  // e (U+0065) + ´ (U+0301)
4
5console.log(composed === decomposed); // false 😱
6console.log(composed.length); // 1
7console.log(decomposed.length); // 2

**Corrección: Normalizar antes de comparar

JavaScript
1const a = 'café'.normalize('NFC');
2const b = 'café'.normalize('NFC');
3console.log(a === b); // true ✅
4
5// Forms:
6// NFC (Canonical Composition) - use this for display
7// NFD (Canonical Decomposition) - use for searching
8// NFKC/NFKD (Compatibility) - use for normalization

Normalizar en consultas de base de datos:

JavaScript
1// Search ignoring normalization
2const searchTerm = userInput.normalize('NFD');
3const results = await db.query(
4  'SELECT * FROM products WHERE LOWER(name) LIKE LOWER(content: )',
5  [`%${searchTerm}%`]
6);

4. Límites de longitud

Tenga cuidado con los límites de caracteres:

JavaScript
1// ❌ Wrong: Byte length != character length
2const text = '中文测试';
3console.log(text.length); // 4 characters
4console.log(Buffer.from(text, 'utf8').length); // 12 bytes
5
6// Database varchar(10) in bytes = only 3 Chinese chars!

Fix: Contar caracteres, no bytes

JavaScript
1function truncate(str, maxChars) {
2  if (str.length <= maxChars) return str;
3  return str.slice(0, maxChars) + '...';
4}
5
6// Or use Array.from to handle emojis correctly
7function truncateEmoji(str, maxChars) {
8  const chars = Array.from(str);
9  if (chars.length <= maxChars) return str;
10  return chars.slice(0, maxChars).join('') + '...';
11}
12
13truncateEmoji('Hello 👨‍👩‍👧‍👦 World', 10);
14// "Hello 👨‍👩‍👧‍👦 Wo..."

5. Manejar correctamente los emojis

Problema: Los emojis son complejos.

JavaScript
1const emoji = '👨‍👩‍👧‍👦'; // Family emoji
2console.log(emoji.length); // 11 😱
3
4// Why? It's multiple code points joined with Zero-Width Joiners

Fix: Utilizar la segmentación Unicode adecuada

JavaScript
1// Split by grapheme clusters
2const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
3const segments = Array.from(segmenter.segment('Hello 👨‍👩‍👧‍👦 World'));
4console.log(segments.map(s => s.segment));
5// ['H', 'e', 'l', 'l', 'o', ' ', '👨‍👩‍👧‍👦', ' ', 'W', 'o', 'r', 'l', 'd']
6
7// Count "characters" correctly
8const charCount = segments.length; // 13 ✅

Comprobación de problemas de codificación

Datos de prueba

Utilice estas cadenas para probar la codificación:

JavaScript
1const testStrings = [
2  'Hello World',                    // ASCII baseline
3  'Café résumé naïve',              // Latin-1 extensions
4  'Привет мир',                     // Cyrillic
5  '你好世界',                        // Chinese
6  'مرحبا بالعالم',                  // Arabic (RTL)
7  '🔥💯👍',                          // Emojis
8  '👨‍👩‍👧‍👦',                            // Complex emoji (ZWJ sequence)
9  '\u0000\u0001\u001F',          // Control characters
10  'test\r\nline\rbreaks\n',     // Line breaks
11];
12
13testStrings.forEach(str => {
14  // Send through your system
15  const result = yourFunction(str);
16  assert(result === str, 'Encoding corruption detected');
17});

Pruebas automatizadas

JavaScript
1// Encoding round-trip test
2describe('Encoding', () => {
3  it('should preserve UTF-8 through database', async () => {
4    const testString = 'Café 中文 🔥';
5
6    await db.insert({ text: testString });
7    const result = await db.query('SELECT text FROM table');
8
9    expect(result[0].text).toBe(testString);
10  });
11
12  it('should handle API round-trip', async () => {
13    const testString = 'Résumé 日本語';
14
15    const response = await fetch('/api/echo', {
16      method: 'POST',
17      headers: { 'Content-Type': 'application/json; charset=UTF-8' },
18      body: JSON.stringify({ text: testString })
19    });
20
21    const data = await response.json();
22    expect(data.text).toBe(testString);
23  });
24});

Problemas específicos de la plataforma

Windows

Problema: Windows utiliza diferentes codificaciones por defecto.

  • Símbolo del sistema: Codificación 437 o Windows-1252
  • PowerShell: UTF-16 LE

Fix:

POWERSHELL
# Set PowerShell to UTF-8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

macOS/Linux

Problema: Normalmente UTF-8 por defecto, pero compruébalo:

Terminal
1locale
2# Should show UTF-8
3
4# If not:
5export LC_ALL=en_US.UTF-8
6export LANG=en_US.UTF-8

Python

Python 3:

Python
# ✅ Default is UTF-8 (usually)
with open('file.txt', 'r', encoding='utf-8') as f:
    content = f.read()

Python 2 (legacy):

Python
1# ❌ Default is ASCII (nightmare)
2# Always specify encoding
3import codecs
4with codecs.open('file.txt', 'r', 'utf-8') as f:
5    content = f.read()

Java

Problema: Java usa UTF-16 internamente.

JAVA
1// ✅ Read UTF-8 files
2BufferedReader reader = new BufferedReader(
3    new InputStreamReader(
4        new FileInputStream("file.txt"),
5        StandardCharsets.UTF_8
6    )
7);
8
9// ✅ Write UTF-8 files
10BufferedWriter writer = new BufferedWriter(
11    new OutputStreamWriter(
12        new FileOutputStream("file.txt"),
13        StandardCharsets.UTF_8
14    )
15);

PHP

PHP
1<?php
2// ✅ Set default encoding
3ini_set('default_charset', 'UTF-8');
4mb_internal_encoding('UTF-8');
5
6// ✅ Database connection
7$pdo = new PDO(
8    'mysql:host=localhost;dbname=mydb;charset=utf8mb4',
9    'user',
10    'password'
11);

Validación de codificación de IntlPull

Cuando usted empuja traducciones a IntlPull, nosotros automáticamente:

  • ✅ Validamos la codificación UTF-8
  • ✅ Comprobación de secuencias de bytes no válidas
  • ✅ Normalizar Unicode (forma NFC)
  • ✅ Detectar desajustes de codificación
  • ✅ Marcar mojibake potencial
Terminal
1npx @intlpullhq/cli upload
2
3# Output:
4# ✅ All strings valid UTF-8
5# ⚠️ Warning: String "Café" looks like double-encoded UTF-8
6# 💡 Suggestion: Check database encoding

Esto detecta problemas de codificación antes de que lleguen a producción.

El TL;DR

Reglas para vivir:

  1. Utiliza UTF-8 en todas partes. Sin excepciones.
  2. Establezca la codificación explícitamente en cada frontera (archivos, DB, HTTP).
  3. Probar con cadenas no ASCII (chino, árabe, emojis).
  4. Normalizar antes de comparar (.normalize('NFC')).
  5. Contar caracteres correctamente (usar Array.from() para emojis).

**Errores comunes

  • MySQL utf8 (usar utf8mb4)
  • Olvidar charset=UTF-8 en las cabeceras HTTP
  • Comparación sin normalización
  • Longitud de cadena para límites de caracteres
  • Apertura de archivos Windows sin codificación especificada

Cuando vea mojibake:

  1. Compruebe la codificación de la base de datos
  2. Compruebe la codificación de la conexión
  3. Comprobar cabeceras HTTP
  4. Pruebe la decodificación doble (latin1 → utf8)

**¿Necesita ayuda para gestionar contenidos multilingües?

Pruebe IntlPull. Valida automáticamente la codificación, detecta mojibake y normaliza Unicode en sus traducciones. Disponible en versión gratuita.

O simplemente recuerde: UTF-8 en todas partes. Todo irá bien.

Tags
character-encoding
utf-8
unicode
i18n
technical
debugging
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.