Las búsquedas hacia adelante y hacia atrás (lookahead y lookbehind) son aserciones de ancho cero: verifican si un patrón aparece o no junto a tu coincidencia, sin consumir caracteres ni incluirlos en el resultado. La búsqueda hacia adelante (?=...) comprueba lo que sigue, la búsqueda hacia atrás (?<=...) comprueba lo que precede, y ambas tienen formas negativas para "no seguido por" y "no precedido por".
La palabra "ancho cero" es la parte que confunde a todos. Un token regex normal coincide con un carácter y mueve el cursor hacia adelante. Una aserción coincide con una posición. Observa el texto circundante, devuelve verdadero o falso, y deja el cursor exactamente donde estaba. Por eso puedes apilar varias búsquedas hacia adelante en el mismo lugar, que es el truco detrás de casi todos los patrones de validación de contraseñas que hayas pegado alguna vez.
Las cuatro aserciones de búsqueda#
Exactamente cuatro búsquedas se dividen claramente en dos ejes: dirección (adelante o atrás) y polaridad (positiva o negativa). Una vez que las ves en una tabla, la sintaxis deja de parecer arbitraria.
| Aserción | Sintaxis | Significado | Explicación sencilla |
|---|---|---|---|
| Búsqueda positiva hacia adelante | (?=...) | La coincidencia debe ir seguida de ... | "va seguido de" |
| Búsqueda negativa hacia adelante | (?!...) | La coincidencia no debe ir seguida de ... | "no va seguido de" |
| Búsqueda positiva hacia atrás | (?<=...) | La coincidencia debe ir precedida de ... | "viene después de" |
| Búsqueda negativa hacia atrás | (?<!...) | La coincidencia no debe ir precedida de ... | "no viene después de" |
Los puntos dentro de cada aserción son un subpatrón normal. Se evalúan, el motor registra si coincidieron y luego descarta el movimiento del cursor. Nada dentro de una búsqueda termina en tu coincidencia o en tus grupos de captura.
Consejo: si alguna vez quieres confirmar que una aserción es realmente de ancho cero, mira la longitud del texto coincidente. Un patrón compuesto solo por búsquedas coincide con una cadena vacía en una posición. El resultado interesante es dónde coincidió, no qué.
Búsqueda positiva anticipada: Encuentra algo seguido de otra cosa#
Supón que tienes precios como 42USD, 99EUR y 7GBP, y solo quieres el número que está inmediatamente seguido de USD, sin incluir el código de moneda.
\d+(?=USD)
En 42USD 99EUR 7USD, esto coincide con 42 y 7. Se requiere USD para que ocurra la coincidencia, pero nunca se captura, por lo que obtienes números limpios que puedes analizar directamente. Compáralo con \d+USD, que te obliga a eliminar el USD después.
Ese es el valor total de la búsqueda anticipada: una condición que controla la coincidencia sin formar parte de ella.
Negación anticipada (Negative Lookahead): Coincidir con lo que no sigue#
Invierte la aserción con (?!...) y obtienes "coincidir con algo que no está seguido de otra cosa". Un caso clásico es buscar una palabra que no esté seguida de un sufijo específico.
\bcat(?!alog)\b
Contra cat catalog category cats, esto coincide con cat independiente y con cat dentro de cats (porque cats no es catalog), pero omite catalog. Los límites de palabra \b evitan que coincida con cat dentro de texto no relacionado.
La negación anticipada también permite patrones como "coincidir con una línea que no contenga X" cuando se ancla:
^(?!.*ERROR).*$
Esto coincide con cualquier línea completa que no contenga la palabra ERROR en ninguna parte. La anticipación escanea toda la línea primero; si encuentra ERROR, la aserción falla y la línea se omite. Esto es realmente útil para filtrar registros.
Lookbehind: Comprobar lo que viene antes#
Lookbehind hace el mismo trabajo en la otra dirección. El lookbehind positivo (?<=...) requiere un patrón anterior; el lookbehind negativo (?<!...) lo prohíbe.
Un caso común es extraer una cantidad que sigue a un símbolo de moneda, sin incluir el símbolo:
(?<=\$)\d+(\.\d{2})?
Contra Precio: $42.50 y gratis, esto coincide con 42.50 y deja el $ fuera del resultado. El lookbehind negativo también es útil. Para coincidir con un número que no esté precedido por un signo de dólar:
(?<!\$)\b\d+\b
Esto coincide con números sueltos como 2024 mientras ignora valores monetarios como $99. El lookbehind es donde las diferencias entre sabores comienzan a afectar, que es lo siguiente que necesitas saber antes de enviar un patrón.
La trampa de la portabilidad: el lookbehind no es universal#
Esta es la parte que la mayoría de los tutoriales omiten y la que rompe el código en producción. El lookahead es compatible en casi todas partes. El lookbehind no lo es, e incluso donde existe, las reglas sobre si puede tener longitud variable difieren según el motor.
| Motor / sabor | Lookahead | Lookbehind | Lookbehind de longitud variable |
|---|---|---|---|
| JavaScript (ES2018+) | Sí | Sí | Sí |
| PCRE / PCRE2 (PHP, muchas herramientas) | Sí | Sí | No (solo longitud fija) |
Python (re) | Sí | Sí | No (solo longitud fija) |
Python (módulo regex) | Sí | Sí | Sí |
Go (regexp, RE2) | No | No | No |
Dos cosas destacan. Primero, el paquete estándar regexp de Go utiliza el motor RE2, que deliberadamente no tiene ningún lookaround, porque RE2 garantiza una coincidencia en tiempo lineal y los lookarounds rompen esa garantía. Si escribes (?<=\$) en Go, no compilará. En su lugar, reestructuras con grupos de captura.
Segundo, "lookbehind de longitud fija" significa que PCRE y el re integrado de Python rechazan un lookbehind cuyo ancho pueda variar. (?<=cat|dog) funciona porque ambas alternativas tienen 3 caracteres; (?<=cats?) falla en esos motores porque la coincidencia podría tener 3 o 4 caracteres de ancho. JavaScript y el módulo regex de terceros de Python permiten lookbehind de longitud variable, por lo que el mismo patrón que funciona en la consola de tu navegador puede fallar en un backend de Python.
Advertencia: "funciona en regex101" no es lo mismo que "funciona en mi lenguaje". regex101 te permite elegir un sabor, pero es fácil probar bajo PCRE y luego enviar a un servicio Go o Python que rechace el patrón. Siempre confirma contra tu motor de destino real.
Un caso de uso real: validación de políticas de contraseñas#
El lugar más común donde los desarrolladores se encuentran con la anticipación (lookahead) son las reglas de contraseñas, y es el ejemplo más claro de apilamiento de aserciones de ancho cero. Supongamos que tu política es: al menos 8 caracteres, con al menos una letra minúscula, una letra mayúscula y un dígito.
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$
Recorrámoslo de izquierda a derecha:
^fija el inicio de la cadena.(?=.*[a-z])afirma que en algún lugar adelante hay una letra minúscula. El cursor no se mueve.(?=.*[A-Z])afirma que existe una letra mayúscula, nuevamente desde el inicio.(?=.*\d)afirma que existe un dígito..{8,}$finalmente consume 8 o más caracteres hasta el final.
Cada anticipación escanea independientemente toda la cadena desde la misma posición inicial, porque ninguna movió el cursor. Esa es exactamente la razón por la que puedes expresar "debe contener todos estos, en cualquier orden" como una lista plana de aserciones. Sin anticipación, necesitarías una alternancia fea que cubra cada orden posible.
Para prohibir espacios, agrega una anticipación negativa: (?!.*\s). Para requerir un carácter especial, agrega (?=.*[!@#$%^&*]). El patrón crece una aserción por regla, lo cual es mucho más legible que la alternativa.
Paso 1: Construye las aserciones una regla a la vez#
Comienza con los anclajes y la longitud, luego agrega una sola anticipación y confirma que se comporta correctamente antes de agregar la siguiente. Pega el patrón parcial en un probador de expresiones regulares gratuito y prueba una cadena que debería pasar y otra que debería fallar. Si agregas las cuatro aserciones a la vez y algo falla, no sabrás cuál está mal.
Paso 2: Prueba los casos límite, no solo el camino feliz#
Una expresión regular para contraseñas que acepta Abcdef12 no está probada como correcta. Prueba las cadenas que deberían fallar: una cadena completamente en minúsculas, una sin dígito, una de exactamente 7 caracteres. Confirma que cada una sea rechazada por la razón correcta. Observar el resaltado en vivo en un probador te dice si el fallo provino de la longitud o de una aserción faltante.
Paso 3: Fija el motor antes de copiar el patrón#
Una vez que pase, configura el probador en el motor que realmente usarás (JavaScript, PCRE, Python o Go) y vuelve a ejecutar los mismos casos. Si usaste una búsqueda hacia atrás de longitud variable o cualquier lookaround en Go, aquí es donde lo detectas, en el editor, no en una compilación CI fallida.
Lookahead vs Grupos de Captura: Cuándo Usar Cada Uno#
Un punto frecuente de confusión es cuándo recurrir a un lookahead frente a un grupo de captura simple, ya que ambos permiten "encontrar X cerca de Y". La diferencia radica en qué termina en tu coincidencia.
- Usa un grupo de captura
(...)cuando quieras conservar el texto circundante y extraer una parte del mismo. La coincidencia completa incluye todo; el grupo aísla una parte. - Usa un lookaround cuando el texto circundante sea solo una condición y no quieras que aparezca en el resultado. La coincidencia es solo el objetivo.
Concretamente, (\d+)USD coincide con 42USD y captura 42 en el grupo 1. \d+(?=USD) coincide solo con 42 y no captura nada. Ambos te dan 42, pero el primero mantiene USD en la coincidencia general y el segundo no. Si estás haciendo una búsqueda y reemplazo y quieres dejar USD intacto, el lookahead es más limpio porque no hay nada que restaurar.
Para un trabajo más profundo con patrones, nuestro tutorial sobre cómo dominar las expresiones regulares con un probador de regex en vivo cubre grupos, anclas y banderas junto con lookarounds.
Hoja de referencia rápida#
Ten esto a mano junto a tu editor:
(?=foo)coincide con la posición seguida defoo(?!foo)coincide con la posición no seguida defoo(?<=foo)coincide con la posición precedida defoo(?<!foo)coincide con la posición no precedida defoo- Las anticipaciones son de ancho cero: afirman, nunca consumen.
- Apila anticipaciones al inicio (
^) para requerir múltiples condiciones en cualquier orden. - La inspección hacia atrás no es compatible en Go (RE2) y debe tener longitud fija en PCRE y en el
reintegrado de Python.
Cuando necesites validar o desenredar un patrón, pégalo en el probador de regex de Molixa y observa las coincidencias y grupos actualizarse en vivo, luego cambia de sabor para confirmar que sea portable. Si también estás manipulando cargas útiles de API, el formateador JSON es ideal para limpiar respuestas antes de aplicarles regex, y si has usado regex101, nuestra alternativa gratuita a regex101](/alternatives/regex101-alternative) explica las diferencias.
Preguntas Frecuentes#
¿Cuál es la diferencia entre lookahead y lookbehind en regex?
Lookahead (?=...) verifica el texto que viene después de la posición actual, mientras que lookbehind (?<=...) verifica el texto que viene antes. Ambos son aserciones de ancho cero, es decir, prueban una condición sin incluir el texto circundante en la coincidencia. Cada uno también tiene una forma negativa, (?!...) y (?<!...), para "no seguido por" y "no precedido por".
¿JavaScript soporta lookbehind en regex?
Sí. JavaScript soporta tanto lookbehind como lookbehind de longitud variable desde ES2018, por lo que (?<=\$)\d+ funciona en navegadores modernos y Node. Entornos antiguos anteriores a ES2018 no lo soportan; si tu objetivo es un entorno heredado, pruébalo allí o reestructura con un grupo de captura en lugar de lookbehind.
¿Por qué mi lookbehind funciona en JavaScript pero falla en Python?
Probablemente es de longitud variable. El módulo re integrado de Python solo permite lookbehind de longitud fija, por lo que (?<=cats?) falla porque el ancho varía. JavaScript permite lookbehind de longitud variable, por lo que el mismo patrón funciona allí. Usa el módulo de terceros regex en Python para soporte de longitud variable, o reescribe la aserción a un ancho fijo.
¿Cómo uso lookahead para validación de contraseñas?
Apila un lookahead positivo por cada regla al inicio del patrón, luego consume los caracteres. Por ejemplo, ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$ requiere una letra minúscula, una mayúscula, un dígito y una longitud mínima de 8. Como los lookaheads son de ancho cero, cada uno escanea independientemente toda la cadena desde la misma posición.
¿Go soporta lookahead y lookbehind en regex?
No. El paquete estándar regexp de Go usa el motor RE2, que no tiene soporte para lookaround, por diseño, para garantizar coincidencia en tiempo lineal. Los patrones con (?=...) o (?<=...) no compilarán en Go. Reestructura la lógica con grupos de captura, o usa un enlace PCRE de terceros si realmente necesitas aserciones.
¿Cuándo debo usar lookahead en lugar de un grupo de captura?
Usa lookahead cuando el texto circundante sea solo una condición y no quieras incluirlo en tu resultado. Usa un grupo de captura cuando quieras el texto circundante en la coincidencia y necesites aislar una parte. Por ejemplo, \d+(?=USD) devuelve solo el número, mientras que (\d+)USD devuelve el número pero mantiene USD en la coincidencia general.


