Probado en: Excel 365 v2509 · Excel 2021 · Excel 2019 · última verificación el 07/06/2026
En resumen — ByRef pasa la variable original, así que un procedimiento puede cambiarla. ByVal pasa una copia, así que el original queda a salvo. VBA usa ByRef por defecto — por eso una variable puede cambiar a tus espaldas tras una llamada:
Sub Demo()
Dim n As Long: n = 10
MasUno n
MsgBox n ' 11 — n cambió de verdad, porque ByRef es el valor por defecto
End Sub
Sub MasUno(ByRef x As Long) ' ByRef: x ES n — escribir en x escribe en n
x = x + 1
End Sub
' Cambia «ByRef» por «ByVal» arriba y MsgBox muestra 10 — el original queda intacto.
Ese valor por defecto — ByRef cuando no dices otra cosa — es el origen de casi todos los fallos de «¿por qué cambió mi variable?» en VBA. Así no vuelves a llevarte una sorpresa.
El modelo mental: el original o una fotocopia
Cuando pasas una variable a un procedimiento, le das acceso a tus datos. Hay dos formas de entregarla:
ByRef= le entregas tu cuaderno de verdad. El procedimiento escribe sobre las páginas reales. Cuando devuelve el cuaderno, tus cambios están ahí. (Ref= referencia = «un puntero al original».)ByVal= le entregas la fotocopia de una página. El procedimiento puede garabatear la copia a gusto; tu cuaderno no cambia. (Val= valor = «una instantánea del contenido».)
La misma idea, dos consecuencias. Con ByRef, los cambios dentro del procedimiento se quedan. Con ByVal, se esfuman en cuanto el procedimiento termina. Todo lo demás es detalle.
La única regla: el valor por defecto es ByRef, y ahí está la trampa
Este es el hecho que muerde: si no escribes ByVal, VBA usa ByRef. Estas dos declaraciones son idénticas:
Sub Procesar(x As Long) ' sin palabra clave...
Sub Procesar(ByRef x As Long) ' ...significa exactamente esto
Así que todo parámetro sin marcar que hayas escrito es, en silencio, paso por referencia. La mayoría de las veces no importa — pero el día en que un ayudante incrementa sin avisar el contador que le pasaste, tienes un fallo que parece imposible: la variable cambió, y la línea que la cambió está en otro procedimiento. Para quien viene de Python, Java o C# (que pasan los primitivos por valor), esto parece al revés — y con razón: aquí VBA es la excepción.
El hábito que evita toda esta clase de fallos: escribe ByVal explícitamente en cada parámetro, y cambia a ByRef solo cuando de verdad quieras «devolver un valor». Tu yo futuro depurando a las seis de la tarde te lo agradecerá.
' Seguro por defecto — las variables de quien llama quedan protegidas:
Sub LogFila(ByVal numFila As Long, ByVal etiqueta As String)
Debug.Print numFila & ": " & etiqueta
End Sub
Usar ByRef a propósito: más de una respuesta
ByRef no es un error que evitar — es una herramienta. Una Function devuelve exactamente un valor. Cuando de verdad necesitas que un procedimiento devuelva dos o más resultados, los parámetros de salida ByRef son la vía limpia y clásica:
Sub MinMax(datos As Range, ByRef lo As Double, ByRef hi As Double)
lo = Application.Min(datos)
hi = Application.Max(datos)
End Sub
Sub Usar()
Dim bajo As Double, alto As Double
MinMax Range("A1:A100"), bajo, alto ' rellena LAS DOS variables
MsgBox "Rango: " & bajo & " a " & alto
End Sub
MinMax no devuelve nada por su nombre, pero entrega dos números escribiendo en las bajo y alto de quien llama. Es justo el trabajo para el que ByRef se diseñó. La regla práctica: ByVal para entradas, ByRef para salidas. Márcalos a propósito y la firma se documenta sola.
La trampa que vuelca ByRef en ByVal: paréntesis de más
Esta es de verdad sigilosa y enlaza directamente con cómo se llama a un Sub. Envolver un argumento en paréntesis adicionales fuerza a evaluarlo primero como expresión — así que VBA pasa el resultado, es decir, una copia, y anula ByRef:
MasUno n ' ByRef como se quería → n pasa a 11
MasUno (n) ' el (n) se evalúa primero → se pasa una COPIA → n sigue en 10
Call MasUno(n) ' aquí los paréntesis pertenecen a Call → de nuevo ByRef → n pasa a 11
MasUno (n) y Call MasUno(n) se parecen pero se comportan al revés. Si un parámetro ByRef se niega misteriosamente a actualizar la variable de quien llama, busca un par de paréntesis perdido alrededor del argumento. También es un truco deliberado: para proteger una variable aunque el procedimiento sea ByRef, envolverla en ( ) es una anulación local rápida.
El matiz que todos confunden: los objetos ignoran la diferencia
ByVal protege los valores simples — números, cadenas, booleanos, fechas. No copia un objeto en profundidad. Con un Range, un Worksheet o un Workbook, incluso ByVal pasa una copia de la referencia, y esa copia sigue apuntando al mismo objeto vivo:
Sub Vaciar(ByVal rng As Range) ' ByVal, y aun así...
rng.ClearContents ' ...esto vacía de verdad las celdas de quien llama
End Sub
Uno recurre a ByVal creyendo que «protegerá» un Range y se desconcierta cuando las celdas se borran igual. La verdad: ByVal en un objeto solo te impide reapuntar la variable a un objeto distinto dentro del procedimiento — nunca protege el contenido del objeto. Si quieres datos intactos, copia los valores tú mismo; la palabra clave no lo hará.
Errores frecuentes de ByRef / ByVal (y la solución)
| Síntoma | Causa | Solución |
|---|---|---|
| Una variable cambió tras una llamada | El ByRef por defecto dejó que el procedimiento la mutara |
Declarar el parámetro ByVal, o dejar de modificarla dentro |
El parámetro ByRef no actualiza a quien llama |
Argumento entre paréntesis de más: MiSub (x) |
Quitar los paréntesis (o usar Call MiSub(x)) |
| «Tipo de argumento ByRef no coincide» | Tipo de la variable de quien llama ≠ tipo del parámetro declarado | Igualar los tipos, o pasar ByVal para que VBA convierta una copia |
El Range ByVal se modificó igual |
Los objetos pasan una referencia incluso con ByVal | Copiar los valores antes de la llamada; la palabra clave no protege el contenido |
| Hacen falta dos retornos, se usaron globales | Una Function devuelve solo un valor | Parámetros de salida ByRef — la herramienta prevista |
| La recursión agota la memoria | Un valor grande pasado ByVal se copia en cada nivel |
Pasar los arreglos grandes ByRef para evitar copiar en cada llamada |
Cuando pasar datos de aquí para allá es la lata — describe el resultado
ByRef/ByVal es justo el tipo de detalle que hace de VBA un campo de minas: correcto, pero quisquilloso, y a un paréntesis perdido de un fallo. Si el objetivo real es «divide esta hoja por región y manda a cada responsable su parte», no deberías estar razonando sobre cómo se copian los argumentos. ExcelMaster Agent toma el objetivo en español sencillo y produce el resultado — sin parámetros, sin convención de paso, sin trampas. Prueba gratis →
Guías relacionadas
- VBA Sub en Excel — Procedimientos, llamada y por qué tu macro es solo un Sub
- VBA Function en Excel — Valores de retorno y funciones personalizadas
- VBA MsgBox en Excel — Sí/No, botones y la regla de los paréntesis
- VBA Bucle For en Excel — 8 ejemplos reales
Preguntas frecuentes
¿Cuál es la diferencia entre ByRef y ByVal en VBA?
ByRef pasa una referencia a la variable original, así que los cambios hechos dentro del procedimiento persisten tras volver. ByVal pasa una copia, así que el original no se toca nunca. La palabra clave controla si un procedimiento puede modificar la variable de quien lo llama.
¿VBA es ByRef o ByVal por defecto?
ByRef por defecto. Si escribes un parámetro sin palabra clave — Sub Procesar(x As Long) — VBA lo trata como ByRef, así que el procedimiento puede cambiar la variable que pasaste. Es lo contrario de la mayoría de los lenguajes modernos y una fuente frecuente de fallos.
¿Por qué cambió mi variable tras llamar a un Sub?
Porque el parámetro era ByRef (el valor por defecto) y el procedimiento lo modificó. Para proteger la variable, declara el parámetro ByVal, que pasa una copia. Como alternativa, envuelve el argumento en paréntesis de más en el punto de llamada — MiSub (x) — para forzar una copia.
¿Cuándo debo usar ByRef en VBA?
Usa ByRef a propósito cuando un procedimiento deba devolver un valor a quien llama — por ejemplo, dos o más resultados mediante parámetros de salida. Buena regla: ByVal para entradas, ByRef para salidas.
¿ByVal protege un objeto Range o Worksheet?
No. ByVal solo copia la referencia, no el objeto; la copia sigue apuntando al mismo Range o Worksheet vivo, así que modificar su contenido sigue afectando a quien llama. ByVal en un objeto solo impide reasignar la variable a otro objeto dentro del procedimiento.
¿Qué hace Call MiSub(x) distinto de MiSub (x)?
Con Call, los paréntesis forman parte de la sintaxis Call, así que x se sigue pasando ByRef. Sin Call, (x) es una expresión que se evalúa primero, así que se pasa una copia — anulando ByRef. Se parecen pero actúan al revés.
