Ir al contenido
OneKitly

Quitar los acentos rompe la búsqueda — hasta que lo haces en los dos lados

Publicado el 9/7/2026 · 16 min de lectura · Herramientas de texto e idioma

Daniel Okonkwo

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

Rendimiento web · Formatos de archivo

Verificado con 5 fuentes

Ver perfil
En resumen

Quitar los diacríticos es un paso de normalización, no una función de búsqueda: solo ayuda si la función idéntica corre sobre el índice y sobre la consulta. Haz el experimento: seis consultas contra seis nombres. Sin normalización, «cafe» encuentra Cafe Central y «café» encuentra Café Amélie: dos aciertos, cada uno a medias. Pliega solo la consulta y «café» pasa a encontrar Cafe Central y falla Café Amélie, la coincidencia exacta que antes obtenía, mientras «müller» baja de un acierto a cero. Pliega solo el índice y «café» no devuelve nada. Pliega los dos lados y todas las grafías devuelven los dos cafés. Una normalización unilateral no suaviza la coincidencia; traslada el desajuste a otro sitio. El plegado en sí es NFD seguido de borrar las marcas combinantes: é es el punto de código único U+00E9, pasa a U+0065 U+0301 tras la descomposición, y borrar U+0301 deja e. Funciona para é, ö, å y ñ, y no hace absolutamente nada con ø, ł, ß, œ y ı, que son puntos de código únicos sin marca combinante y atraviesan NFD intactos. Esos exigen una tabla por idioma — ö alemán a oe, ß a ss — o un Intl.Collator con sensibilidad base, que empareja Ørsted con Orsted sin borrar nada.

Plegar 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.

La normalización es una función, y tiene que correr en los dos lados

Aquí está todo el argumento en un experimento. Toma un índice de seis nombres — Café Amélie, Cafe Central, Zoë Müller, Zoe Miller, Łukasz Nowak, Ørsted Energi — y seis consultas: cafe, café, muller, müller, lukasz, orsted. Sin ninguna normalización, cafe encuentra una entrada y café encuentra otra distinta, muller no encuentra nada y müller encuentra Zoë Müller. Seis consultas, dos aciertos. Ese es el problema que se pretende arreglar.

Ahora aplica el plegado solo a la consulta, que es el cambio que se hace primero porque la consulta es lo que uno controla. El resultado es peor, no mejor. La consulta café se pliega a cafe y coincide con Cafe Central, mientras que Café Amélie — la entrada que encontraba perfectamente hace un instante — ya no coincide en absoluto, porque el índice sigue guardando la forma acentuada. La consulta müller pierde su único acierto por la misma razón. Dos aciertos siguen siendo dos aciertos, pero no son los mismos dos, y una de las pérdidas era una coincidencia exacta.

Pliega solo el índice y ocurre la imagen especular: cafe encuentra ahora los dos cafés, pero café no encuentra nada, porque la consulta acentuada ya no puede coincidir con el índice plegado. Pliega los dos lados y la tabla por fin se comporta: las cuatro grafías de la consulta café devuelven las dos entradas, y las dos grafías de Müller devuelven la única entrada. La regla que esto establece no es «quita los acentos». Es: sea cual sea la función que apliques, aplica la misma, al indexar y al consultar, desde el mismo camino de código. Una normalización aplicada en un solo lado no es una versión suave de lo correcto: es una regla de coincidencia distinta y en general peor.

NFC, NFD y qué es realmente el plegado

Unicode permite escribir una letra acentuada de dos formas. En forma compuesta, NFC, la letra é es un único punto de código, U+00E9. En forma descompuesta, NFD, son dos: U+0065, la e simple, seguida de U+0301, el acento agudo combinante. Ambas se ven idénticas en pantalla. Ambas son correctas. No son la misma cadena: en JavaScript, la é compuesta tiene longitud 1 y la é descompuesta longitud 2, y la igualdad estricta entre ambas es falsa. Una palabra como café tomada de una fuente NFC y buscada en un documento NFD sencillamente no aparece, y el desarrollador ve una búsqueda que falla sobre un texto que está leyendo en la página.

El plegado explota esto. Se normaliza a NFD, lo que separa toda letra canónicamente descomponible en una letra base más sus marcas, y luego se borra todo lo que cae en la categoría general Unicode M: las marcas combinantes. é pasa a U+0065 U+0301 y de ahí a e. ñ pasa a U+006E U+0303 y de ahí a n. å pasa a U+0061 U+030A y de ahí a a. Ese es todo el mecanismo, y por eso la operación se llama con propiedad plegado y no eliminación: no se quita nada del alfabeto, se descarta una distinción.

Una consecuencia práctica: antes de plegar nada, normaliza todo a una sola forma. Si tu índice está en NFC y el texto entrante en NFD, plegar ambos a las mismas letras base tapa la diferencia por accidente, pero cualquier comparación hecha antes del plegado, o sobre un campo que decidiste no plegar, seguirá estando mal. Normaliza a NFC en la entrada, como regla de almacenamiento, y trata el plegado como un índice aparte construido encima.

Las letras que no llevan ninguna marca

El mecanismo tiene un modo de fallo evidente: si una letra no tiene descomposición canónica, NFD la deja como está y no hay marca combinante que borrar, así que el plegado no hace nada. Ejecútalo y verás exactamente cuáles son esas letras. ß sigue siendo ß. ø sigue siendo ø. ł sigue siendo ł. œ sigue siendo œ. æ sigue siendo æ. ı sigue siendo ı. đ sigue siendo đ. En cada uno de estos casos el diacrítico no es una marca aplicada a una letra base; el trazo de la l, la barra de la o, la ligadura entre la o y la e forman parte del dibujo de la letra, y Unicode codifica cada una como un carácter por derecho propio.

Por eso el experimento de los seis nombres sigue sin devolver nada para lukasz y orsted incluso después de plegar los dos lados. Łukasz Nowak se pliega a Łukasz Nowak, sin cambios; Ørsted Energi se pliega a Ørsted Energi, sin cambios. Las dos entradas que un usuario tiene menos probabilidades de teclear bien son justo las dos con las que el plegado no ayuda. Una palabra como Łódź es el caso más claro: pliégala y obtienes Łodz, porque ó y ź se descomponen y ł no, así que el resultado no es ni el original ni la grafía ASCII que alguien buscaría.

El arreglo es un pequeño mapa explícito aplicado antes de la pasada NFD: ł a l, ø a oe u o, œ a oe, æ a ae, đ a d, ß a ss. La descomposición de compatibilidad, NFKD, resuelve las ligaduras œ y æ pero no las letras con trazo, así que tampoco es una respuesta general. No hay forma de esquivar la pregunta de qué idioma estás indexando, y de eso van justamente las tres secciones siguientes.

Alemán: oe, no o — y ss, no s

Las diéresis alemanas sí se descomponen, así que el plegado ingenuo produce algo. Produce lo equivocado. Größe se pliega a Große: la diéresis desaparece, la ese fuerte se queda, y el resultado es una palabra alemana real que significa otra cosa. Müller se pliega a Muller, Öl a Ol. La convención que los lectores alemanes esperan de verdad, codificada en la DIN 5007 variante 2 y usada en las guías telefónicas, es el desarrollo en dos letras: ä a ae, ö a oe, ü a ue, ß a ss. Größe pasa a Groesse, Müller a Mueller, Straße a Strasse.

La ese fuerte merece su propio párrafo, porque se comporta como ninguna otra letra de esta lista. No tiene descomposición canónica, así que NFD la deja; NFKC también la deja, de modo que Straße sigue siendo Straße. Pero su paso a mayúsculas en JavaScript da la cadena de dos caracteres SS, lo que significa que un pipeline ingenuo de «mayúsculas y luego comparar» ya la pliega bien, mientras que un plegado de acentos ingenuo no. La forma capital U+1E9E existe y vuelve a ß en minúsculas. Si tu índice alemán se construye con mayúsculas y tu consulta con retirada de acentos, ß coincidirá en un sentido y no en el otro: otra vez el problema unilateral, con otro disfraz.

Hay una forma de acertar sin escribir tabla alguna. Un Intl.Collator alemán con sensibilidad base ya trata Grosse y Größe como iguales: es la colación alemana estándar, donde ß tiene una diferencia secundaria respecto a ss. Pide la variante de guía telefónica, la configuración regional de-u-co-phonebk, y Groesse y Größe también comparan como iguales, porque esa colación es justamente la que trata ö como oe. El CLDR mantiene esas tablas; tú no tienes que hacerlo.

Escandinavo y turco: letras, no decoraciones

En danés, noruego y sueco, æ, ø y å son letras del alfabeto, y van después de la z. Un colador lo demuestra en pantalla: ordena A, Aa, Å, Ø y Z con un colador danés y obtienes A, Z, Ø, Å, Aa; ordena esas mismas cinco con un colador inglés y obtienes A, Å, Aa, Ø, Z. En danés, å no se ha archivado discretamente junto a la a: tiene su propia posición al final, y el dígrafo Aa se ordena con ella. Plegar å a a no quita, por tanto, un acento: fusiona dos letras distintas, y plegar ø a o fusiona otras dos.

El turco ofrece el caso más nítido, y va de mayúsculas y minúsculas, no de acentos. El turco distingue una ı sin punto, U+0131, de una i con punto, y correspondientemente una mayúscula con punto İ, U+0130, de la I normal. La minúscula de I es ı y la minúscula de İ es i, pero solo en la configuración regional turca. Ejecútalo en JavaScript y la trampa se ve: la cadena İSTANBUL pasada a minúsculas con las reglas por defecto mide nueve caracteres, porque İ se convierte en i seguida del punto suprascrito combinante U+0307. Pasada a minúsculas con toLocaleLowerCase("tr") mide ocho, el istanbul llano que teclearía un usuario. Un índice de búsqueda construido con la minúscula por defecto no encontrará jamás esa consulta, y las dos cadenas se ven idénticas en pantalla.

Ambos casos apuntan a lo mismo. Las letras que rompen un plegado ingenuo son las que un idioma trata como miembros de pleno derecho de su alfabeto, y la transformación que un idioma espera es una propiedad del idioma, no del carácter. Para eso existe un argumento de configuración regional, y pasarlo no cuesta nada.

Las palabras que tu propio idioma no te dejará plegar

El plegado es destructivo de un modo fácil de demostrar en cualquier lengua con diacríticos. En español, año se pliega a ano, una colisión que cualquier redactor conoce y que basta por sí sola para justificar una revisión humana de lo que se pliega. Sí se pliega a si, de modo que la afirmación y la conjunción condicional se vuelven el mismo token, y son dos de las palabras más frecuentes del idioma. Es exactamente el tipo de colisión que hace que una lista de resultados parezca rota.

Esto no es un argumento contra el plegado. Es un argumento a favor de conservar también la forma sin plegar. El patrón que funciona son dos campos: guarda el texto original tal cual se escribió, normalizado a NFC y nada más, y construye a su lado un segundo campo plegado para la coincidencia. Ordena las coincidencias exactas sobre el original por encima de las plegadas, y el lector que escribió el acento obtiene primero la entrada que buscaba, mientras que el que no lo escribió encuentra algo igualmente. El plegado como índice adicional es una función; el plegado como sustitución destructiva de tus datos es un fallo del que te enterarás más tarde.

Qué usar en lugar de un plegado escrito a mano

Para comparar y ordenar, usa un colador en vez de una transformación. Un Intl.Collator con sensibilidad base declara cafe y café iguales, Muller y Müller iguales, Orsted y Ørsted iguales, y Lukasz y Łukasz iguales, incluidas las cuatro letras que el plegado NFD no puede tocar, porque las tablas de colación saben qué son esas letras. Y lo hace sin producir una cadena intermedia estropeada que luego haya que guardar en algún sitio.

Para los slugs de URL, donde de verdad necesitas una salida ASCII acotada, conserva el plegado, pero condúcelo primero con un mapa de idioma explícito y después con NFD, y revisa el resultado. Un generador de slugs es uno de los pocos sitios donde un plegado destructivo e irreversible es correcto, porque un slug no es un dato: es una etiqueta regenerable, y tiene permiso para perder distinciones que el texto original llevaba. Solo asegúrate de aplicar el mapa antes de NFD, o ł y ø atravesarán el slug y saldrán como caracteres inutilizables.

Y decidas lo que decidas, escríbelo una sola vez. La causa más frecuente del fallo del título no es un mal plegado: es un buen plegado implementado dos veces, una en el indexador y otra en el buscador, por dos personas, con seis meses de diferencia. Una función, exportada de un solo módulo, llamada desde los dos lados.

Nueve letras a través de NFD y un plegado ingenuo de marcas combinantes, ejecutado en Node 22. Las cinco cuya columna NFD es un solo punto de código salen sin cambios: no hay marca que borrar.
LetraPuntos de código NFCPuntos de código NFDEl plegado ingenuo daLo que exige el idioma
éU+00E9U+0065 U+0301ee vale para buscar, pero fusiona palabras que el idioma distingue
öU+00F6U+006F U+0308ooe en alemán; o es aceptable en sueco y finés
ßU+00DFU+00DF (sin descomposición)ß, sin cambiosss
øU+00F8U+00F8 (sin descomposición)ø, sin cambiosoe; es una letra propia, no una o decorada
åU+00E5U+0061 U+030Aaaa en danés y noruego; una letra distinta, ordenada después de la z
ıU+0131U+0131 (sin descomposición)ı, sin cambiosi para un índice en alfabeto latino, pero nunca confundirla con i dentro del turco
İU+0130U+0049 U+0307Ii, pero solo toLocaleLowerCase("tr") lo produce en un único punto de código
łU+0142U+0142 (sin descomposición)ł, sin cambiosl
œU+0153U+0153 (sin descomposición)œ, sin cambiosoe; NFKD lo daría, NFD no
Eliminar acentosElimina acentos y diacríticos del texto: é pasa a e y ü a u.Probar la herramienta

Preguntas frecuentes

¿Debo guardar el texto plegado o plegar sobre la marcha?
Guárdalo, como campo adicional, y nunca como sustitución. Plegar sobre la marcha significa plegar todo el índice en cada consulta, lo cual es lento, y hace demasiado fácil que un camino de código pliegue y otro se olvide. Un campo plegado guardado es barato, se construye una vez con la misma función que llama el buscador, y deja el original intacto para la coincidencia exacta y para mostrarlo. Lo único que no debes hacer es plegar en el sitio: una vez que la forma acentuada desaparece de tu base de datos ya no puedes mostrar el nombre bien, y ninguna astucia posterior la recupera.
¿Es NFKD mejor que NFD aquí, ya que también descompone las ligaduras?
Resuelve un problema y crea varios. NFKD sí convierte œ en oe y fi en fi, que es justo lo que quieres en un índice de búsqueda. Pero la descomposición de compatibilidad también reescribe los superíndices como dígitos normales, las letras latinas de ancho completo como ASCII, el signo de ohmio como una omega y varios caracteres de espaciado como espacios simples. En un campo de visualización eso es destructivo de maneras que no pediste. En un campo de coincidencia suele ser aceptable y a menudo útil. Así que: NFC para almacenar, NFKD como una entrada del campo plegado de coincidencia si quieres el comportamiento con ligaduras, y un mapa explícito para ø, ł y ß, que ninguna de las dos formas arreglará.
¿Lo hace la base de datos por mí si elijo la colación correcta?
En buena medida sí, y suele ser mejor respuesta que plegar en el código de la aplicación. Una colación insensible a acentos implementa la misma idea que el colador, en el nivel donde la comparación ocurre de verdad, así que el índice y la consulta se comparan bajo una sola regla por construcción. Dos advertencias. Primera, la colación es por columna o por comparación, así que una consulta que compare una columna colacionada contra una expresión que plegaste tú vuelve al problema unilateral. Segunda, las colaciones insensibles a acentos dependen del idioma exactamente como se ha descrito arriba, así que elige la que corresponde a tu contenido y no un valor por defecto genérico.
¿Por qué dos cadenas que se ven idénticas fallan una prueba de igualdad?
Porque una está compuesta y la otra descompuesta. La igualdad estricta compara unidades de código, y una é compuesta es una unidad mientras que una é descompuesta son dos, así que la prueba falla aunque el renderizado sea idéntico píxel a píxel. Es la sorpresa Unicode más frecuente en una función de búsqueda, y también la más fácil de arreglar: normaliza los dos operandos a NFC antes de comparar. Fíjate en que una comparación sensible a la configuración regional ya declara equivalentes a las dos, lo cual es un buen diagnóstico: si localeCompare devuelve cero y la igualdad estricta devuelve falso, has encontrado un desajuste de normalización y no un error de datos.
¿Alguna vez es correcto plegar el nombre de una persona?
Para la coincidencia, sí. Para mostrarlo, no. Alguien llamada Zoë Müller tiene derecho a ver su nombre bien escrito en la pantalla, en la factura y en el correo, y un sistema que solo guarda la forma plegada no puede garantizarlo haga lo que haga después. Pliega hacia una clave de búsqueda junto al registro, nunca encima, y asegúrate de que todos los caminos de salida leen el campo original. Esta es también la razón práctica por la que gana el patrón de dos campos: hace que el camino de visualización y el de coincidencia sean estructuralmente distintos, de modo que nadie pueda imprimir la clave de búsqueda por accidente.

Artículos que podrían interesarte

Todas las guías
ExplicaciónDetectar el idioma de un texto y por qué los textos cortos fallanMedido, no afirmado: 90 frases cortas reales en seis idiomas, ninguna rechazada y 68 acertadas — un 76%, que baja al 64% por debajo de dieciséis letras. Cuatro de las respuestas erróneas volvieron con un 100% de confianza.ExplicaciónMayúscula inicial y mayúsculas de título: las reglas cambian según el idiomaLas mayúsculas de título inglesas tienen tres cortes distintos según el manual de estilo. El español, el francés, el portugués y el italiano no tienen ninguno. El alemán pone en mayúscula cada sustantivo. La herramienta no sabe nada de esto: aquí está exactamente lo que hace.ExplicaciónLos emoji son más difíciles de lo que parecen: por qué «basta con quitarlos» no tiene respuesta en una líneaUn 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.ExplicaciónLo que un verificador de palíndromos debe decidir antes de poder responderCaja, espacios, puntuación y diacríticos: cuatro políticas, seis frases reales, y la respuesta cambia con cada una. Luego la mitad más difícil — invertir una cadena no está definido de por sí, y la inversión por unidad de código rompe los emojis y desprende los acentos, demostrado en Node.ExplicaciónLos hashtags son un índice de búsqueda, no un megáfonoUn hashtag hace que una publicación sea localizable en una consulta, que es un trabajo distinto de hacer que se difunda. Modela la visibilidad que una etiqueta compra de verdad y la respuesta cae sola: el valor de una etiqueta es su número de espectadores por publicación publicada, no su volumen — así que una etiqueta muy popular no devuelve casi nada y una específica devuelve veinticinco veces más.GuíaEtiquetas title, meta descripciones, y qué hacen con ellas los buscadoresLo que dice la propia documentación de Google sobre reescribir títulos y sobre la meta descripción, en vez de lo que dice el folclore SEO. Luego la parte medible: los títulos se truncan por ancho en píxeles, así que dos títulos de exactamente sesenta caracteres pueden renderizarse con 204,55 píxeles de diferencia y solo uno sobrevive.

Herramientas relacionadas

Fuentes

¿Has detectado un error en este artículo?