TL;DR —
CellsesRangedireccionado por número.Range("A1")nombra una celda con una cadena en el orden en que la lees (letra de columna y luego fila).Cells(row, column)nombra una celda con dos enteros en el orden en que Excel la guarda — primero la fila, luego la columna — y por esoCells(1, 2)es B1, no A2. Como ambas coordenadas son números que puedes calcular,Cellses la referencia hecha para los bucles. UsaRangepara las regiones que escribes por nombre,Cellspara las posiciones que calculas, yRange(Cells(1, 1), Cells(n, k))para construir un bloque a partir de esquinas calculadas.
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Data")
' Range nombra una region fija; Cells calcula una posicion con la fila primero.
ws.Range("A1").Value = "Header" ' la forma humana - una direccion escrita
ws.Cells(2, 1).Value = "First row" ' fila 2, columna 1 = A2 (NO B2)
Referirse a celdas es lo primero que hace toda macro, y Cells frente a Range es el primer sitio
donde la gente tropieza. La confusión no es que una sea mejor — es que direccionan las celdas de
forma distinta, y confundir el orden escribe en el lugar equivocado sin avisar. Esta guía se
construye sobre una sola idea que vuelve predecible todo lo demás: Cells es el gemelo numérico
de Range, y sus argumentos van con la fila primero. En cuanto lo tienes claro, tanto los
patrones de bucle como los errores de orientación cobran sentido.
Lo que aprenderás
- El modelo mental —
CellsesRangedireccionado con dos números en vez de una cadena - La regla más importante —
Cells(row, column), así queCells(1, 2)es B1, no A2 - Construir un bloque a partir de esquinas calculadas con
Range(Cells(...), Cells(...)) - Por qué un
Cellsa secas, sin índice, significa cada celda de la hoja - Indexar dentro de un rango existente con
rng.Cells(i)y su orden por filas - Cuándo echar mano de
Rangey cuándo deCells— y por qué no meterlo en un bucle
El modelo mental: Cells es Range direccionado por número
Una referencia de celda tiene que responder una sola pregunta — ¿qué celda? — y VBA te da dos
formas de responderla. Range("A1") responde con una cadena en notación
de hoja de cálculo: primero la letra de columna, luego el número de fila, exactamente como tu ojo
recorre la cuadrícula. Cells(1, 1) responde con dos enteros: la fila y luego la columna.
ws.Range("B3").Value = 10 ' direccion de texto: columna B, fila 3
ws.Cells(3, 2).Value = 10 ' la misma celda, como numeros: fila 3, columna 2
Apuntan a la misma celda. La diferencia que importa es qué puedes hacer con la dirección: una cadena
como "B3" es texto fijo, pero Cells(3, 2) son dos números que puedes calcular. Esa es toda
la razón de que exista Cells — es la referencia que puedes gobernar desde un contador, un UBound
o una fila que encontraste en tiempo de ejecución. Range es para la región que conoces por su
nombre; Cells es para la posición que resuelves en el código.
La regla más importante: Cells es (fila, columna), así que Cells(1, 2) es B1
Esta es la línea que pilla a todos los que vienen de escribir "A2". Los argumentos van primero
la fila, luego la columna — abajo antes que a lo ancho — que es lo contrario del orden «letra y
luego número» que lees en la hoja:
ws.Cells(1, 2).Value = "here" ' fila 1, columna 2 => B1 (NO A2)
ws.Cells(2, 1).Value = "there" ' fila 2, columna 1 => A2
Si esperabas que Cells(1, 2) fuera A2 porque «A es la columna 1 y 2 es la fila», lo leíste en
orden de cuadrícula; VBA lo lee en orden de almacenamiento. Un truco fiable: R antes que C — Row
(fila) y luego Column (columna), el mismo orden que la notación R1C1 y los subíndices de una matriz.
Invierte esto dentro de un bucle y cada escritura cae desviada un eje, transpuesta por toda la hoja,
sin ningún error que te avise. Ante la duda, dilo en voz alta como «fila r, columna c» cada vez.
El argumento de columna también acepta una letra como cadena, algo cómodo cuando conoces la columna pero calculas la fila:
ws.Cells(lastRow + 1, "C").Value = total ' la fila se calcula, la columna C se conoce
Construir un bloque a partir de esquinas calculadas: Range(Cells, Cells)
Cells direcciona una sola celda, así que por sí mismo no puede describir un bloque. El modismo que
desbloquea los rangos dinámicos es pasar dos llamadas a Cells como las esquinas de un Range:
Dim lastRow As Long, lastCol As Long
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row ' fondo de la columna A
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' Construir todo el bloque de datos a partir de dos esquinas calculadas:
Dim data As Range
Set data = ws.Range(ws.Cells(1, 1), ws.Cells(lastRow, lastCol)) ' A1 : ultima celda
Esta es la recompensa de aprender Cells: Range("A1") por sí solo no puede tomar una esquina
inferior derecha calculada sin pegar una cadena a mano ("A" & lastRow), lo que se rompe en cuanto
tus columnas pasan de la Z. Range(Cells(1, 1), Cells(lastRow, lastCol)) se mantiene numérico y
nunca le importa lo anchos que sean los datos. Desde aquí puedes reformar la referencia con
Resize o dejar que Excel encuentre el bloque por ti con
CurrentRegion.
Un Cells a secas significa toda la hoja
Cells sin índice no es un error ni una sola celda — es la colección de celdas de toda la hoja.
Eso es realmente útil para operaciones que abarcan la hoja entera:
ws.Cells.ClearContents ' borra todos los valores de la hoja
ws.Cells.Font.Name = "Calibri" ' fija la fuente en todas partes de una vez
Pero también es una trampa: un Cells a secas donde querías una celda concreta actúa sin avisar
sobre las 1.048.576 × 16.384 que hay. Si una macro de «dar formato a una celda» reformatea la hoja
entera, busca un Cells que perdió su índice. El hábito seguro es darle siempre a Cells su
(row, column) salvo que de verdad quieras decir todo.
Cells dentro de un rango, y el índice lineal
Cells no es solo una propiedad de la hoja — también funciona en relación con un rango, donde
(1, 1) es la esquina superior izquierda del propio rango, no A1:
Dim blk As Range: Set blk = ws.Range("C5:F20")
blk.Cells(1, 1).Value = "top-left of the block" ' C5, no A1
También existe una forma de índice único, Cells(i), que recorre las celdas en orden por
filas — de izquierda a derecha a lo largo de una fila, y luego baja a la siguiente:
blk.Cells(1).Value ' C5 (primera celda)
blk.Cells(5).Value ' C6 (cuatro a lo ancho del bloque de 4, luego salta a la fila siguiente)
La sorpresa aquí es la dirección: Cells(i) cuenta a lo ancho antes que hacia abajo, así que en
un bloque de 4 columnas la celda número 5 es el comienzo de la segunda fila, no la quinta fila. Si
un recorrido lineal parece saltar al lugar equivocado, casi siempre es este orden por filas frente a
una expectativa por columnas.
Range para regiones, Cells para posiciones — y no lo metas en un bucle
La regla limpia no es «prefiere una». Es: usa Range para las regiones que nombras y Cells para
las posiciones que calculas. Un área de informe fija, un bloque con nombre, una fila de encabezado
que escribes una vez — eso es Range("A1:D1"). Una celda cuya fila o columna calculas en tiempo de
ejecución — eso es Cells(r, c). Se combinan: Range(Cells(...), Cells(...)) construye un bloque
con forma de rango a partir de esquinas calculadas.
Lo único que hay que resistir es echar mano de Cells dentro de un bucle celda a celda cuando
existe una operación sobre todo el rango. Escribir For r = 2 To lastRow: ws.Cells(r, 3).Value = ...
toca la hoja una vez por fila y es la razón número uno de que las macros vayan a paso de tortuga. La
jugada madura es usar Cells para encontrar las esquinas, construir el Range y actuar sobre
todo el bloque de una vez — asigna una matriz a Range(...).Value, o llama a un método nativo (ver
WorksheetFunction). Cells es cómo localizas el trabajo; no
debería ser cómo haces cada unidad de él.
Cómo ayuda ExcelMaster
Cells frente a Range esconde más decisiones de las que parece: qué argumento es la fila, si un
Cells a secas acaba de tocar la hoja entera, si un índice lineal va a lo ancho o hacia abajo, y si
un bucle debería haber sido una única escritura sobre todo el rango. Cada giro equivocado es
silencioso — un valor desviado un eje, un reformateo de toda la hoja, una macro que va a paso de
tortuga.
ExcelMaster te deja describir el
resultado en su lugar. Di «rellena la columna C con un acumulado hasta la última fila de datos», y
encuentra la última fila, construye Range(Cells(2, 3), Cells(lastRow, 3)) y escribe los valores en
una sola pasada — con los argumentos en orden de fila primero, sin un Cells accidental sobre toda
la hoja, sin bucle celda a celda. Te quedas con el libro y con el código; te ahorras la pasada en la
que las escrituras cayeron transpuestas.
Preguntas frecuentes
¿Cuál es la diferencia entre Cells y Range en Excel VBA?
Ambos se refieren a celdas, pero las direccionan de forma distinta. Range("A1") usa una cadena
en notación de hoja de cálculo — letra de columna y luego fila. Cells(1, 1) usa dos números —
fila y luego columna. Como los argumentos de Cells son números que puedes calcular, Cells es lo
que usas dentro de los bucles y para las posiciones que resuelves en tiempo de ejecución; Range es
para las regiones fijas o con nombre que escribes por su nombre. Se combinan como
Range(Cells(r1, c1), Cells(r2, c2)) para construir un bloque a partir de esquinas calculadas.
¿Por qué Cells(1, 2) es la celda B1 y no A2?
Porque Cells toma sus argumentos primero la fila, luego la columna — lo contrario del orden
«letra y luego número» que lees en la cuadrícula. Cells(1, 2) significa fila 1, columna 2, que es
B1. Cells(2, 1) significa fila 2, columna 1, que es A2. Recuerda «R antes que C» (row y luego
column), el mismo orden que la notación R1C1.
¿Cómo selecciono un rango de celdas usando Cells en VBA?
Pasa dos llamadas a Cells como las esquinas de un Range: Range(Cells(1, 1), Cells(10, 4)) es
A1:D10. Esta es la forma estándar de construir un bloque cuando las esquinas se calculan, por ejemplo
Range(Cells(2, 1), Cells(lastRow, lastCol)). Cualifica tanto los Cells como el Range con la
misma hoja para que la referencia no apunte a la hoja que resulte estar activa.
¿Qué hace Cells sin argumentos en VBA?
Un Cells a secas (sin índice) se refiere a cada celda de la hoja. Cells.ClearContents borra
la hoja entera y Cells.Font.Size = 11 da valor a cada celda. Eso es útil para cambios que abarcan
toda la hoja, pero también significa que un Cells que perdió su índice (row, column) actuará sin
avisar sobre la hoja entera — así que ponle siempre el índice cuando te refieras a una celda concreta.
¿Puedo usar una letra de columna con Cells en vez de un número?
Sí. El argumento de columna acepta una cadena, así que Cells(2, "B") es lo mismo que
Cells(2, 2) (la celda B2). El argumento de fila tiene que ser un número. Usar una letra es cómodo
cuando conoces la columna pero calculas la fila, como en Cells(lastRow + 1, "C").
Probado en
Probado en: Excel 365 (Windows 11), VBA 7.1 — última verificación el 15/08/2026.
Guías relacionadas: VBA Range · VBA Resize · VBA CurrentRegion · VBA Offset · VBA Bucle For
