Ir al contenido
Allin

Los emoji son más difíciles de lo que parecen: por qué «basta con quitarlos» no tiene respuesta en una línea

Publicado el 29/9/2025 · 13 min de lectura · Herramientas de texto e idioma

Daniel Okonkwo

Daniel OkonkwoDesarrollador front-end y redactor de Tecnología en Allin

Rendimiento web · Formatos de archivo

Verificado con 5 fuentes

Ver perfil
En resumen

No existe una expresión regular simple para quitar emoji, porque no existe una definición simple de qué es un emoji. Una sola imagen en pantalla puede ser un punto de código (😀 es U+1F600), un carácter base más un selector de variante invisible (❤️ es U+2764 U+FE0F), una base más un modificador de tono de piel (👍🏽 es U+1F44D U+1F3FD), una cadena de caracteres unidos por juntores de ancho cero (👨‍👩‍👧‍👦 son siete puntos de código y once unidades UTF-16), un par de indicadores regionales (🇵🇹 es U+1F1F5 U+1F1F9), o una secuencia de etiquetas de siete puntos de código. Las propiedades que se suelen usar tampoco encajan: \p{Emoji} reconoce los dígitos ASCII del 0 al 9 y también # y *, así que usarla para limpiar una frase borra los números; lo ejecutamos y «2026 revenue up 40%» volvió como « revenue up %». \p{Extended_Pictographic} es la propiedad correcta para las imágenes pero no cubre tonos de piel ni banderas. La regla que funciona es segmentar el texto en grupos de grafemas y eliminar grupos enteros. La misma regla arregla el truncado, donde cortar en un índice de unidad de código puede partir una familia de cuatro en dos personas sin relación o dejar medio carácter que acaba como U+FFFD.

Un emoji visible puede valer un punto de código o catorce unidades UTF-16. Lanzamos tres expresiones regulares populares sobre una frase real y cada una falló de otra forma; una borró los dígitos. Aquí está el porqué, qué propiedad Unicode responde a qué pregunta, y la regla de grupos de grafemas que sí funciona.

Una imagen, seis construcciones distintas

Pregunta a un programa cuánto mide un emoji y obtendrás tres respuestas según la unidad. Medimos los mismos ocho emoji de tres formas: en unidades de código UTF-16, que es lo que devuelve .length en JavaScript; en puntos de código; y en grupos de grafemas, que es lo que un lector llamaría un carácter. El recuento en grafemas es 1 para todos ellos. Los otros dos van de 1 a 14.

El caso simple es un único punto de código: 😀 es U+1F600, dos unidades UTF-16 porque está fuera del plano multilingüe básico. Luego se complica. ❤️ es U+2764 seguido de U+FE0F, un selector de variante invisible cuyo único cometido es decir «dibuja el carácter anterior como imagen, no como símbolo». 1️⃣ son tres puntos de código: el dígito ASCII 1, el mismo selector y U+20E3 COMBINING ENCLOSING KEYCAP. 👍🏽 es un pulgar arriba seguido de U+1F3FD, un modificador de tono que es en sí mismo un carácter válido y se dibuja como un cuadrado de color cuando va solo.

Los casos difíciles son las secuencias. 👨‍👩‍👧‍👦 son un hombre, una mujer, una niña y un niño separados por tres copias de U+200D ZERO WIDTH JOINER: siete puntos de código, once unidades UTF-16, una imagen. 👩‍💻 es una mujer más un portátil unidos igual, y 🏴‍☠️ es una bandera negra unida a una calavera con un selector de variante al final. Las banderas funcionan de otra manera todavía: 🇵🇹 no es un carácter de bandera sino el par de indicadores regionales U+1F1F5 U+1F1F9, que son las letras P y T en un alfabeto especial, y 🏴󠁧󠁢󠁳󠁣󠁴󠁿 es una bandera negra seguida de cinco caracteres de etiqueta invisibles que deletrean un código de subdivisión, más un terminador: siete puntos de código, catorce unidades UTF-16.

La propiedad que parece correcta y borra tus números

JavaScript, y todo motor de expresiones regulares con escapes de propiedades Unicode, ofrece \p{Emoji}. Es la elección evidente y es la equivocada. Emoji es una propiedad de carácter que significa «este carácter puede participar en un emoji», y los dígitos del 0 al 9 participan: son las bases de las secuencias de tecla. También # y *, por la misma razón.

La consecuencia es fácil de demostrar. Toma la frase «2026 revenue up 40%», que no contiene ningún emoji, y ejecuta replace(/\p{Emoji}/gu, ""). Devuelve « revenue up %». Todos los dígitos han desaparecido; el signo de por ciento, que no es un componente de emoji, sobrevive. La misma expresión convierte «Order #7 shipped ✅» en «Order shipped ». Ejecuta \p{Extended_Pictographic} sobre la primera frase y vuelve intacta, que es el comportamiento que querías.

Pero Extended_Pictographic tampoco es una respuesta completa, porque significa «este carácter es un pictograma» y varias partes de un emoji no lo son. Probamos cada parte: el modificador de tono U+1F3FD no es Extended_Pictographic, ni tampoco los indicadores regionales que forman una bandera. Ejecuta Extended_Pictographic sola sobre una frase con 🇵🇹 y 👍🏽 y la bandera sobrevive entera mientras el pulgar desaparece y deja su tono detrás, como un cuadrado de color solitario.

Emoji_Presentation es la propiedad que responde a «¿esto se dibujará como imagen?»

Algunos caracteres se dibujan por defecto como imagen en color y otros por defecto como texto monocromo. Esa distinción es exactamente lo que registra Emoji_Presentation. Sondeamos un conjunto de caracteres y el corte es limpio: 😀, ⌚, 👍 y 🀄 tienen Emoji_Presentation, así que se dibujan como imagen sin nada más. ❤, ☺, ▶, ✔, ©, ® y ™ no; solos se dibujan como glifos de texto en la fuente que los rodea.

Para eso existe el selector de variante. U+FE0F es la petición de dibujar el carácter anterior como emoji, y es lo que convierte ❤ en ❤️ y ▶ en ▶️. También es invisible, sin peso en la representación mental del lector, y un carácter real en la cadena: de ahí el U+FE0F huérfano que deja una regex que quita el pictograma pero no su selector. Lo medimos: «done ❤️» sin los Extended_Pictographic vuelve como seis puntos de código: d, o, n, e, espacio, U+FE0F. El resultado parece limpio y no lo está.

Tres regex ingenuas, tres fallos distintos

Tomamos una frase —«Shipping to 🇵🇹 today 👨‍👩‍👧‍👦 — 40% off, thanks 👍🏽 #7 ❤️», que son 63 unidades UTF-16, 55 puntos de código y 46 grupos de grafemas— y ejecutamos los tres enfoques habituales, más un cuarto construido sobre grupos de grafemas.

El enfoque por rango de puntos de código, /[\u{1F300}-\u{1FAFF}]/gu, es el que aparece en la mitad de las respuestas de internet. Quitó a los miembros de la familia y el pulgar, pero la bandera sobrevivió porque los indicadores regionales están en U+1F1E6–U+1F1FF, por debajo del rango; el corazón sobrevivió porque U+2764 está muy por debajo; y los tres juntores de ancho cero que sostenían a la familia se quedaron en la cadena como basura invisible.

El enfoque \p{Emoji} quitó la bandera, la familia y el pulgar, y también el 40 y el 7 de #7, dejando «% off» y una almohadilla desnuda. El enfoque Extended_Pictographic conservó la bandera intacta, quitó el pulgar pero no su tono, y dejó los juntores y el selector de variante. Tres líneas plausibles, tres clases distintas de salida errónea, ninguna anunciando un problema.

El cuarto enfoque segmenta el texto en grupos de grafemas con Intl.Segmenter y luego descarta un grupo entero en cuanto contiene un indicador regional, un carácter con Emoji_Presentation, o un pictograma seguido del selector de variante. Sobre la misma frase devolvió «Shipping to today — 40% off, thanks #7 »: dígitos conservados, bandera eliminada como unidad, ningún juntor huérfano, ningún selector suelto. El único artefacto son los espacios dobles donde estaban los emoji, que un colapso de espacios arregla en una segunda pasada.

El truncado es donde esto muerde en producción

Cortar una cadena en N unidades es con diferencia la forma más común de romper emoji, porque es lo más barato de escribir y funciona con todas las cadenas de prueba que a un desarrollador anglófono se le ocurre probar. Toma «Great work 👨‍👩‍👧‍👦 thanks», 29 unidades UTF-16. Cortar en 12, 15 o 18 deja la cadena terminada en medio carácter alto suelto, que no es texto válido. Pásalo a UTF-8 y se convierte en los tres bytes EF BF BD, el carácter de reemplazo: el lector ve un rombo negro. isWellFormed() devuelve false para esa cadena en JavaScript, lo cual es una forma barata de cazar el fallo en una prueba.

El fallo más sutil es peor, porque produce texto válido que significa otra cosa. Cortar la misma cadena en 16 unidades da «Great work 👨‍👩»: la familia de cuatro se ha convertido en un hombre y una mujer con un juntor entre ellos, lo cual no es una secuencia definida, así que se dibuja como dos personas separadas. Nada está malformado. Nada lanza un error. El mensaje ahora muestra una imagen distinta de la que se envió.

Iterar por punto de código con el operador de propagación arregla el problema del medio carácter y no el otro: tomar los 14 primeros puntos de código de la misma cadena sigue dando «Great work 👨‍👩». Solo los grupos de grafemas aciertan, porque son la unidad que el algoritmo de segmentación define como un carácter percibido por el usuario. Tomar los 14 primeros grupos de grafemas da «Great work 👨‍👩‍👧‍👦 t»: la familia entera conservada como una unidad, que es lo que un lector esperaría de una vista previa de catorce caracteres.

Contar emoji plantea el mismo problema que quitarlos

Toma la cadena «🇵🇹 👨‍👩‍👧‍👦 👍🏽 ❤️ 😀». Un humano cuenta cinco emoji. Consultamos seis métodos distintos y obtuvimos seis respuestas distintas: 27 unidades UTF-16, 18 puntos de código, 9 grupos de grafemas, 5 grupos de grafemas no blancos, 7 coincidencias de \p{Extended_Pictographic}, 9 de \p{Emoji_Presentation} y 10 de \p{Emoji}. Solo una vale 5.

Los recuentos por propiedad son altos por las razones exactas de arriba: la familia aporta cuatro pictogramas, el pulgar aporta uno y su tono otro bajo Emoji_Presentation, y la bandera aporta dos. Si tu producto aplica una regla del tipo «como máximo tres emoji por publicación», la regla solo vale lo que vale el contador que hay detrás, y el contador tiene que ser el que le dé la razón al lector: los grupos de grafemas que contienen un carácter emoji.

Qué hace en cambio quitar lo no ASCII, y por qué no es el mismo trabajo

Un atajo tentador es quedarse solo con ASCII, con el argumento de que todos los emoji están fuera. Todos lo están, y también casi todo el resto del mundo. Ejecutamos replace(/[^\x00-\x7F]/g, "") sobre «Café ☕ — résumé sent 👍» y devolvió «Caf rsum sent »: la taza y el pulgar han desaparecido, y también la é de Café, los dos acentos de résumé y la raya. En una frase francesa, española, portuguesa, alemana o italiana esto destruye palabras corrientes, no adornos.

Las dos operaciones pertenecen a herramientas distintas por una razón. Quitar emoji significa «saca las imágenes y deja en paz el idioma». Quitar lo no ASCII significa «reduce esto a los 128 caracteres que un sistema antiguo sabe manejar», que es un trabajo de transliteración con pérdidas reales que conviene elegir a conciencia. Recurrir al segundo cuando querías el primero es una de las formas más silenciosas de romper un producto multilingüe.

Unidades UTF-16
Un emoji visible, medido de tres formas — Node 26, Intl.Segmenter con granularity "grapheme"
EmojiCómo está construidoUnidades UTF-16Puntos de códigoGrupos de grafemas
😀Un solo punto de código, U+1F600211
❤️Base U+2764 más selector de variante U+FE0F221
1️⃣Dígito 1, selector U+FE0F, tecla U+20E3331
👍🏽Base U+1F44D más modificador de tono U+1F3FD421
🇵🇹Dos indicadores regionales, U+1F1F5 U+1F1F9421
👨‍👩‍👧‍👦Cuatro personas unidas por tres juntores U+200D1171
🏴󠁧󠁢󠁳󠁣󠁴󠁿Bandera negra más cinco caracteres de etiqueta más un terminador1471
Eliminar emojisElimina todos los emojis y pictogramas de tu texto.Probar la herramienta

Preguntas frecuentes

¿Cuál es la regex más corta que quita emoji correctamente?
No la hay, y esa es la respuesta honesta. Una regex reconoce puntos de código, y un emoji es una secuencia de puntos de código cuyas fronteras las define un algoritmo de segmentación, no un patrón. Puedes acercarte con una alternancia larga que cubra pictogramas, tiradas de juntores, pares de indicadores regionales y modificadores, pero entonces estás reimplementando mal el algoritmo. Segmenta primero, filtra grupos después: son tres líneas y es correcto.
¿Por qué al quitar emoji quedó un carácter invisible?
Porque quitaste la imagen y no a sus acompañantes. Los dos sospechosos habituales son U+FE0F, el selector de variante que pide el dibujado como emoji, y U+200D, el juntor de ancho cero que enlaza las partes de una secuencia. Ninguno es un pictograma, así que un filtro basado en pictogramas los deja. Son invisibles en pantalla pero reales en la cadena: cuentan para los límites de caracteres, rompen las comparaciones de igualdad y sorprenderán a quien compare el texto después.
¿Por qué el mismo emoji se ve distinto en otro móvil?
La cadena lleva la identidad, no el dibujo. Cada fabricante distribuye su propia fuente de emoji, así que U+1F600 es la sonrisa de un dibujante en una plataforma y la de otro en otra. Cuando un dispositivo no tiene glifo para una secuencia, recurre a dibujar las partes: una familia que se ve como una imagen en tu móvil puede aparecer como cuatro personas separadas en un aparato más antiguo, que es el repliegue correcto para una secuencia con juntores, no un fallo.
¿Cómo debe contar un emoji un límite de caracteres?
Cuenta grupos de grafemas si el límite existe para el lector, y cuenta bytes si existe para el almacenamiento. Las dos respuestas difieren en un factor de once en un emoji de familia, así que elige la que corresponda al motivo del límite y di cuál es junto al campo. Lo que nunca debes hacer es aplicar una longitud UTF-16 en silencio, porque quien escriba tres banderas chocará con un límite de 30 caracteres a los doce caracteres visibles y sin explicación.
¿Son © y ™ emoji?
Por propiedades, sí y no a la vez, que es justo la confusión de la que trata este artículo. Sondeamos ambos: cada uno tiene Emoji y Extended_Pictographic, pero ninguno tiene Emoji_Presentation, así que ambos se dibujan como texto normal salvo que un selector de variante pida una imagen. Eso significa que un filtro basado en Extended_Pictographic borrará el símbolo de copyright de tu pie de página. Si solo quieres las imágenes en color, comprueba Emoji_Presentation o un pictograma seguido inmediatamente de U+FE0F.
¿Quitar los emoji cambia el significado de un mensaje?
A menudo, lo que es un argumento para quitarlos en los bordes de un sistema y no en el medio. Los emoji llevan tono, negación y a veces todo el contenido de una respuesta. Quitarlos es apropiado cuando el destino no puede dibujarlos —una exportación en texto plano, una columna de base de datos antigua, un informe impreso, un nombre de archivo— e inapropiado cuando el texto lo seguirá leyendo una persona. Quítalos a la salida y guarda el original.

Artículos que podrían interesarte

Todas las guías
GuíaFormatear números para seis idiomas: separadores, moneda y la vuelta al valor1.234,56 y 1,234.56 son el mismo número, y confundirlos cambia el valor que lee un lector. Ejecutamos Intl.NumberFormat para las seis locales del sitio e imprimimos cada separador —incluido el invisible que usa el francés— y luego medimos por qué parseFloat no puede deshacer nada de eso.ExplicaciónContar palabras es ambiguo, y cada herramienta responde distintoUn recuento de palabras es una definición, no una medida. Contamos el mismo párrafo de cuatro formas y obtuvimos 25, 28, 33 y 38; luego contamos 50 000 caracteres de prosa corriente y obtuvimos acuerdo dentro del 4,5 %. La diferencia se debe por completo a compuestos, cifras y URL.TutorialFiltrar líneas por un patrón sin línea de comandosEsto es grep para quien no usa grep, con una diferencia importante: la búsqueda es una subcadena literal, así que una expresión regular de verdad devuelve un cuadro vacío y ningún error. Cada afirmación se comprobó ejecutando la herramienta.ExplicaciónDónde puede cortarse una línea: el algoritmo Unicode detrás de cada párrafo ajustado«Cortar en los espacios» falla en la mayoría de los sistemas de escritura. UAX #14 da a cada carácter una clase de corte de línea; buscamos las nuestras en Unicode 17.0.0 y ejecutamos una implementación conforme sobre espacios duros, guiones blandos, espacios de ancho cero, URL, japonés y tailandés.ExplicaciónQuitar los acentos rompe la búsqueda — hasta que lo haces en los dos ladosPlegar los diacríticos es un paso de normalización, y la normalización solo funciona cuando la misma función corre sobre el índice y sobre la consulta. NFC frente a NFD con los puntos de código a la vista, y las letras — ø, ł, ß, œ, ı — que salen del plegado intactas.GuíaConvertir entre formatos de lista sin perder datos: las reglas de comillas que nadie leePasar de una lista con saltos de línea a una lista con comas es trivial hasta que un elemento contiene una coma. Las reglas de comillas de la RFC 4180, por qué un campo CSV puede contener un salto de línea, por qué las hojas de cálculo europeas usan el punto y coma, y qué le hace un elemento vacío a la ida y vuelta — cada caso ejecutado e impreso.

Herramientas relacionadas

Fuentes

¿Has detectado un error en este artículo?