Excel VBA: mover una fila automáticamente al marcar una casilla (checkbox) y revertirla

¿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.

Índice

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

  1. Guarda el archivo como .xlsm (habilitado para macros).
  2. 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.
  3. Reserva una columna auxiliar (recomendado Z) para registrar la hoja de origen. Eso facilita revertir movimientos.

Mapa rápido de configuración

ParámetroDescripciónEjemplo
CHECK_COLColumna con la casilla (número: A=1, B=2, …)H → 8
DEST_SHEETNombre de la hoja destinoCompleted Trailer log
ORIGIN_COLColumna auxiliar que guarda el nombre de la hoja origenZ → 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 &lt;&gt; 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) &gt; 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 con On 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) cambia Calculation a xlCalculationManual 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:

  1. Recorrer de abajo arriba: procesa primero la fila con índice mayor (menor riesgo de saltos).
  2. 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 en Workbook_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)

  1. Decide la columna del checkbox y la hoja destino.
  2. Inserta casillas de Formulario y vincúlalas a las celdas de esa columna.
  3. Reserva la columna Z para “Origen”.
  4. Pega el código de la Solución base en el módulo de la hoja origen.
  5. Opcional: pega el código de reversión en la hoja destino.
  6. Opcional: usa la versión Workbook_SheetChange si consolidarás varias hojas.
  7. 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 &lt;&gt; 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) &gt; 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.

Índice