Exportas una tabla de un sitio web alemán. La columna de ingresos muestra "1.234,56 €".
Lo pegas en Excel. Se convierte en "1.23456E" o, peor, una fecha.
Bienvenido al problema del formato numérico internacional.
El Problema Central: ¿Qué Significa "1.234"?
En EE.UU. y el Reino Unido: 1.234 = uno punto dos tres cuatro (decimal)
En Alemania, España y la mayor parte de Latinoamérica: 1.234 = mil doscientos treinta y cuatro
Mismos caracteres. Valores completamente diferentes.
Cuando extraes datos de sitios web internacionales, no puedes asumir con qué formato estás trabajando. Y equivocarte significa que tu análisis estará desfasado por órdenes de magnitud.
La Ambigüedad de Tres Dígitos
El caso más difícil es exactamente tres dígitos después de un separador.
| Entrada | Interpretación EE.UU. | Interpretación UE |
|---|---|---|
| 1.234 | 1.234 (decimal) | 1.234 (miles) |
| 1,234 | 1,234 (miles) | 1.234 (decimal) |
| 1.234.567 | Inválido | 1.234.567 |
| 1,234,567 | 1,234,567 | Inválido |
Con dos dígitos después, es claramente decimal: "1.23" o "1,23".
Con cuatro o más dígitos después, es claramente decimal: "3.14159".
¿Con tres dígitos? Podría ser cualquiera.
El Algoritmo de Detección
Aquí hay un enfoque heurístico que maneja la mayoría de los casos reales:
function normalizeNumberString(value) {
if (typeof value !== "string") return value;
const v = value.trim();
if (!v || !/[0-9]/.test(v)) return value;
// Paso 1: Limpiar símbolos de moneda, porcentajes, espacios
let cleaned = v
.replace(/ /g, " ")
.replace(/^(USD|EUR|GBP|R\$|MXN)\s*/i, "")
.replace(/[$€£¥₹₽₩]/g, "")
.replace(/%/g, "")
.replace(/\s+/g, "");
// Paso 2: Manejar signos negativos
const isNegative = cleaned.match(/^-/);
if (isNegative) {
cleaned = cleaned.substring(1).trim();
}
// Paso 3: ¿Solo dígitos? Listo
if (/^\d+$/.test(cleaned)) {
return isNegative ? `-${cleaned}` : cleaned;
}
// Paso 4: Detectar separadores
const hasComma = cleaned.includes(",");
const hasDot = cleaned.includes(".");
// Paso 5: Aplicar heurística
let decimalSeparator = ".";
let thousandsSeparator = ",";
if (hasComma && hasDot) {
// Ambos presentes: el último es decimal
const lastComma = cleaned.lastIndexOf(",");
const lastDot = cleaned.lastIndexOf(".");
if (lastComma > lastDot) {
decimalSeparator = ",";
thousandsSeparator = ".";
}
} else if (hasComma && !hasDot) {
const parts = cleaned.split(",");
if (parts.length === 2 && parts[1].length <= 2) {
// "1,23" → decimal
decimalSeparator = ",";
} else {
// "1,234" o "1,234,567" → miles
decimalSeparator = null;
thousandsSeparator = ",";
}
} else if (hasDot && !hasComma) {
const parts = cleaned.split(".");
if (parts.length === 2) {
const afterDot = parts[1];
if (afterDot.length <= 2 || afterDot.length >= 4) {
// "1.23" o "3.14159" → decimal
decimalSeparator = ".";
} else {
// "1.234" → MILES (decisión pragmática para tablas)
decimalSeparator = null;
thousandsSeparator = ".";
}
} else {
// Múltiples puntos → miles
decimalSeparator = null;
thousandsSeparator = ".";
}
}
// Paso 6: Limpiar separadores de miles
if (thousandsSeparator) {
cleaned = cleaned.replace(new RegExp(`\\${thousandsSeparator}`, "g"), "");
}
// Paso 7: Normalizar decimal a punto
if (decimalSeparator && decimalSeparator !== ".") {
cleaned = cleaned.replace(decimalSeparator, ".");
}
// Paso 8: Validar
const num = Number(cleaned);
if (!Number.isFinite(num)) return value;
return isNegative ? `-${cleaned}` : cleaned;
}
Por Qué Tres Dígitos = Miles
La decisión clave: cuando vemos exactamente tres dígitos después de un único separador (como "1.234"), lo interpretamos como miles, no como decimal.
¿Por qué? En tablas HTML:
- Los datos financieros con miles son extremadamente comunes: $1.234, €1.234
- Los decimales con exactamente tres dígitos son raros en la práctica
- Los datos científicos con tres decimales (como "3.141") generalmente se escriben como "3.14159" o simplemente "3.14"
Esta heurística es una decisión pragmática que funciona para la mayoría de las tablas del mundo real.
La Matriz Completa de Formatos
| Entrada | Interpretación | Salida Normalizada |
|---|---|---|
| 1.234,56 | Decimal UE | 1234.56 |
| 1,234.56 | Decimal EE.UU. | 1234.56 |
| 1.234.567,89 | Miles UE + decimal | 1234567.89 |
| 1,234,567.89 | Miles EE.UU. + decimal | 1234567.89 |
| 1.234 | Miles | 1234 |
| 1,234 | Miles | 1234 |
| 3.14 | Decimal | 3.14 |
| 3,14 | Decimal | 3.14 |
| 3.14159 | Decimal (4+ dígitos) | 3.14159 |
| $ 1.200,50 | Moneda + UE | 1200.50 |
| -5.000 | Miles negativos | -5000 |
| 12.5% | Porcentaje | 12.5 |
Manejando Códigos de Moneda
Los datos internacionales vienen con prefijos y sufijos de moneda. Límpialos primero:
// Remover códigos de moneda ANTES de símbolos
.replace(/^(USD|EUR|GBP|JPY|CHF|CAD|AUD|CNY|INR|BRL|R\$|MXN|KRW)\s*/i, "")
// Luego remover símbolos
.replace(/[$€£¥₹₽₩₪฿₫₴₦]/g, "")
El orden importa: "R$" (Real Brasileño) contiene "$", así que remover códigos de moneda primero evita coincidencias parciales.
Cuándo la Heurística Falla
Ninguna heurística es perfecta. Obtendrás resultados incorrectos cuando:
- Datos científicos con exactamente 3 decimales: "3.141" se convierte en "3141"
- Precios menores a $10 con 3 decimales: "$1.234" se convierte en "$1234"
- Formatos mixtos en una columna: Algunas filas UE, otras EE.UU.
Para el caso 3, necesitas detección de formato a nivel de columna:
function detectColumnFormat(values) {
let euIndicators = 0;
let usIndicators = 0;
for (const v of values) {
if (/\d\.\d{3},\d{2}$/.test(v)) euIndicators++;
if (/\d,\d{3}\.\d{2}$/.test(v)) usIndicators++;
}
if (euIndicators > usIndicators) return "eu";
if (usIndicators > euIndicators) return "us";
return "auto"; // Usar heurística por celda
}
Perfiles de Exportación para Diferentes Regiones
Cuando conoces el país de origen, puedes evitar la ambigüedad por completo usando perfiles específicos por región:
- Formato europeo: Asume coma como separador decimal, usa punto y coma como delimitador CSV
- Formato EE.UU./UK: Asume punto como separador decimal, usa coma como delimitador CSV
HTML Table Exporter incluye perfiles preconfigurados para ambos formatos, más perfiles especializados para herramientas como Pandas y DuckDB.
Errores Comunes
No Perder Precisión
// Mal: pierde precisión en números grandes
const num = parseFloat("12345678901234567890");
// 12345678901234567000 (límite de JavaScript)
// Mejor: mantener como string hasta el cálculo final
const cleaned = normalizeNumberString(value);
// Solo parseFloat cuando necesités calcular
Cuidado con las Entidades HTML
Las tablas web a veces contienen (espacio sin separación) dentro de números:
// "1 234 567" debería convertirse en "1234567"
.replace(/ /g, " ")
.replace(/\s+/g, "")
Preservar el Original Ante la Duda
Si el valor no parece un número, devuélvelo sin cambios:
if (!/[0-9]/.test(v)) return value;
// ...
if (!Number.isFinite(num)) return value;
Es mejor dejar "N/A" como "N/A" que intentar parsearlo.
Resumen
| Escenario | Detección | Notas |
|---|---|---|
| Ambos . y , | El último = decimal | Universal |
| Solo coma, ≤2 dígitos después | Decimal | "1,23" |
| Solo coma, 3+ dígitos después | Miles | "1,234" |
| Solo punto, ≤2 o 4+ dígitos | Decimal | "1.23", "3.14159" |
| Solo punto, exactamente 3 dígitos | Miles | "1.234" |
El caso de tres dígitos es la decisión pragmática. Funciona para la mayoría de los datos reales. Ante la duda, usa un perfil de exportación que coincida con tu región de origen.
Para más sobre copiar datos de sitios web a Excel sin problemas de formato, consulta nuestra guía sobre copiar tablas de sitios web a Excel.
¿Necesitas exportaciones de números limpias sin adivinanzas? Conoce más en gauchogrid.com/es/html-table-exporter o pruébalo gratis en la Chrome Web Store.
Top comments (0)