¿Quieres que marcar una casilla mueva automáticamente la fila a otra hoja en Excel? Esta guía práctica con VBA te da la solución inmediata, segura y reversible. Incluye ejemplos listos para pegar, variaciones (varias hojas, tablas) y buenas prácticas para evitar errores.
Qué vas a conseguir
- Mover automáticamente una fila al marcar una casilla (checkbox) en una columna concreta.
- Eliminar la fila de la hoja origen tras moverla (no solo copiarla).
- Revertir el movimiento si desmarcas la casilla en la hoja destino.
- Unificar varias hojas “alimentadoras” (feeder) en una hoja resumen.
- Hacerlo con código robusto, rápido y fácil de mantener.
Cuándo usar esta técnica
Es ideal para bitácoras de trabajo, listas de tareas, pipelines de producción o cualquier flujo en el que una fila “cambia de estado” al marcar un checkbox: por ejemplo de “Trailer Log” a “Completed Trailer log”, o de “Master” a “Completed”.
Preparación mínima del libro
- Guarda el archivo como .xlsm (habilitado para macros).
- Si usas checkbox de Formulario, vincula cada casilla a su celda (clic derecho > Formato de control > Control > Celda vinculada). La casilla escribirá
TRUE/FALSE
(booleanos) en la celda. - Reserva una columna auxiliar (recomendado Z) para registrar la hoja de origen. Eso facilita revertir movimientos.
Mapa rápido de configuración
Parámetro | Descripción | Ejemplo |
---|---|---|
CHECK_COL | Columna con la casilla (número: A=1, B=2, …) | H → 8 |
DEST_SHEET | Nombre de la hoja destino | Completed Trailer log |
ORIGIN_COL | Columna auxiliar que guarda el nombre de la hoja origen | Z → 26 |
Solución base: mover la fila al marcar en la columna H
Pega este código en el módulo de la hoja origen (en el Editor de VBA, doble clic sobre Trailer Log):
Option Explicit
Private Sub Worksheet\_Change(ByVal Target As Range)
Const CHECK\_COL As Long = 8 ' Columna H
Const DEST\_SHEET As String = "Completed Trailer log"
Const ORIGIN\_COL As Long = 26 ' Columna Z: guarda el nombre de la hoja origen```
Dim dest As Worksheet, rng As Range, cel As Range, lastRow As Long
If Intersect(Target, Me.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Salir
Set dest = ThisWorkbook.Worksheets(DEST_SHEET)
Set rng = Intersect(Target, Me.Columns(CHECK_COL))
For Each cel In rng.Cells
' Asegúrate de que la celda contiene un booleano TRUE
If VarType(cel.Value) = vbBoolean And cel.Value = True Then
lastRow = dest.Cells(dest.Rows.Count, 1).End(xlUp).Row + 1
' Guarda el origen para poder revertir si hiciera falta
Me.Cells(cel.Row, ORIGIN_COL).Value = Me.Name
' Mover la fila (copia + elimina; más seguro en tablas que Cut)
Me.Rows(cel.Row).Copy dest.Rows(lastRow)
Me.Rows(cel.Row).Delete
End If
Next cel
```
Salir:
Application.EnableEvents = True
End Sub </code></pre>
<h3>Por qué funciona y qué hace cada parte</h3>
<ul>
<li><code>Worksheet_Change</code> se dispara cuando cambia la celda (la casilla vinculada escribe <code>TRUE/FALSE</code>).</li>
<li>Limitamos el <em>trigger</em> a la <strong>columna H</strong> con <code>Intersect</code> para no reaccionar a otros cambios.</li>
<li>Desactivamos eventos con <code>Application.EnableEvents = False</code> para evitar bucles (p. ej., al copiar).</li>
<li>Si la celda es <strong>booleana</strong> y <code>True</code>, buscamos la última fila ocupada en destino, <strong>copiamos</strong> y <strong>borramos</strong> la fila original.</li>
<li>Guardamos la hoja de origen en la <strong>columna Z</strong> para poder <em>deshacer</em> desde la hoja destino.</li>
</ul>
<h2>Quitar la fila de la hoja origen tras moverla</h2>
<p>Ya está contemplado: primero se copia y después se borra la fila en la hoja origen con <code>Rows(...).Delete</code>. Si prefieres cortar en un paso (no recomendable en tablas), sustituye las dos líneas por:</p>
<pre><code class="language-vb">Me.Rows(cel.Row).Cut dest.Rows(lastRow)
</code></pre>
<p><strong>Consejo:</strong> en rangos simples, <em>Cut</em> es más rápido; en <em>ListObjects</em> (tablas), copiar + borrar suele ser más estable.</p>
<h2>Variación: casillas en la columna A (de “Master” a “Completed”)</h2>
<p>Adapta los nombres de hojas si difieren. Pega el código en el módulo de la hoja <em>Master</em>:</p>
<pre><code class="language-vb">Option Explicit
Private Sub Worksheet\_Change(ByVal Target As Range)
Const CHECK\_COL As Long = 1 ' Columna A
Const DEST\_SHEET As String = "Completed"
Const ORIGIN\_COL As Long = 26 ' Columna Z```
Dim dest As Worksheet, rng As Range, cel As Range, lastRow As Long
If Intersect(Target, Me.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Salir
Set dest = ThisWorkbook.Worksheets(DEST_SHEET)
Set rng = Intersect(Target, Me.Columns(CHECK_COL))
For Each cel In rng.Cells
If VarType(cel.Value) = vbBoolean And cel.Value = True Then
lastRow = dest.Cells(dest.Rows.Count, 1).End(xlUp).Row + 1
Me.Cells(cel.Row, ORIGIN_COL).Value = Me.Name
Me.Rows(cel.Row).Copy dest.Rows(lastRow)
Me.Rows(cel.Row).Delete
End If
Next cel
```
Salir:
Application.EnableEvents = True
End Sub
Si el evento no se dispara, asegúrate de que la casilla esté vinculada a la celda de la columna A y que escriba TRUE/FALSE
(booleanos, no texto).
Revertir un movimiento desde la hoja destino
Si marcaste por error y la fila llegó a Completed, puedes desmarcar allí para enviarla de vuelta. Como guardamos el nombre de la hoja origen en la columna Z, basta con escuchar el cambio y actuar:
Option Explicit
Private Sub Worksheet\_Change(ByVal Target As Range)
Const CHECK\_COL As Long = 8 ' H (ajusta si usas otra)
Const ORIGIN\_COL As Long = 26 ' Z```
Dim rng As Range, cel As Range, src As Worksheet, lastRow As Long, srcName As String
If Intersect(Target, Me.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Salir
Set rng = Intersect(Target, Me.Columns(CHECK_COL))
For Each cel In rng.Cells
If VarType(cel.Value) = vbBoolean And cel.Value = False Then
srcName = Me.Cells(cel.Row, ORIGIN_COL).Value
If Len(srcName) > 0 Then
Set src = ThisWorkbook.Worksheets(srcName)
Else
' Fallback si no hubiera origen guardado:
Set src = ThisWorkbook.Worksheets("Trailer Log")
End If
lastRow = src.Cells(src.Rows.Count, 1).End(xlUp).Row + 1
Me.Rows(cel.Row).Copy src.Rows(lastRow)
Me.Rows(cel.Row).Delete
End If
Next cel
```
Salir:
Application.EnableEvents = True
End Sub </code></pre>
<h2>Unir varias hojas origen hacia una hoja resumen</h2>
<p>Con un único controlador a nivel de libro puedes consolidar filas marcadas de cualquier hoja en una <em>Summary</em>, y permitir la reversión desde la propia <em>Summary</em>. Pega esto en <strong>ThisWorkbook</strong>:</p>
<pre><code class="language-vb">Option Explicit
Private Sub Workbook\_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const CHECK\_COL As Long = 8 ' H
Const DEST\_SHEET As String = "Summary" ' Tu hoja resumen
Const ORIGIN\_COL As Long = 26 ' Z```
Dim dest As Worksheet, rng As Range, cel As Range
Dim lastRow As Long, src As Worksheet, srcName As String
On Error GoTo Salir
If Intersect(Target, Sh.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
Set rng = Intersect(Target, Sh.Columns(CHECK_COL))
For Each cel In rng.Cells
If VarType(cel.Value) = vbBoolean Then
' 1) De cualquier hoja origen -> a la hoja resumen si pasa a TRUE
If Sh.Name <> DEST_SHEET And cel.Value = True Then
Set dest = ThisWorkbook.Worksheets(DEST_SHEET)
lastRow = dest.Cells(dest.Rows.Count, 1).End(xlUp).Row + 1
Sh.Cells(cel.Row, ORIGIN_COL).Value = Sh.Name
Sh.Rows(cel.Row).Copy dest.Rows(lastRow)
Sh.Rows(cel.Row).Delete
' 2) Desde la hoja resumen -> devolver al origen si pasa a FALSE
ElseIf Sh.Name = DEST_SHEET And cel.Value = False Then
srcName = Sh.Cells(cel.Row, ORIGIN_COL).Value
If Len(srcName) > 0 Then
Set src = ThisWorkbook.Worksheets(srcName)
lastRow = src.Cells(src.Rows.Count, 1).End(xlUp).Row + 1
Sh.Rows(cel.Row).Copy src.Rows(lastRow)
Sh.Rows(cel.Row).Delete
End If
End If
End If
Next cel
```
Salir:
Application.EnableEvents = True
End Sub
Buenas prácticas y notas esenciales
- Evita bucles de eventos: usa el patrón
Application.EnableEvents = False/True
conOn Error
para restaurar siempre. - Booleans reales: compara con
True
/False
, no con"True"
/"False"
. - Controles de formulario: deben estar vinculados a su celda para que el evento se dispare.
- Rendimiento: evita Select/Activate, y si mueves muchas filas añade
Application.ScreenUpdating=False
y (opcional) cambiaCalculation
axlCalculationManual
retornando al final. - Tablas (ListObjects): en tablas, copiar + borrar es más seguro que Cut. Si necesitas eliminar filas de una tabla, usa
ListRows(...).Delete
. - Valores vs fórmulas: si solo quieres “congelar” valores en destino, después de copiar ejecuta
dest.Rows(lastRow).Value = dest.Rows(lastRow).Value
. - Prueba en una copia: siempre valida en un archivo duplicado antes de pasar a producción.
Versión para tablas (ListObjects) — estable y con estructura
Si tus hojas usan tablas (con encabezados y filtros), te conviene trabajar a nivel de ListObject. Suponiendo que cada hoja tiene una única tabla con el mismo orden de columnas:
Option Explicit
' Pega en el módulo de la hoja origen si usas una sola hoja origen,
' o adapta al patrón Workbook\_SheetChange si consolidas varias.
Private Sub Worksheet\_Change(ByVal Target As Range)
Const CHECK\_COL As Long = 8 ' H
Const DEST\_SHEET As String = "Completed Trailer log"
Const ORIGIN\_COL As Long = 26 ' Z```
Dim loSrc As ListObject, loDst As ListObject
Dim dest As Worksheet, rng As Range, cel As Range
Dim idx As Long, newRow As ListRow
If Intersect(Target, Me.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Salir
Set loSrc = Me.ListObjects(1) ' o por nombre: Me.ListObjects("TrailerTable")
Set dest = ThisWorkbook.Worksheets(DEST_SHEET)
Set loDst = dest.ListObjects(1) ' o por nombre: dest.ListObjects("CompletedTable")
Set rng = Intersect(Target, Me.Columns(CHECK_COL))
For Each cel In rng.Cells
If VarType(cel.Value) = vbBoolean And cel.Value = True Then
' Translate fila de hoja a índice de ListObject (1-based)
idx = cel.Row - loSrc.HeaderRowRange.Row
If idx >= 1 And idx <= loSrc.ListRows.Count Then
Set newRow = loDst.ListRows.Add
' Guarda origen y mueve datos (valores)
Me.Cells(cel.Row, ORIGIN_COL).Value = Me.Name
newRow.Range.Value = loSrc.ListRows(idx).Range.Value
loSrc.ListRows(idx).Delete
End If
End If
Next cel
```
Salir:
Application.EnableEvents = True
End Sub </code></pre>
<p><strong>Notas:</strong> cambia <code>ListObjects(1)</code> por el nombre de tu tabla si tienes varias; asegúrate de que ambas tablas tengan las <strong>mismas columnas</strong> y encabezados.</p>
<h2>Copiar solo valores, sin fórmulas ni formatos</h2>
<p>Tras copiar una fila, puedes “pintar” solo valores y limpiar formatos para que el destino no herede validaciones o formatos no deseados:</p>
<pre><code class="language-vb">' Después de copiar a dest.Rows(lastRow)
With dest.Rows(lastRow)
.Value = .Value ' congela valores
.Interior.Pattern = xlNone
.FormatConditions.Delete
End With
</code></pre>
<h2>Registrar marcas de tiempo y usuario</h2>
<p>Añade trazabilidad creando columnas auxiliares (por ejemplo AA y AB) para “Fecha movimiento” y “Usuario”.</p>
<pre><code class="language-vb">dest.Cells(lastRow, 27).Value = Me.Name ' Z: Origen
dest.Cells(lastRow, 28).Value = Now ' AA: Fecha/hora
dest.Cells(lastRow, 29).Value = Environ$("Username") ' AB: Usuario
</code></pre>
<h2>Mover a otro libro (workbook) de manera segura</h2>
<p>Si el destino está en otro archivo abierto (p. ej., <em>StatusArchive.xlsm</em>):</p>
<pre><code class="language-vb">Dim wbDst As Workbook, dest As Worksheet
Set wbDst = Application.Workbooks("StatusArchive.xlsm")
Set dest = wbDst.Worksheets("Completed Trailer log")
' ... usa el mismo patrón lastRow/copy/delete </code></pre>
<p><strong>Tip:</strong> valida que el libro esté abierto, y considera abrirlo en modo programático si no lo está.</p>
<h2>Diagnóstico de problemas frecuentes</h2>
<table>
<thead>
<tr>
<th>Síntoma</th>
<th>Causa probable</th>
<th>Solución</th>
</tr>
</thead>
<tbody>
<tr>
<td>No pasa nada al marcar la casilla</td>
<td>La casilla no está vinculada a la celda; o la columna no coincide con <code>CHECK_COL</code></td>
<td>Vincula el control a su celda y verifica <code>CHECK_COL</code></td>
</tr>
<tr>
<td>Se mueve pero no se borra en origen</td>
<td>El código usó <em>Copy</em> sin <em>Delete</em></td>
<td>Revisa que esté la línea <code>Rows(...).Delete</code> tras copiar</td>
</tr>
<tr>
<td>Se pega desordenado en destino</td>
<td>Tablas con columnas distintas u orden diferente</td>
<td>Alinea encabezados y cantidad de columnas en ambas hojas</td>
</tr>
<tr>
<td>Errores intermitentes al pegar</td>
<td>Eventos activos / fórmulas volátiles / cálculos pesados</td>
<td>Usa <code>ScreenUpdating=False</code> y considera poner <code>Calculation=Manual</code> durante la operación</td>
</tr>
<tr>
<td>No se dispara al cambiar por fórmula</td>
<td><code>Worksheet_Change</code> no detecta cálculos; solo ediciones</td>
<td>Usa casillas vinculadas (edición directa) o implementa un patrón con <code>Worksheet_Calculate</code></td>
</tr>
</tbody>
</table>
<h2>Higiene del código y patrón “seguro” de eventos</h2>
<p>Para operaciones más largas o lotes de cambios, encapsula ajustes de aplicación y asegúrate de restaurarlos incluso si ocurre un error:</p>
<pre><code class="language-vb">Private Sub SafeMove(ByVal action As String)
Dim ev As Boolean, su As Boolean, calc As XlCalculation
ev = Application.EnableEvents
su = Application.ScreenUpdating
calc = Application.Calculation```
On Error GoTo Fin
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' ... tu lógica de mover filas aquí ...
```
Fin:
Application.EnableEvents = ev
Application.ScreenUpdating = su
Application.Calculation = calc
End Sub
Validaciones previas al movimiento
Si requieres que ciertas columnas tengan datos antes de permitir el movimiento (por ejemplo, fecha de despacho o firma):
If Len(Me.Cells(cel.Row, "E").Value) = 0 Then
MsgBox "Falta la fecha en columna E. Completa antes de marcar.", vbExclamation
GoTo SaltarFila
End If
' ...
SaltarFila:
Evitar problemas al borrar múltiples filas
Cuando varias celdas de la columna de control cambian a la vez (pegar o Relleno), borrar filas dentro del For Each
puede ser delicado. Dos estrategias:
- Recorrer de abajo arriba: procesa primero la fila con índice mayor (menor riesgo de saltos).
- Unión y borrado en bloque: acumula filas a borrar en un Range (Union) y elimina al final.
Ejemplo de borrado en bloque:
Dim toDelete As Range
' dentro del bucle, en lugar de borrar:
If toDelete Is Nothing Then
Set toDelete = Me.Rows(cel.Row)
Else
Set toDelete = Union(toDelete, Me.Rows(cel.Row))
End If
' Al final:
If Not toDelete Is Nothing Then toDelete.Delete
Seguridad, protección y permisos
- Si proteges la hoja, establece la protección con
UserInterfaceOnly:=True
enWorkbook_Open
para que las macros puedan modificar celdas/filas aunque el usuario no.
Private Sub Workbook_Open()
Dim sh As Worksheet
For Each sh In ThisWorkbook.Worksheets
On Error Resume Next
sh.Protect Password:="tu_clave", UserInterfaceOnly:=True
On Error GoTo 0
Next sh
End Sub
FAQ (preguntas rápidas)
- ¿Puedo usar “1/0” o “Sí/No” en lugar de TRUE/FALSE? Sí, pero adapta la validación:
If cel.Value = True Or cel.Value = 1 Or UCase$(cel.Value) = "SÍ" Then ...
. Lo más limpio es usar booleanos. - ¿Se puede colorear la fila al moverla? Sí, basta con aplicar formato tras pegar en destino (
dest.Rows(lastRow).Interior.Color = ...
). - ¿Qué pasa si el destino está vacío?
End(xlUp)
devuelve la última fila con datos o la primera si no hay. Si tu hoja tiene encabezados en fila 1,lastRow
quedará correctamente en 2. - ¿Cómo limito el movimiento a un área específica? Intersecta también con el rango de datos:
Intersect(Target, Me.Range("A2:H500"), Me.Columns(CHECK_COL))
.
Checklist de implementación (paso a paso)
- Decide la columna del checkbox y la hoja destino.
- Inserta casillas de Formulario y vincúlalas a las celdas de esa columna.
- Reserva la columna Z para “Origen”.
- Pega el código de la Solución base en el módulo de la hoja origen.
- Opcional: pega el código de reversión en la hoja destino.
- Opcional: usa la versión Workbook_SheetChange si consolidarás varias hojas.
- Guarda como
.xlsm
, cierra y reabre para habilitar macros; prueba en una copia.
Resumen ejecutivo
- Marcar la casilla en la columna definida mueve (copia + borra) la fila a la hoja destino.
- Desmarcar en la hoja destino revierte el movimiento si guardas el origen en la columna auxiliar (Z).
- Es posible consolidar varias hojas en una Summary con un único manejador en
ThisWorkbook
. - Para tablas, usa
ListObjects
para mayor estabilidad.
Apéndice: plantillas útiles
Plantilla base con valores congelados en destino
Option Explicit
Private Sub Worksheet\_Change(ByVal Target As Range)
Const CHECK\_COL As Long = 8
Const DEST\_SHEET As String = "Completed Trailer log"
Const ORIGIN\_COL As Long = 26```
Dim dest As Worksheet, rng As Range, cel As Range, lastRow As Long
If Intersect(Target, Me.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
Application.ScreenUpdating = False
On Error GoTo Salir
Set dest = ThisWorkbook.Worksheets(DEST_SHEET)
Set rng = Intersect(Target, Me.Columns(CHECK_COL))
For Each cel In rng.Cells
If VarType(cel.Value) = vbBoolean And cel.Value = True Then
lastRow = dest.Cells(dest.Rows.Count, 1).End(xlUp).Row + 1
Me.Cells(cel.Row, ORIGIN_COL).Value = Me.Name
Me.Rows(cel.Row).Copy dest.Rows(lastRow)
' (Opcional) Congelar valores para que el destino no dependa de fórmulas
dest.Rows(lastRow).Value = dest.Rows(lastRow).Value
Me.Rows(cel.Row).Delete
End If
Next cel
```
Salir:
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub </code></pre>
<h3>Plantilla de consolidación con reversión</h3>
<pre><code class="language-vb">Option Explicit
Private Sub Workbook\_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const CHECK\_COL As Long = 8
Const DEST\_SHEET As String = "Summary"
Const ORIGIN\_COL As Long = 26```
Dim dest As Worksheet, rng As Range, cel As Range
Dim lastRow As Long, src As Worksheet, srcName As String
If Intersect(Target, Sh.Columns(CHECK_COL)) Is Nothing Then Exit Sub
Application.EnableEvents = False
On Error GoTo Salir
Set rng = Intersect(Target, Sh.Columns(CHECK_COL))
For Each cel In rng.Cells
If VarType(cel.Value) = vbBoolean Then
If Sh.Name <> DEST_SHEET And cel.Value = True Then
Set dest = ThisWorkbook.Worksheets(DEST_SHEET)
lastRow = dest.Cells(dest.Rows.Count, 1).End(xlUp).Row + 1
Sh.Cells(cel.Row, ORIGIN_COL).Value = Sh.Name
Sh.Rows(cel.Row).Copy dest.Rows(lastRow)
Sh.Rows(cel.Row).Delete
ElseIf Sh.Name = DEST_SHEET And cel.Value = False Then
srcName = Sh.Cells(cel.Row, ORIGIN_COL).Value
If Len(srcName) > 0 Then
Set src = ThisWorkbook.Worksheets(srcName)
lastRow = src.Cells(src.Rows.Count, 1).End(xlUp).Row + 1
Sh.Rows(cel.Row).Copy src.Rows(lastRow)
Sh.Rows(cel.Row).Delete
End If
End If
End If
Next cel
```
Salir:
Application.EnableEvents = True
End Sub
Conclusión
Con este conjunto de patrones en VBA puedes transformar una hoja de cálculo en un flujo operativo fiable: marca una casilla y la fila “cambia de estado”, se limpia de la vista original, y queda trazabilidad del origen e incluso capacidad de reversión. Empieza con la solución base y evoluciona hacia tablas, consolidación y validaciones según crezca tu escenario.