5 errores comunes de descodificación de URL y cómo solucionarlos
Los errores de descodificación de URL pueden convertir una experiencia de usuario fluida en una pesadilla de depuración. Basándonos en años de experiencia en desarrollo web y miles de informes de errores, estos son los 5 errores más comunes de descodificación de URL y exactamente cómo solucionarlos.
Error #1: Formato de codificación porcentual incorrecto
El problema
No todas las cadenas que parecen codificadas en URL son realmente válidas. Las secuencias porcentuales inválidas harán que la descodificación falle.
Patrones inválidos comunes:
hello%world // Faltan dígitos hexadecimales
test%2 // Secuencia incompleta (necesita 2 dígitos hex)
data%ZZ // Caracteres hexadecimales inválidos
url%GG%20test // Mezcla de inválidos (%GG) y válidos (%20)
Qué ocurre
// ¡Esto lanzará un error!
decodeURIComponent('hello%world');
// URIError: URI mal formada
decodeURIComponent('test%2');
// URIError: URI mal formada
La causa raíz
- Construcción manual de URL sin codificación adecuada
- URLs truncadas (errores de copiar y pegar)
- Datos no-URL confundidos con cadenas codificadas
- Sistemas heredados que no siguen RFC 3986
La solución
Solución #1: Validar antes de descodificar
function esValidoCodificado(str) {
// Buscar patrones porcentuales inválidos
const patronInvalido = /%(?![0-9A-Fa-f]{2})|%[0-9A-Fa-f](?![0-9A-Fa-f])/;
if (patronInvalido.test(str)) {
return false;
}
// Intentar descodificar - si lanza error, es inválido
try {
decodeURIComponent(str);
return true;
} catch (e) {
return false;
}
}
// Uso
const entradaUsuario = params.get('search');
if (esValidoCodificado(entradaUsuario)) {
const descodificado = decodeURIComponent(entradaUsuario);
} else {
console.error('Codificación URL inválida detectada');
// Manejar el error apropiadamente
}
Solución #2: Sanear codificaciones malformadas
function sanearCodificacion(str) {
// Reemplazar secuencias porcentuales incompletas o inválidas
return str.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
// Convierte % a %25 cuando no está seguido de 2 dígitos hex
}
// Ejemplo
sanearCodificacion('hello%world'); // → 'hello%25world'
decodeURIComponent(sanearCodificacion('hello%world')); // → 'hello%world'
Solución #3: Preprocesar con regex
function descodificarSeguro(str) {
try {
return decodeURIComponent(str);
} catch (e) {
// Respaldo: reemplazar manualmente patrones comunes
return str
.replace(/%20/g, ' ')
.replace(/%21/g, '!')
.replace(/%40/g, '@')
.replace(/%23/g, '#')
.replace(/%25/g, '%');
// Nota: Esto no es exhaustivo, solo un respaldo
}
}
Prevención
Use siempre funciones de codificación adecuadas:
// ✅ Correcto
const consulta = encodeURIComponent(entradaUsuario);
const url = `/search?q=${consulta}`;
// ❌ Incorrecto - construcción manual de URL
const url = `/search?q=${entradaUsuario.replace(/ /g, '%20')}`;
Prueba rápida
// Casos de prueba para validación
const casosPrueba = [
{ input: 'hello%20world', valid: true },
{ input: 'hello%world', valid: false },
{ input: 'test%2', valid: false },
{ input: '%E4%B8%AD%E6%96%87', valid: true },
{ input: 'normal-text', valid: true }, // Sin codificación también es válido
];
casosPrueba.forEach(({ input, valid }) => {
const resultado = esValidoCodificado(input);
console.assert(resultado === valid, `Falló para: ${input}`);
});
Error #2: Discrepancias de codificación de caracteres
El problema
Codificar una cadena en un conjunto de caracteres (ej., ISO-8859-1) y descodificarla como otro (UTF-8) produce texto sin sentido o el carácter de reemplazo �.
Síntomas:
Esperado: café
Obtenido: café
Esperado: 中文
Obtenido: ���
Esperado: Ñoño
Obtenido: �o�o
Qué ocurre
// Si el servidor codificó en ISO-8859-1 pero descodificas como UTF-8:
const codificado = '%C3%A9'; // é en UTF-8
decodeURIComponent(codificado); // → 'é' (correcto en UTF-8)
// Pero si fue realmente codificado en ISO-8859-1 como %E9:
const codificacionIncorrecta = '%E9';
decodeURIComponent(codificacionIncorrecta); // → 'é' pero se muestra mal
La causa raíz
- Sistemas heredados usando codificaciones no UTF-8
- Codificación mixta en diferentes partes de la aplicación
- Base de datos configurada con charset incorrecto
- Cabeceras HTTP especificando codificación incorrecta
La solución
Solución #1: Estandarizar en UTF-8 en todas partes
<!-- En HTML -->
<meta charset="UTF-8">
<!-- En cabeceras HTTP -->
Content-Type: text/html; charset=UTF-8
// En Express.js
app.use(express.urlencoded({ extended: true, charset: 'utf-8' }));
-- En MySQL
CREATE DATABASE mibd CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Solución #2: Detectar discrepancias de codificación
function pareceTextoCorrupto(str) {
// Patrones comunes de UTF-8 interpretado como ISO-8859-1
const patronesSospechosos = [
/é|è|à |ç/, // Común en francés
/£|Â¥|©/, // Monedas y símbolos
/�/, // Carácter de reemplazo
];
return patronesSospechosos.some(patron => patron.test(str));
}
// Uso
const descodificado = decodeURIComponent(codificado);
if (pareceTextoCorrupto(descodificado)) {
console.warn('¡Posible discrepancia de codificación detectada!');
}
Solución #3: Recodificar si es necesario
// Si sabes que la fuente era Latin-1 pero se descodificó como UTF-8:
function arreglarLatin1AUTf8(str) {
// Esta es una operación compleja, usa una biblioteca si es posible
const codificador = new TextEncoder();
const descodificador = new TextDecoder('iso-8859-1');
const bytes = codificador.encode(str);
return descodificador.decode(bytes);
}
Prevención
Imponer UTF-8 en todas las capas:
- Base de datos: UTF-8 (o utf8mb4 para MySQL)
- Cabeceras HTTP:
Content-Type: charset=UTF-8 - HTML:
<meta charset="UTF-8"> - Archivos fuente: Guardar como UTF-8
- APIs: Aceptar y devolver UTF-8
Prueba rápida
// Probar con caracteres internacionales
const pruebas = [
{ text: 'café', lang: 'Francés' },
{ text: '中文', lang: 'Chino' },
{ text: 'العربية', lang: 'Árabe' },
{ text: '😀', lang: 'Emoji' },
];
pruebas.forEach(({ text, lang }) => {
const codificado = encodeURIComponent(text);
const descodificado = decodeURIComponent(codificado);
console.assert(descodificado === text, `Codificación ${lang} falló`);
});
Error #3: Descodificación incompleta (problemas de múltiples capas)
El problema
Las URLs codificadas múltiples veces necesitan múltiples operaciones de descodificación. Detenerse demasiado pronto deja secuencias porcentuales en la salida.
Ejemplo:
Original: Hello World
Codificado 1x: Hello%20World
Codificado 2x: Hello%2520World
Codificado 3x: Hello%252520World
// Si solo descodificas una vez:
decodeURIComponent('Hello%252520World') // → 'Hello%2520World' (¡aún codificado!)
Qué ocurre
const dobleCodificado = 'search%253Dhello%2520world';
// Descodificar una vez
const unavez = decodeURIComponent(dobleCodificado);
console.log(unavez); // 'search%3Dhello%20world' - ¡aún contiene %3D y %20!
// Descodificar dos veces
const dosveces = decodeURIComponent(unavez);
console.log(dosveces); // 'search=hello world' - ¡correcto!
La causa raíz
- Múltiples redireccionamientos cada uno codificando la URL
- Cadenas de middleware que codifican repetidamente
- Copiar-pegar del usuario de URLs ya codificadas
- Auto-codificación del framework encima de codificación manual
La solución
Solución #1: Descodificación iterativa hasta estabilizar
function descodificarCompletamente(str) {
let descodificado = str;
let anterior = '';
let iteraciones = 0;
const MAX_ITERACIONES = 5; // Límite de seguridad
while (descodificado !== anterior && iteraciones < MAX_ITERACIONES) {
anterior = descodificado;
try {
const temp = decodeURIComponent(descodificado);
// Solo continuar si algo realmente cambió
if (temp !== descodificado) {
descodificado = temp;
} else {
break;
}
} catch (e) {
// Detener en error
console.error('Descodificación detenida por error:', e);
break;
}
iteraciones++;
}
console.log(`Descodificado ${iteraciones} veces`);
return descodificado;
}
// Uso
descodificarCompletamente('Hello%252520World'); // → 'Hello World' (3 iteraciones)
Solución #2: Contar capas de codificación
function contarCapas(str) {
let cantidad = 0;
let actual = str;
while (/%[0-9A-Fa-f]{2}/.test(actual) && cantidad < 10) {
try {
const descodificado = decodeURIComponent(actual);
if (descodificado === actual) break; // Sin cambios
actual = descodificado;
cantidad++;
} catch (e) {
break;
}
}
return cantidad;
}
// Uso
console.log(contarCapas('Hello%20World')); // 1
console.log(contarCapas('Hello%2520World')); // 2
console.log(contarCapas('Hello%252520World')); // 3
Solución #3: Detectar y advertir
function descodificarConAdvertencia(str) {
const capas = contarCapas(str);
if (capas > 1) {
console.warn(`Codificación de múltiples capas detectada: ${capas} capas`);
}
return descodificarCompletamente(str);
}
Prevención
Evitar doble codificación:
// ❌ No hagas esto
const yaCodificado = encodeURIComponent(entradaUsuario);
const dobleCodificado = encodeURIComponent(yaCodificado); // ¡Mal!
// ✅ Codifica solo una vez
const codificado = encodeURIComponent(entradaUsuario);
// ✅ O verifica si ya está codificado
function codificarUnaVez(str) {
// Verificación simple: si contiene %, asume que está codificado
if (/%[0-9A-Fa-f]{2}/.test(str)) {
return str; // Ya codificado
}
return encodeURIComponent(str);
}
Prueba rápida
const pruebasMulticapa = [
{ input: 'Hello%20World', layers: 1 },
{ input: 'Hello%2520World', layers: 2 },
{ input: '%25252525', layers: 4 }, // %25 codificado 4 veces
];
pruebasMulticapa.forEach(({ input, layers }) => {
const detectado = contarCapas(input);
console.assert(detectado === layers, `Falló: esperado ${layers}, obtenido ${detectado}`);
});
Error #4: Confusión de caracteres reservados
El problema
No saber qué caracteres están reservados lleva a decisiones incorrectas de codificación/descodificación.
Errores comunes:
Codificar ? en una cadena de consulta // ¡Mal! ? es el delimitador de consulta
No codificar & en un valor // ¡Mal! & separa parámetros
Codificar / en una ruta // ¡Generalmente mal! / es el separador de ruta
Qué ocurre
// Mal: codificar el delimitador de consulta
const urlMala = `/search${encodeURIComponent('?q=test')}`;
// → /search%3Fq%3Dtest (¡el ? está codificado!)
// Mal: no codificar & en un valor
const nombre = 'Tom & Jerry';
const urlMala = `/search?query=${nombre}`;
// → /search?query=Tom & Jerry
// El navegador interpreta como: query=Tom y un parámetro llamado "Jerry"
// Correcto:
const urlBuena = `/search?query=${encodeURIComponent(nombre)}`;
// → /search?query=Tom%20%26%20Jerry
La causa raíz
- Confusión sobre la estructura de URL
- Función de codificación incorrecta (
encodeURIvsencodeURIComponent) - Construcción manual de URL sin entender caracteres reservados
Caracteres reservados en URLs
| Carácter | Significado | ¿Codificar en valores? |
|---|---|---|
: | Separador protocolo/puerto | Sí (en valores) |
/ | Separador de ruta | No (en rutas), Sí (en valores) |
? | Inicio de cadena de consulta | No (como delimitador), Sí (en valores) |
# | Identificador de fragmento | No (como delimitador), Sí (en valores) |
& | Separador de parámetros | No (como separador), Sí (en valores) |
= | Separador clave-valor | No (como separador), Sí (en valores) |
@ | Separador de info de usuario | Sí (usualmente) |
La solución
Solución #1: Usar la función de codificación correcta
// Para codificar URLs COMPLETAS
const urlCompleta = 'https://example.com/ruta con espacios/archivo.html';
const codificado = encodeURI(urlCompleta);
// → 'https://example.com/ruta%20con%20espacios/archivo.html'
// Nota: /, :, ? NO están codificados
// Para codificar COMPONENTES de URL (valores de consulta, segmentos de ruta)
const valor = 'hello/world?test=value';
const codificado = encodeURIComponent(valor);
// → 'hello%2Fworld%3Ftest%3Dvalue'
// Nota: TODOS los caracteres especiales están codificados
Solución #2: Construir URLs apropiadamente
// ❌ Forma incorrecta
const busqueda = 'hello & goodbye';
const url = '/search?q=' + busqueda; // Se rompe con &
// ✅ Forma correcta - codificar el valor
const url = '/search?q=' + encodeURIComponent(busqueda);
// ✅ Mejor - usar API de URL
const url = new URL('/search', window.location.origin);
url.searchParams.set('q', busqueda); // Codificación automática
console.log(url.href);
Solución #3: Analizar URLs correctamente
// ❌ Incorrecto - análisis manual
const consulta = window.location.search; // ?name=Tom%20%26%20Jerry
const valor = consulta.split('=')[1]; // 'Tom%20%26%20Jerry'
// Si olvidas descodificar, mostrarás la versión codificada
// ✅ Correcto - usar API de URL
const params = new URLSearchParams(window.location.search);
const valor = params.get('name'); // Descodificado automáticamente: 'Tom & Jerry'
Prevención
Usar utilidades de URL:
// Node.js o navegadores modern os
const { URL, URLSearchParams } = require('url'); // Node.js
// O simplemente usa URL y URLSearchParams globales en navegadores
// Construir URLs de forma segura
const url = new URL('https://example.com/search');
url.searchParams.append('query', 'hello & goodbye');
url.searchParams.append('page', '1');
console.log(url.toString());
// → https://example.com/search?query=hello+%26+goodbye&page=1
Prueba rápida
const pruebasCaracteresReservados = [
{ char: '&', desc: 'Ampersand' },
{ char: '=', desc: 'Igual' },
{ char: '?', desc: 'Interrogación' },
{ char: '#', desc: 'Almohadilla' },
{ char: '/', desc: 'Barra' },
];
pruebasCaracteresReservados.forEach(({ char, desc }) => {
const valor = `antes${char}despues`;
const codificado = encodeURIComponent(valor);
const descodificado = decodeURIComponent(codificado);
console.log(`${desc} (${char}):`);
console.log(` Original: ${valor}`);
console.log(` Codificado: ${codificado}`);
console.log(` Descodificado: ${descodificado}`);
console.assert(descodificado === valor, `${desc} falló ida y vuelta`);
});
Error #5: Usar funciones/métodos de descodificación incorrectos
El problema
Diferentes lenguajes y frameworks tienen diferentes funciones de descodificación. Usar la incorrecta produce resultados incorrectos.
Errores comunes
JavaScript:
// ❌ Incorrecto para parámetros de consulta
decodeURI('hello%20world%26test');
// → 'hello world%26test' (no descodifica &)
// ✅ Correcto
decodeURIComponent('hello%20world%26test');
// → 'hello world&test'
Python:
# ❌ Incorrecto - quote() en vez de unquote()
from urllib.parse import quote
result = quote('hello%20world')
# → 'hello%2520world' (¡doble codificado!)
# ✅ Correcto
from urllib.parse import unquote
result = unquote('hello%20world')
# → 'hello world'
PHP:
// Los signos más (+) representan espacios en datos de formulario
$codificado = 'hello+world';
// ❌ urldecode() trata + como espacio
$resultado = urldecode($codificado);
// → 'hello world'
// ✅ Usa rawurldecode() para mantener + como literal
$resultado = rawurldecode($codificado);
// → 'hello+world'
// O usa urldecode() si + debe ser espacio (datos de formulario)
La solución
Solución #1: Conoce tus funciones
JavaScript:
decodeURI()- para URLs enteras (no descodifica&,=,?, etc.)decodeURIComponent()- para partes de URL (descodifica todo)
Python:
urllib.parse.unquote()- descodificación estándarurllib.parse.unquote_plus()- descodifica + como espacio (para datos de formulario)
PHP:
urldecode()- descodifica + como espaciorawurldecode()- no descodifica +
Solución #2: Manejar signos más correctamente
// Si trabajas con datos codificados de formulario donde + significa espacio:
function descodificarDatosFormulario(str) {
return decodeURIComponent(str.replace(/\+/g, ' '));
}
// Uso
descodificarDatosFormulario('hello+world'); // → 'hello world'
decodeURIComponent('hello+world'); // → 'hello+world' (+ no descodificado)
Solución #3: Probar tu función de descodificación
const cadenasprueba = [
'hello%20world', // Espacio
'hello+world', // Más
'hello%2Bworld', // Más codificado
'test%26value', // Ampersand
'%E4%B8%AD%E6%96%87', // UTF-8
];
cadenasprueba.forEach(str => {
console.log(`Entrada: ${str}`);
console.log(`decodeURI: ${decodeURI(str)}`);
console.log(`decodeURIComponent: ${decodeURIComponent(str)}`);
console.log('---');
});
Prevención
Crear funciones envolventes:
// Estandarizar descodificación en tu aplicación
function descodificarParametroSeguro(str) {
if (!str) return '';
try {
// Reemplazar + con espacio para datos de formulario, luego descodificar
return decodeURIComponent(str.replace(/\+/g, ' '));
} catch (e) {
console.error('Error de descodificación:', e);
return str; // Devolver original en error
}
}
// Usar consistentemente
const consultaUsuario = descodificarParametroSeguro(params.get('q'));
Prueba rápida
// Probar todas las funciones de descodificación con la misma entrada
const entradaPrueba = 'hello%20world%26test';
console.log('Probando:', entradaPrueba);
console.log('decodeURI: ', decodeURI(entradaPrueba));
console.log('decodeURIComponent:', decodeURIComponent(entradaPrueba));
console.log('Esperado: hello world&test');
Lista de verificación de depuración
Cuando encuentres problemas de descodificación de URL, usa esta lista:
- ¿Codificación válida? Verifica secuencias porcentuales malformadas (
%ZZ,%2) - ¿Charset correcto? Verifica UTF-8 en toda la pila
- ¿Una o múltiples capas? Cuenta cuántas veces está codificado
- ¿Caracteres reservados? Asegura manejo apropiado de
&,=,?, etc. - ¿Función correcta? ¿Usando
decodeURIComponent()vsdecodeURI()? - ¿Signos más? ¿Deben ser espacios o
+literal? - ¿Manejo de errores? ¿Envuelto en try-catch?
- ¿Saneado? ¿Validado ysaneado después de descodificar?
Herramientas para depuración
- Nuestro descodificador de URL: Herramienta online gratuita con detección de múltiples capas
- DevTools del navegador:
console.log(decodeURIComponent(str)) - Analizador de URL: Visualiza componentes de URL
- Visualizadores hexadecimales: Ver valores de bytes reales
Resumen
| Error | Solución rápida | Prevención |
|---|---|---|
| #1 Formato incorrecto | Validar antes de descodificar | Usar funciones de codificación apropiadas |
| #2 Discrepancia de codificación | Estandarizar en UTF-8 | UTF-8 en todas partes |
| #3 Descodificación incompleta | Descodificar hasta estabilizar | Evitar doble codificación |
| #4 Caracteres reservados | Usar encodeURIComponent() | Usar API de URL |
| #5 Función incorrecta | Conoce tus funciones | Crear envoltorios |
Al entender y solucionar estos 5 errores comunes, manejarás la descodificación de URL como un profesional. Recuerda: valida entradas, descodifica con cuidado, y siempre prueba con casos extremos.
¡Evita estos errores instantáneamente con nuestra herramienta gratuita de descodificación de URL que maneja todos los casos extremos automáticamente!