Números mágicos en programación: qué son y cómo evitarlos
Un número mágico es un número que aparece en medio del código sin que se explique su origen. Falta un nombre que lo describa. Si no conoces la lógica de negocio, te quedas bloqueado pensando qué significa esa cifra salida de la nada.
Es como encontrar en la nevera un táper con una etiqueta que solo dice «3». ¿Tres días? ¿Tres raciones? ¿El tercero de la semana? Quien lo guardó lo sabía; tú tienes que adivinarlo. Con una etiqueta que diga «Lentejas, del lunes» no hay duda. Evitarlos es uno de los principios del código limpio (clean code).
Cómo identificar un número mágico
Mira este código en JavaScript:
const baseImponible = 12.5;
const pvp = baseImponible + baseImponible * 0.21;
console.log(pvp); // 15.125
Aquí se adivina qué es el 0.21 por dos motivos:
- El nombre de las variables da contexto:
baseImponibleypvp(precio de venta al público) suenan a impuestos. - No hay mucho código alrededor que nos enturbie la vista.
Pero no siempre es así. En medio de una maraña de código, cuesta saber a qué se refiere cada cifra. Y aparecen más problemas:
- No se puede buscar. Si buscas
0.21en el proyecto, encontrarás el IVA, pero quizá también un margen de beneficio o un descuento que casualmente valen lo mismo. - Cambiarlo es arriesgado. Si el valor está repetido en diez sitios, tienes que encontrar los diez y no tocar ninguno que signifique otra cosa.
Esto último no es teórico: el IVA general en España pasó del 18 % al 21 % en 2012. Quien tenía el número repartido por el código tuvo que buscarlo a mano.
Cómo eliminar números mágicos: constantes con nombre
Hagamos un refactor (cambiar cómo está escrito el código sin cambiar lo que hace) del ejemplo anterior:
const IVA_GENERAL = 0.21;
function calcularIva(baseImponible) {
return baseImponible * IVA_GENERAL;
}
function calcularPrecioConIva(baseImponible) {
return baseImponible + calcularIva(baseImponible);
}
console.log(calcularPrecioConIva(12.5)); // 15.125
El resultado es el mismo, pero ahora el código se entiende sin conocer la lógica de negocio:
- El
0.21tiene un nombre que dice qué es, y se puede buscar en todo el proyecto. - Si el tipo cambia, se cambia en un solo sitio.
- Las funciones cuentan qué se calcula, no solo cómo.
Cada número mágico que se queda es un poco de deuda técnica: hoy no rompe nada, pero encarece cada cambio.
Antes
baseImponible * 0.21- Hay que adivinar qué es el 0.21
- Para cambiarlo, buscar a mano en todo el código
Después
baseImponible * IVA_GENERAL- El nombre explica el valor
- Se cambia en un solo sitio
En JavaScript, Java, Python y muchos otros lenguajes, la convención es escribir las constantes en mayúsculas y con guiones bajos (IVA_GENERAL). Así se distinguen de un vistazo de una variable normal. Elegir bien ese nombre es la mitad del trabajo: tienes más pautas en Nombres con sentido.
Si tienes que adivinar qué es un número, necesita un nombre.
Cuándo no hace falta una constante
Sustituir todos los números por constantes también empeora el código. Estos casos suelen entenderse solos:
| Valor | Ejemplo | ¿Constante? |
|---|---|---|
0 y 1 como punto de partida o paso |
let total = 0, i + 1 |
No |
| Primer elemento de una lista | lista[0] |
No |
| Mitad, doble, porcentaje | ancho / 2, tanto * 100 |
Normalmente no |
| Valores que pueden cambiar o se repiten | IVA, número de reintentos, tiempo de espera | Sí |
| Valores con significado de negocio | edad mínima, límite de un plan | Sí |
| Unidades poco obvias | 86400 (segundos de un día) |
Sí: SEGUNDOS_POR_DIA |
La pregunta útil no es «¿hay un número?», sino «¿alguien que lea esto sabrá qué significa y de dónde sale?». Una constante como CERO = 0 no aporta nada: repite el valor sin explicar su sentido.
En los tests es habitual escribir los valores esperados tal cual (expect(precio).toBe(15.125)), porque ahí el número es el dato del ejemplo, no una regla de negocio.
El siguiente paso: enums y objetos de valor
Cuando aparecen varios valores relacionados, una constante suelta se queda corta. En España hay tres tipos de IVA: general (21 %), reducido (10 %) y superreducido (4 %). Puedes agruparlos en un enum, un conjunto cerrado de valores con nombre. JavaScript no tiene enums propios, pero un objeto congelado hace ese papel:
const TipoIva = Object.freeze({
GENERAL: 0.21,
REDUCIDO: 0.10,
SUPERREDUCIDO: 0.04,
});
function calcularPrecioConIva(baseImponible, tipo) {
return baseImponible * (1 + tipo);
}
console.log(calcularPrecioConIva(12.5, TipoIva.GENERAL)); // 15.125
console.log(calcularPrecioConIva(12.5, TipoIva.REDUCIDO)); // 13.750000000000002
console.log(calcularPrecioConIva(12.5, TipoIva.SUPERRDUCIDO)); // NaN
Al ejecutarlo salen dos problemas que una constante no resuelve:
- Una errata pasa sin aviso.
SUPERRDUCIDOno existe, JavaScript devuelveundefinedy el precio acaba enNaN(«no es un número»). - Los decimales fallan. Los ordenadores guardan los decimales de forma aproximada, y por eso sale
13.750000000000002. Con dinero conviene trabajar en céntimos, que son números enteros.
Ese es el momento de pasar a un objeto de valor (value object): un objeto pequeño que representa un concepto del negocio, valida sus datos y sabe hacer sus propias operaciones:
class TipoIva {
static GENERAL = new TipoIva(21);
static REDUCIDO = new TipoIva(10);
static SUPERREDUCIDO = new TipoIva(4);
#porcentaje;
constructor(porcentaje) {
if (porcentaje < 0 || porcentaje > 100) {
throw new RangeError(`Tipo de IVA no válido: ${porcentaje}`);
}
this.#porcentaje = porcentaje;
}
aplicarA(centimos) {
return Math.round(centimos * (100 + this.#porcentaje) / 100);
}
}
console.log(TipoIva.GENERAL.aplicarA(1250)); // 1513 céntimos = 15,13 €
console.log(TipoIva.REDUCIDO.aplicarA(1250)); // 1375 céntimos = 13,75 €
Ahora el número 21 vive en un único sitio, junto a la regla que lo usa, y escribir mal TipoIva.SUPERRDUCIDO.aplicarA(…) lanza un error en lugar de devolver NaN en silencio.
No empieces por aquí. Para un script de veinte líneas, una constante basta. El objeto de valor compensa cuando el concepto se usa en muchos sitios o tiene reglas propias. Sus tres rasgos (igualdad por valor, inmutabilidad y validación al crearlo) los tienes explicados en value objects. El redondeo del ejemplo es el más sencillo posible; en una aplicación real hay que aplicar el que exija la normativa.
Números mágicos en código generado por IA
Los asistentes de IA escriben números mágicos con naturalidad: un setTimeout(…, 30000), un retries: 3 o un if (items.length > 50) sin explicar de dónde salen. El código funciona, así que es fácil aceptarlo sin mirar.
Un criterio práctico al revisar código generado: para cada número que no sea obvio, pregúntate «¿de dónde sale este valor?». Si la respuesta es un requisito, dale nombre. Si nadie lo sabe, probablemente la IA lo ha inventado, y conviene confirmarlo antes de que se quede para siempre. Es una revisión rápida y encaja con la idea de IA Limpia: aprovechar la velocidad sin perder lo que hace el código fácil de entender.
Resumen
- Busca números y textos sueltos cuyo significado haya que adivinar.
- Dales un nombre que explique qué son con una constante.
- Deja tranquilos el
0, el1y los valores obvios. - Si hay varios valores relacionados o reglas propias, agrúpalos en un enum o un objeto de valor.
Para practicarlo, la kata de los puntos de un equipo deja un 3 y un 1 a los que poner nombre: está en katas para practicar TDD y resuelta paso a paso en la kata Total amount of points con TDD. Si te interesa seguir limpiando código, el siguiente paso natural son las funciones: Funciones, código limpio.
Otros «números mágicos» que no son estos
La expresión se usa en más sitios, y conviene no mezclarlos.
El número mágico de un fichero
Muchos formatos de fichero empiezan siempre por los mismos bytes, como una firma. Se llama también número mágico, y es lo que mira un programa para saber qué tipo de fichero tiene delante, aunque la extensión mienta. Es como el matasellos de una carta: dice de dónde viene sin abrirla.
Puedes verlo con xxd, que muestra los bytes en hexadecimal:
head -c 4 logo.png | xxd -p
head -c 4 datos.zip | xxd -p
head -c 2 nota.txt.gz | xxd -p
89504e47
504b0304
1f8b
| Formato | Primeros bytes | Qué se lee |
|---|---|---|
| PNG | 89 50 4E 47 |
.PNG |
ZIP (y también .docx, .xlsx o .jar, que son ZIP por dentro) |
50 4B 03 04 |
PK |
| gzip | 1F 8B |
— |
El comando file de Linux se basa en estas firmas para decir qué es cada fichero:
file logo.png datos.zip nota.txt.gz
logo.png: PNG image data, 192 x 192, 8-bit/color RGB, non-interlaced
datos.zip: Zip archive data, made by v3.0 UNIX, extract using at least v1.0, last modified Nov 08 2026 20:28:18, uncompressed size 5, method=store
nota.txt.gz: gzip compressed data, was "nota.txt", last modified: Sun Nov 8 20:28:17 2026, from Unix, original size modulo 2^32 5
Fíjate en la diferencia: aquí el número está documentado en la especificación del formato y todo el mundo sabe qué significa. En tu código, 0x89504E47 suelto seguiría siendo un número mágico; const FIRMA_PNG = 0x89504E47 ya no.
Los números mágicos de la física
En física nuclear, los números mágicos son las cantidades de protones o de neutrones que hacen un núcleo atómico especialmente estable: 2, 8, 20, 28, 50, 82 y 126. Maria Goeppert Mayer y J. Hans D. Jensen, cada uno por su lado, los explicaron con el modelo de capas; compartieron la mitad del Nobel de Física de 1963 (la otra mitad fue para Eugene Wigner). No tienen nada que ver con la programación, salvo el nombre.
Y si buscabas los números «mágicos» de la numerología, este no es el sitio: la numerología no tiene nada que ver con ninguno de estos.
Fuentes
- Maria Goeppert Mayer, The shell model, conferencia Nobel del 12 de diciembre de 1963, apartado «Magic numbers»: da los números 2, 8, 20, 28, 50, 82 y 126, iguales para neutrones y protones. Leída el 8 de noviembre de 2026 en una copia en PDF de la conferencia (Universidad de Atenas).
- Reparto del Nobel de Física de 1963: la mitad para Eugene Wigner y la otra mitad, compartida, para Maria Goeppert Mayer y J. Hans D. Jensen «por sus descubrimientos sobre la estructura de capas nuclear». Comprobado el 8 de noviembre de 2026 en la página oficial del premio.
- Firmas de fichero: comprobadas el 8 de noviembre de 2026 con
xxd2025-11-26 yfile5.46 en Linux, sobre un PNG, un ZIP y un gzip creados para la prueba. - Martin Fowler, Replace Magic Literal, en el catálogo de Refactoring (2.ª edición). Comprobado el 7 de noviembre de 2026: el nombre y el alias Replace Magic Number with Symbolic Constant figuran en esa página.
- Tipos de IVA en España (21 %, 10 % y 4 %): administracion.gob.es, actualizada el 20 de marzo de 2026.
- Subida del IVA general del 18 % al 21 % desde el 1 de septiembre de 2012: Real Decreto-ley 20/2012 (BOE), apartado V del preámbulo.
- Todos los ejemplos de código se han ejecutado con Node.js 24.21.0; las salidas de los comentarios son las reales.