Desarrollo

Entidades HTML y los cinco caracteres que de verdad necesitan una

Una entidad es un carácter escrito en ASCII puro. Necesitas cinco, y el resto lo puedes escribir directamente en el archivo.

Una entidad HTML es una forma de escribir un carácter usando solo ASCII puro, para que el navegador muestre ese carácter en lugar de leerlo como markup. & produce un ampersand, < produce un signo de menor que, — produce una raya. En una página UTF-8 — que hoy es cualquier página — solo cinco caracteres llegan a necesitarlo, y solo dos lo necesitan en todas partes. Los acentos, las flechas, los símbolos de moneda y los emoji los puedes escribir directamente en el archivo, y las entidades que ves para ellos en los tutoriales viejos son restos de una época en la que no podías.

Las tres formas de escribir el mismo carácter

Toda entidad es una referencia de entidad: un ampersand, un nombre o un número, y un punto y coma. Hay tres formas, y los navegadores las tratan como idénticas.

Las tres formas con ampersand producen el mismo carácter. Las referencias numéricas existen para todo lo que Unicode puede expresar; las que llevan nombre, solo para la lista corta, y por eso los escapadores terminan recurriendo a los números. El encoder de aquí mantiene una lista deliberadamente corta — unos setenta nombres que los parsers antiguos y los clientes de correo reconocen de forma fiable — y escribe todo lo demás como número.

¿Hace falta el punto y coma final?

HTML5 decodifica alrededor de cien referencias con nombre antiguas incluso sin el punto y coma final, por compatibilidad con páginas escritas en 1998. Así que &copy en el texto del cuerpo se muestra como ©, lo hayas querido o no. Esto es una trampa, no una función: los parsers de XML, SVG y RSS rechazan lo mismo de plano, y XML solo define cinco entidades con nombre en total — amp, lt, gt, quot y apos. Escribe el punto y coma.

¿Qué caracteres tienes que escapar de verdad?

Cinco, y solo porque si no el parser los leería como algo que no es texto.

El caso del ampersand es el que genera discusión. AT&T se ve bien, porque &T no es una referencia. Salt & Pepper también. Pero un query string como ?page=2&copy=full contiene &copy, y en texto visible eso se convierte en ?page=2©=full. Dentro del valor de un atributo, HTML5 tiene una regla especial que te salva — un nombre sin punto y coma seguido de = o de una letra o un dígito se deja tal cual — que es exactamente el tipo de excepción de la que no quieres depender. Escapa el ampersand y deja de pensar en ello.

Hacerlo a ojo sobre un bloque de texto es lento y es fácil que salga a medias. Un escapador con modo mínimo convierte esos cinco y deja todo lo demás como caracteres reales, que es la salida correcta para una página UTF-8 y mantiene el código fuente legible.

Lo que puedes dejar de escapar

Si tu documento declara <meta charset="utf-8"> y está guardado de verdad como UTF-8, escribe el carácter. Una raya, una ı turca, un ₹, un 🙂 — todos funcionan como texto literal, y todos son más cortos, más legibles y menos propensos a error que &#8212; y compañía.

Las entidades siguen siendo la mejor opción para los caracteres que son invisibles o ambiguos en el código fuente, porque nadie puede revisar lo que no ve:

Vale la pena entender bien el espacio de no separación. Los espacios normales se colapsan: diez seguidos se muestran como uno. Un espacio de no separación no se colapsa, y no permite un salto de línea. Por eso 5&nbsp;kg nunca termina con el 5 en una línea y el kg en la siguiente, y es el arreglo correcto para un número separado de su unidad. Escribir uno a propósito es el caso fácil. Encontrar los que otro dejó en tus datos es el difícil, y es otro trabajo.

¿Por qué mi página muestra &amp; en vez de &?

Porque algo escapó un texto que ya estaba escapado. El ampersand de &amp; se escapó una segunda vez y se convirtió en &amp;amp;, y el navegador decodifica fielmente una capa e imprime el resto.

Es un problema de pipeline, no una errata. Un formulario escapa la entrada al guardar, la plantilla la vuelve a escapar al renderizar, y la segunda pasada no puede saber que la primera ocurrió. El daño se acumula de forma predecible, así que decodificar la string una y otra vez hasta que deje de cambiar te dice cuántas capas hay. Después quita el paso de escapado que no debería estar ejecutándose. Escapar una sola vez, en el punto de salida, es la única disposición que no termina produciendo esto.

Entidades, URL encoding y escapado JSON: ¿qué va dónde?

Tres sistemas de escapado distintos, tres alfabetos distintos, aplicados en tres momentos distintos. Un espacio es &#32; en HTML, %20 en una URL y \u0020 en JSON, y usar uno donde va otro produce texto que parece casi correcto y está roto.

El solapamiento que más daño hace es un enlace. El href es HTML, así que se escapa con entidades; lo que va dentro es una URL, así que sus propios caracteres reservados necesitan percent-encoding primero. Las dos cosas, en ese orden. Si alguna vez viste un enlace romperse en el momento en que alguien metió un espacio o un signo más en un parámetro, las reglas detrás del percent-encoding son la mitad de la respuesta que las entidades no cubren.

JSON es otro mundo aparte. Las entidades HTML no significan nada dentro de una string JSON — son solo caracteres — y los escapes \u de JSON no significan nada para un parser HTML. Cuando una respuesta llega como una única línea ininterrumpida de secuencias escapadas, formatearla antes de intentar leerla te dirá en segundos si el problema son los datos o la codificación.

¿Escapar basta para detener un XSS?

En el texto HTML y en los valores de atributo entre comillas, escapar esos cinco es lo que impide que el markup inyectado sea markup. Fuera de esos dos lugares te sirve mucho menos de lo que parece. Escapar hace que el markup se muestre como texto; no elimina nada, y solo es correcto para el contexto en el que estás escribiendo. Cuatro lugares donde no basta:

Y si quieres conservar algunas etiquetas y descartar otras — permitir <b>, rechazar <script> — eso es sanitizar, y necesita un parser de verdad con una allowlist. Escapar es todo o nada por diseño.

¿Por qué mi emoji salió como dos entidades rotas?

Los caracteres por encima de U+FFFF — emoji, CJK poco frecuente, casi todas las escrituras históricas — se guardan en JavaScript como dos unidades de código. Un escapador que recorre una string índice a índice emite dos referencias como &#55357;&#56832;, y ningún parser puede volver a juntarlas; HTML convierte cada mitad en un carácter de reemplazo. Un escapador correcto itera por code point, así que una cara sonriente se convierte en una sola referencia, &#x1F600;. Si alguna vez viste una fila de rombos negros donde debería haber un emoji, normalmente es por esto.

Las referencias numéricas entre &#128; y &#159; fallan de otra manera. Los parsers HTML deliberadamente no representan esas referencias como los caracteres de control Unicode que nombran; las mapean a los caracteres de Windows-1252 que los autores querían en los años noventa, así que &#151; sale como una raya. Correcto según el estándar, nada obvio, y una razón más para escribir el carácter en lugar del número.

Si ahora mismo estás mirando una string llena de &amp;, el encoder y decoder de entidades de aquí la puede recorrer en el sentido contrario, y decodificar otra vez hasta que el texto deje de cambiar te dice cuántas capas se aplicaron. También hace la dirección de ida, con el modo mínimo que quieres para cualquier cosa que vaya a una página UTF-8. Una cosa que no te va a decir: un nombre de entidad que no reconoce sale sin cambios en lugar de marcado, exactamente como lo mostraría un navegador, así que un nombre mal escrito parece que se decodificó bien.

Las entidades son un caso de un truco más amplio: tomar algo con lo que un parser se atragantaría y reescribirlo en un alfabeto seguro. Incrustar una imagen como Base64 es la misma idea aplicada a lo binario, con el mismo inconveniente — es más grande que lo que reemplaza, y vale la pena saber cuándo ese intercambio es malo.

Preguntas frecuentes

¿Qué son las entidades HTML?

Son caracteres escritos en ASCII puro para que el navegador los muestre en lugar de leerlos como markup. Cada una empieza con un ampersand y termina con un punto y coma, y puede usar un nombre (&amp;), un número decimal (&#38;) o un número hexadecimal (&#x26;). Los tres ejemplos producen un ampersand.

¿Qué caracteres tengo que escapar en HTML?

El ampersand y el menor que siempre, el mayor que por convención, y el carácter de comilla con el que envolviste el valor de un atributo. En una página UTF-8 no hace falta nada más: las letras acentuadas, las rayas, los símbolos de moneda y los emoji se pueden escribir directamente en el código fuente.

¿Por qué mi página muestra &amp; en vez de &?

El texto se escapó dos veces, así que el ampersand de la primera entidad se volvió a escapar. Normalmente son dos etapas de un pipeline haciendo las dos el mismo trabajo, como un formulario que escapa al guardar y una plantilla que escapa al renderizar. Se arregla escapando una sola vez, en el punto de salida.

¿Es &nbsp; lo mismo que un espacio normal?

No. Un espacio de no separación es U+00A0: no se colapsa cuando aparecen varios seguidos, y no permite un salto de línea. Eso lo convierte en la forma correcta de mantener un número pegado a su unidad, y en la forma incorrecta de indentar o espaciar un layout.

¿Son las entidades HTML lo mismo que el URL encoding?

No, son sistemas separados que se aplican en momentos distintos. Un espacio es &#32; en HTML y %20 en una URL, y JSON usa \u0020 para el mismo carácter. Un enlace necesita los dos: primero se hace percent-encoding de la URL, y luego se escapa con entidades el atributo entero.

Última actualización 19 de septiembre de 2026