🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Manejo de errores en VBA — El único patrón que usa toda macro fiable

|

Manejo de errores en VBA — El único patrón que usa toda macro fiable

Probado en: Excel 365 v2509 · Excel 2021 · Excel 2019 · última verificación el 08/06/2026

En resumen — VBA no tiene try/catch. El manejo de errores fiable es una sola forma que reutilizas en cada procedimiento que importe: arma un controlador arriba, haz el trabajo, enruta cada salida —éxito o fallo— por un único punto de limpieza y luego sal. Copia esta plantilla:

Sub ProcesarInforme()
    On Error GoTo ControlErrores
    Application.ScreenUpdating = False        ' estado que DEBEMOS restaurar después

    Dim hoja As Worksheet
    Set hoja = Sheets("Datos")                ' cualquier error aquí salta a ControlErrores
    hoja.Range("A1:A1000").Value = 0

SalidaLimpia:                                 ' la ÚNICA salida por la que pasan todos los caminos
    Application.ScreenUpdating = True         ' siempre restaurado, haya error o no
    Exit Sub

ControlErrores:
    MsgBox "ProcesarInforme falló: " & Err.Description
    Resume SalidaLimpia                       ' limpia y luego sal
End Sub

Esa estructura —On Error GoTo / trabajo / SalidaLimpia: / Exit Sub / ControlErrores: / Resume SalidaLimpia— es toda la disciplina. El resto de esta guía es el porqué de cada línea.

El modelo mental: estás construyendo a mano el try/finally que VBA no te da

Lenguajes como Python y C# tienen try / except / finally. VBA no tiene nada de eso. Lo que te da a cambio son tres primitivas —On Error GoTo Etiqueta, una etiqueta y Resume— con la expectativa de que las ensambles tú mismo en la estructura equivalente.

Mapéalo a try/finally y deja de parecer arbitrario:

try/catch/finally Equivalente en VBA
try { On Error GoTo ControlErrores
catch (e) { el bloque ControlErrores:, leyendo Err
finally { el bloque SalidaLimpia: al que llegan ambos caminos
throw Err.Raise

En cuanto ves la plantilla como un «try/finally manual», la posición de cada línea se vuelve obvia en lugar de memorizada. La parte innegociable es la única etiqueta de limpieza por la que pasan tanto el camino de éxito como el de error.

La única regla: Resume decide adónde vas tras el controlador — elige mal y entras en bucle o dejas fugas

Dentro de un controlador tienes tres formas de continuar, y elegir la equivocada es el clásico error de manejo de errores en VBA:

Resume                ' RE-EJECUTA la línea exacta que falló — solo seguro si corregiste la causa
Resume Next           ' SÁLTATE la línea fallida y continúa con la siguiente
Resume SalidaLimpia   ' SALTA a una etiqueta — normalmente tu única salida de limpieza
  • Resume reintenta la línea culpable. Úsalo solo cuando el controlador cambió algo para que el reintento pueda salir bien (creó una carpeta que faltaba, abrió un libro cerrado). Úsalo a ciegas y obtienes un bucle infinito — falla, maneja, reintenta, falla, para siempre.
  • Resume Next continúa pasada la falla. Bien para «regístralo y sigue»; peligroso si la línea saltada era de carga estructural.
  • Resume Etiqueta es el caballo de batalla: envía el control a tu SalidaLimpia: para que la limpieza siempre corra antes de que termine el procedimiento.

Un controlador que simplemente se cae por el final sin ningún Resume/Exit deja que el error detenga la macro en la práctica — así que decide la salida siempre de forma explícita.

La parte que todos se saltan: el único punto de limpieza es lo que salva tu archivo

Aquí está el modo de fallo que convierte un bug pequeño en un ticket de soporte. Una macro fija algún estado global de Excel para ganar velocidad:

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual

…y luego peta con un error antes de volver a activarlos. Ahora Excel está congelado y no recalcula, y el usuario cree que el propio Excel está roto — mucho después de que tu macro saliera. La misma historia vale para un identificador de archivo abierto (queda bloqueado), un objeto con Set (nunca liberado) o una hoja que quedó desprotegida.

La solución es estructural, no «programar con cuidado»: pon cada línea que «siempre debe correr» en una única etiqueta por la que pasen tanto el camino normal como el controlador, y haz que el controlador haga Resume hasta ella:

Sub ImportarArchivo()
    Dim ff As Integer
    On Error GoTo ControlErrores
    Application.ScreenUpdating = False
    ff = FreeFile
    Open "C:\Datos\entrada.csv" For Input As #ff
    ' ... lee el archivo, que podría reventar a mitad ...

SalidaLimpia:
    If ff <> 0 Then Close #ff             ' el archivo SIEMPRE se cierra
    Application.ScreenUpdating = True      ' la interfaz SIEMPRE se restaura
    Exit Sub
ControlErrores:
    MsgBox "La importación falló: " & Err.Description
    Resume SalidaLimpia                   ' el camino de error también pasa por la limpieza
End Sub

El éxito se cae dentro de SalidaLimpia. El fallo salta a ControlErrores y luego Resume SalidaLimpia. Ambas rutas cierran el archivo y restauran la interfaz. Ese es tu finally.

Burbujear errores hacia quien llama (re-lanzado)

Un ayudante de bajo nivel normalmente no debería decidir cómo reacciona toda la aplicación — debería limpiar sus propios recursos y dejar que quien llama decida. Captura el error, restaura el estado y vuelve a lanzarlo:

Private Sub CargarDatos()
    On Error GoTo ControlErrores
    ' ... trabajo que puede fallar ...
    Exit Sub
ControlErrores:
    Dim n As Long, d As String, s As String
    n = Err.Number: d = Err.Description: s = Err.Source   ' guarda antes de que la limpieza borre Err
    ' (limpieza local aquí)
    Err.Raise n, s, d                                     ' re-lanza al controlador de quien llama
End Sub

Guarda los valores de Err primero — las instrucciones de limpieza borran Err por debajo de ti. Después Err.Raise envía el error original hacia quien llamó a CargarDatos, donde un controlador de nivel superior puede registrarlo una vez y mostrarle al usuario un único mensaje limpio.

Un controlador de registro central

Para cualquier cosa que distribuyas, registra los errores en algún sitio que puedas leer luego, en vez de confiar en que el usuario capture un MsgBox:

ControlErrores:
    RegistrarError "ProcesarInforme", Err.Number, Err.Description
    Resume SalidaLimpia
End Sub

Private Sub RegistrarError(proc As String, num As Long, desc As String)
    Dim ff As Integer: ff = FreeFile
    Open ThisWorkbook.Path & "\registro_errores.txt" For Append As #ff
    Print #ff, Now & " | " & proc & " | " & num & " | " & desc
    Close #ff
End Sub

Cuándo On Error Resume Next es la herramienta correcta

No toda situación pide un controlador. Para una sola línea donde el fallo es esperado e inofensivo —comprobar si un objeto existe— un Resume Next reducido es más limpio que una etiqueta, siempre que compruebes Err.Number y desarmes con On Error GoTo 0:

On Error Resume Next
Set hoja = Sheets("Opcional")
On Error GoTo 0
If hoja Is Nothing Then Set hoja = Sheets.Add   ' no existía → créala

El repaso completo de cuándo esto es seguro frente a temerario está en On Error: Resume Next vs GoTo.

La opinión: la madurez no es cuántos controladores escribes — es si convergen

Puedes salpicar On Error GoTo por cada procedimiento y aun así escribir macros frágiles. La señal de madurez en el manejo de errores es más estrecha y más dura: ¿tiene cada procedimiento una única salida por la que pasan todos los caminos, donde ocurre la limpieza? Si la respuesta es no, entonces un solo error a mitad de procedimiento deja ScreenUpdating apagado, el motor de cálculo en manual y un archivo bloqueado — y tu usuario culpa a Excel, no a tu código.

Así que mi listón para «esta macro está lista para producción» no es la presencia de controladores. Es: elige cualquier línea, imagínala lanzando un error — ¿se sigue cerrando el archivo y se sigue restaurando la interfaz? Si sí, has interiorizado el patrón. Si no, más instrucciones On Error no te salvarán; una SalidaLimpia: sí.

Errores frecuentes de manejo (y la solución)

Síntoma Causa Solución
Excel «se congela» tras un error de la macro ScreenUpdating/Calculation nunca se restauraron Restáuralos en una SalidaLimpia: a la que el controlador haga Resume
El archivo queda bloqueado / «ya está abierto» Se saltó Close/Set …= Nothing en el camino de error Pon la limpieza en la única salida por la que pasan ambos caminos
Bucle infinito Resume reintenta una línea cuya causa no se corrigió Usa Resume Next o Resume Etiqueta, no un Resume pelado
Quien llama nunca se entera de que falló El ayudante se tragó el error con un MsgBox Guarda Err, limpia y luego Err.Raise para re-lanzar
El registro muestra error 0 Leíste Err después de que la limpieza lo borrara Copia Err.Number/Description antes de limpiar
El éxito muestra también el mensaje de fallo Falta Exit Sub antes de ControlErrores: Pon Exit Sub (o cae en SalidaLimpia) antes de la etiqueta

Cuando la limpieza pesa más que el trabajo — describe el trabajo y ya

Vuelve a mirar esa plantilla. La tarea real son dos líneas; el controlador, la etiqueta de limpieza, el Resume, el registro y el re-lanzado son las otras veinte. Esa proporción solo empeora en flujos reales. ExcelMaster Agent te deja describir el trabajo —«importa estos CSV, cuádralos por el ID de pedido, marca las diferencias y envíaselas a contabilidad»— en español sencillo, y el Python generado gestiona los fallos, restaura el estado y hace una copia de seguridad de tu libro antes de tocar nada. Sin On Error, sin Resume, sin Excel congelado. Prueba gratis →

Guías relacionadas

Preguntas frecuentes

¿VBA tiene try/catch? No. VBA no tiene try/catch/finally. Construyes el equivalente a partir de On Error GoTo Etiqueta, una etiqueta de controlador, una única etiqueta de limpieza y Resume. La plantilla estándar arma un controlador arriba, enruta cada salida por un punto de limpieza y luego sale.

¿Cuál es el mejor patrón de manejo de errores en VBA? Arma On Error GoTo ControlErrores, haz el trabajo y luego ten una etiqueta SalidaLimpia: donde restauras el estado (ScreenUpdating, archivos abiertos, objetos) y haces Exit Sub. El bloque ControlErrores: lee Err, opcionalmente lo registra y hace Resume SalidaLimpia. Tanto el éxito como el fallo pasan por la misma limpieza — esa es la clave.

¿Cuál es la diferencia entre Resume, Resume Next y Resume Etiqueta? Resume re-ejecuta la línea que falló (solo seguro si el controlador corrigió la causa, o tendrás un bucle infinito). Resume Next se salta la línea fallida y continúa. Resume Etiqueta salta a una etiqueta — normalmente tu única salida de limpieza para que esta siempre corra.

¿Cómo me aseguro de que la limpieza siempre corra tras un error en VBA? Pon cada instrucción que «siempre debe correr» en una única etiqueta (p. ej. SalidaLimpia:) colocada de forma que el camino normal caiga en ella, y haz que el controlador de errores haga Resume SalidaLimpia. Ambos caminos ejecutan entonces la limpieza. Este es el sustituto de un bloque finally en VBA.

¿Cómo paso un error de un ayudante hacia el procedimiento que lo llama? En el controlador del ayudante, guarda Err.Number, Err.Description y Err.Source en variables, haz cualquier limpieza local y luego llama a Err.Raise con esos valores guardados. El error se propaga al controlador On Error de quien llama, donde puedes registrarlo una vez y mostrarle al usuario un único mensaje.