La macro grabada que formatea la hoja equivocada
En el área de logística de una distribuidora, Inés grabó con el grabador de macros un procedimiento llamado FormatoInforme para dar formato al informe diario de expediciones, que ocupa unas 1.200 filas y las columnas A:H. Lo grabó con la hoja «Datos» activa; el código empieza por Range("A1:H1").Select, sigue con Selection.Font.Bold = True y usa ActiveCell.Offset en cinco puntos distintos. El libro contiene además las hojas «Resumen» y «Parámetros», esta última oculta. Cuando su compañero ejecuta la macro desde «Resumen», el formato se aplica a esa hoja y, en algunas ejecuciones, aparece el error 1004 «El método Select de la clase Range ha fallado». El módulo no lleva Option Explicit y en él conviven asignaciones a fila, Fila y filas sin ninguna declaración previa. La macro se guardó en un módulo estándar del propio libro y se lanza desde un botón de la cinta.
1. ¿Por qué el formato acaba en la hoja equivocada y por qué aparece el error 1004? Describe los cambios mínimos para que el procedimiento deje de depender de la hoja activa.
Read the worked answer
Causa raíz. El grabador no escribe referencias, escribe acciones sobre lo que está activo en ese instante. Range("A1:H1") sin calificar equivale a ActiveSheet.Range(...), y Selection es lo que el usuario tenga seleccionado justo entonces. Ejecutada desde «Resumen», la hoja activa es otra y el formato cae allí. El fallo no es de la macro, es de diseño: depende del estado de la ventana.
El error 1004. Select solo funciona sobre un rango de la hoja activa y visible. Si la hoja destino no está activa, si el libro que contiene el código no es el libro activo o si la selección actual es una hoja de gráfico o un objeto sin Font, VBA lanza «El método Select de la clase Range ha fallado». Por eso el error aparece «a veces»: depende de dónde esté el foco.
Cambios mínimos.
- Calificar siempre:
ThisWorkbook.Worksheets("Datos").Range("A1:H1").ThisWorkbookes el libro que contiene el código, no el que esté activo. - Sustituir
Select+Selectionpor un bloqueWith:With ws.Range("A1:H1")y dentro.Font.Bold = True,.Interior.Color = RGB(220, 230, 241),End With. Desaparece el portapapeles y el parpadeo. - Cambiar
ActiveCell.Offset(f, c)por una referencia calculada, por ejemplows.Cells(fila, 3)dentro del bucle. - Añadir
Option Expliciten la primera línea del módulo.
Regla de revisión: en un módulo de código no debería quedar ni un Select ni un Activate; son marcas de grabación, no de código terminado.
2. Si conviven `fila`, `Fila` y `filas`, ¿cuántas variables distintas hay en realidad? ¿Qué efecto tendría añadir `Option Explicit` y declararlas?
Read the worked answer
VBA no distingue mayúsculas de minúsculas en los identificadores. Por tanto fila y Fila son la misma variable, mientras que filas es otra distinta. El analista cree tener tres contadores y en realidad tiene dos, uno de ellos reasignado en dos sitios; cualquier lectura posterior devuelve el último valor escrito, lo que produce resultados absurdos sin ningún error visible.
Qué añade Option Explicit. Obliga a declarar antes de usar. Sin él, fila se crea como Variant con valor Empty y la expresión fila = fila + 1 funciona devolviendo 1, ocultando el error de lógica. Con él, esas tres líneas dan error de compilación «Variable no definida» hasta que se declaren.
Cuidado con el efecto secundario. Si se declaran a la vez Dim fila As Long y Dim Fila As Long, el compilador da «Declaración duplicada en el ámbito actual», porque sigue siendo el mismo nombre. La solución no es declarar, es renombrar con una convención estable: filaActual, filaInicio, numFilas.
Recomendación adicional. Declarar como Long y no como Integer: con 1.200 filas hoy no importa, pero el día que el informe crezca hasta 40.000 el Integer desborda con error 6. Además, las variables tipadas evitan conversiones implícitas Variant en cada iteración, lo que se nota en bucles largos.
3. El código vive en un módulo estándar. ¿Es la ubicación correcta? ¿Qué cambiaría si mañana el informe debe formatearse solo al abrir el libro?
Read the worked answer
Sí es correcta para este uso. Un módulo estándar aloja procedimientos generales invocables desde un botón, desde la ventana de macros o desde otro procedimiento. El módulo de una hoja está pensado para sus eventos (Worksheet_Change, Worksheet_Activate) y el módulo ThisWorkbook para los eventos del libro. Meter una macro de formato en el módulo de «Datos» la ata a esa hoja y complica su reutilización.
Si debe ejecutarse al abrir. Se añade en el módulo ThisWorkbook un procedimiento Private Sub Workbook_Open() que llame a FormatoInforme. Ese evento se dispara al abrir el libro con macros habilitadas; si el usuario las deshabilita, no se ejecuta nada, algo que conviene documentar.
Detalles que suelen fallar en ese escenario.
Workbook_Opense ejecuta antes de que el usuario pueda hacer nada, pero también antes de que otros libros estén disponibles; siFormatoInformedepende de datos externos, hay que comprobar que existan.- Si el procedimiento muestra un
MsgBox, el usuario se topa con un diálogo en cada apertura; mejor un mensaje solo cuando haya algo anómalo. - Conviene declarar
FormatoInformecomoPublicsi se llama desde otro módulo y comoPrivatesi solo se usa dentro del suyo, para evitar colisiones de nombres al importar módulos.
Ventaja del cambio. La lógica de formato sigue en un único sitio y el evento solo la invoca; así se puede seguir lanzando a mano desde el botón sin duplicar código.