¿Quieres que al cambiar un estado en un desplegable de Excel la fila entera “salte” automáticamente a otra hoja y se borre del origen? Aquí tienes una guía completa con código VBA listo para usar, más variantes robustas para tablas, mapeos de nombres y prevención de errores.
Qué vas a conseguir
Cuando selecciones un estado en la columna I (Estado) de la hoja Register, la fila completa (columnas A:M) se moverá automáticamente a la hoja cuyo nombre coincida con el valor elegido en el desplegable (por ejemplo, Zone1). Si más tarde cambias el estado desde la nueva hoja, el registro volverá a moverse a su nuevo destino. La fila desaparece del origen para evitar duplicados.
Requisitos previos
- Tu columna de estado está en I (o en la que decidas).
- Las hojas de destino existen y sus nombres coinciden exactamente con los valores del desplegable (espacios, mayúsculas, tildes).
- El rango que deseas mover es A:M (puedes ajustarlo).
- Has creado un desplegable (Validación de datos) para el estado.
- El libro permite macros (guárdalo como .xlsm o .xlsb).
Opción A — Rápida: mueve filas solo cuando cambias el estado en “Register”
Esta opción actúa únicamente si el cambio sucede en la hoja Register (ideal si los estados solo se editan ahí).
- Abre el editor de VBA: Alt + F11.
- En el Explorador de proyectos, haz doble clic en la hoja Register.
- En los desplegables superiores, elige Worksheet y luego Change.
- Pega este código:
Private Sub Worksheet_Change(ByVal Target As Range)
Const STATUS_COL As Long = 9 ' I
Const FIRST_COL As String = "A" ' Primer dato de la fila
Const LAST_COL As String = "M" ' Último dato de la fila
On Error GoTo Salir
If Intersect(Target, Columns(STATUS_COL)) Is Nothing Then Exit Sub
If Target.CountLarge > 1 Then Exit Sub
If Len(Target.Value) = 0 Then Exit Sub
Application.EnableEvents = False
Dim wsDest As Worksheet
On Error Resume Next
Set wsDest = ThisWorkbook.Worksheets(CStr(Target.Value)) ' nombre de hoja = valor del desplegable
On Error GoTo 0
If wsDest Is Nothing Then GoTo Salir ' si no existe, salir sin error
Dim rngFila As Range
Set rngFila = Range(FIRSTCOL & Target.Row & ":" & LASTCOL & Target.Row)
Dim nextRow As Long
nextRow = wsDest.Cells(wsDest.Rows.Count, "A").End(xlUp).Row + 1
rngFila.Copy wsDest.Range("A" & nextRow)
' Si quieres quitar solo A:M:
rngFila.Delete Shift:=xlUp
' Si prefieres borrar la fila completa, usa esta línea en su lugar:
' Rows(Target.Row).Delete
Salir:
Application.EnableEvents = True
End Sub
Cuándo elegir esta opción
- Solo editas el estado en “Register”.
- Quieres el código más corto posible.
Opción B — Completa: mueve filas aunque cambies el estado desde cualquier hoja
Esta opción intercepta cambios en todo el libro. Si cambias el estado estando ya en Zone1, Zone2, etc., la fila volverá a moverse a la hoja cuyo nombre coincida con el nuevo estado.
- En el editor VBA, haz doble clic en ThisWorkbook.
- Selecciona Workbook y luego SheetChange.
- Pega este código:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const STATUS_COL As Long = 9 ' I (cámbialo a 7 si usas la columna G)
Const FIRST_COL As String = "A"
Const LAST_COL As String = "M"
On Error GoTo Fin
If Target.CountLarge > 1 Then Exit Sub
If Target.Column <> STATUS_COL Then Exit Sub
If Len(Target.Value) = 0 Then Exit Sub
Dim wsDest As Worksheet
Application.EnableEvents = False
On Error Resume Next
Set wsDest = ThisWorkbook.Worksheets(CStr(Target.Value))
On Error GoTo 0
If wsDest Is Nothing Then GoTo Fin
' Si ya está en la hoja destino, no hacer nada
If wsDest.Name = Sh.Name Then GoTo Fin
Dim rngFila As Range
Set rngFila = Sh.Range(FIRSTCOL & Target.Row & ":" & LASTCOL & Target.Row)
Dim nextRow As Long
nextRow = wsDest.Cells(wsDest.Rows.Count, "A").End(xlUp).Row + 1
rngFila.Copy wsDest.Range("A" & nextRow)
Sh.Rows(Target.Row).Delete
Fin:
Application.EnableEvents = True
End Sub
Ventajas
- Cumple el requisito de mover el registro aunque modifiques el estado desde la hoja en la que esté.
- Evita registros duplicados, ya que borra la fila del origen.
Personalización esencial
Necesidad | Cambio | Ejemplo |
---|---|---|
Usar otra columna para el estado | Modifica STATUS_COL | Columna G → STATUS_COL = 7 |
Mover otro rango de columnas | Edita FIRSTCOL y LASTCOL | De B a N → "B" y "N" |
Eliminar fila completa en lugar de A:M | Usa Rows(Target.Row).Delete | Reemplaza la línea Delete del ejemplo |
Evitar bucles de eventos | Mantén la envoltura Application.EnableEvents | Ya incluido en los ejemplos |
Buenas prácticas de datos: el desplegable
- Usa Validación de datos con una lista de estados (Register, Zone1, Zone2…).
- Los valores deben coincidir exactamente con los nombres de las hojas destino (ojo con mayúsculas, espacios y tildes).
- Si deseas que el usuario solo elija valores válidos, marca “Omitir blancos” y “Mostrar mensaje de error”.
Versión robusta con funciones auxiliares (reutilizable y escalable)
Para proyectos más grandes, conviene encapsular la lógica y añadir utilidades como “siguiente fila libre”, mapeo de estados a hojas con nombres distintos, e incluso creación automática de hojas.
Procedimiento central reutilizable
Pega este módulo estándar (Insert > Module) y nómbralo, por ejemplo, modMoverFilas:
Option Explicit
Public Sub MoverFilaPorEstado(ByVal Sh As Worksheet, ByVal fila As Long, _
ByVal statusValue As String, _
Optional ByVal firstCol As String = "A", _
Optional ByVal lastCol As String = "M", _
Optional ByVal colBusquedaDestino As String = "A", _
Optional ByVal crearSiNoExiste As Boolean = False)
Dim nombreDestino As String
nombreDestino = ResolverHojaDestino(statusValue) ' Permite mapeo flexible
If Len(nombreDestino) = 0 Then Exit Sub
Dim wsDest As Worksheet
Set wsDest = ObtenerHoja(nombreDestino, crearSiNoExiste)
If wsDest Is Nothing Then Exit Sub
' Si ya está en la hoja destino, salir
If wsDest.Name = Sh.Name Then Exit Sub
Dim rngFila As Range
Set rngFila = Sh.Range(firstCol & fila & ":" & lastCol & fila)
Dim nextRow As Long
nextRow = SiguienteFilaLibre(wsDest, colBusquedaDestino)
' Copiar y borrar (valores y formatos)
rngFila.Copy wsDest.Range("A" & nextRow)
Sh.Rows(fila).Delete
End Sub
Public Function SiguienteFilaLibre(ByVal ws As Worksheet, ByVal col As String) As Long
Dim last As Long
last = ws.Cells(ws.Rows.Count, col).End(xlUp).Row
If last = 1 And Application.WorksheetFunction.CountA(ws.Rows(1)) = 0 Then
SiguienteFilaLibre = 1
Else
SiguienteFilaLibre = last + 1
End If
End Function
' Devuelve la hoja correspondiente a un estado. Aquí puedes mapear nombres distintos.
Public Function ResolverHojaDestino(ByVal estado As String) As String
' Mapeo directo por defecto (estado = nombre de hoja)
ResolverHojaDestino = CStr(estado)
' -- EJEMPLO DE MAPE0:
'Select Case UCase$(estado)
' Case "ZONA 1": ResolverHojaDestino = "Zone1"
' Case "ZONA 2": ResolverHojaDestino = "Zone2"
' Case Else: ResolverHojaDestino = CStr(estado)
'End Select
End Function
Public Function ObtenerHoja(ByVal nombre As String, ByVal crear As Boolean) As Worksheet
On Error Resume Next
Set ObtenerHoja = ThisWorkbook.Worksheets(nombre)
On Error GoTo 0
If ObtenerHoja Is Nothing And crear Then
Set ObtenerHoja = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
ObtenerHoja.Name = nombre
End If
End Function
Ahora, desde ThisWorkbook, llama a este procedimiento desde el evento general para controlar cambios en cualquier hoja:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const STATUS_COL As Long = 9 ' I
On Error GoTo Fin
If Target.CountLarge > 1 Then Exit Sub
If Target.Column <> STATUS_COL Then Exit Sub
If Len(Target.Value) = 0 Then Exit Sub
Application.EnableEvents = False
Call MoverFilaPorEstado(Sh, Target.Row, CStr(Target.Value), "A", "M", "A", False)
Fin:
Application.EnableEvents = True
End Sub
Ventajas de la versión modular
- Permite mapear estados a hojas con nombres diferentes.
- Opcionalmente puede crear la hoja si no existe (pon
True
en crearSiNoExiste). - Centraliza la lógica y reduce errores por duplicación de código.
Compatibilidad con Tablas (ListObjects)
Si tus datos están dentro de una Tabla de Excel, borrar filas con Rows(...).Delete
elimina filas completas en la hoja y puede deformar la tabla. En su lugar, borra la fila dentro de la tabla:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const STATUS_COL As Long = 9 ' I
On Error GoTo Salir
If Target.CountLarge > 1 Then Exit Sub
If Target.Column <> STATUS_COL Then Exit Sub
If Len(Target.Value) = 0 Then Exit Sub
Application.EnableEvents = False
Dim wsDest As Worksheet
Set wsDest = Nothing
On Error Resume Next
Set wsDest = ThisWorkbook.Worksheets(CStr(Target.Value))
On Error GoTo 0
If wsDest Is Nothing Then GoTo Salir
Dim firstCol As String: firstCol = "A"
Dim lastCol As String: lastCol = "M"
Dim fila As Long: fila = Target.Row
Dim nextRow As Long
nextRow = wsDest.Cells(wsDest.Rows.Count, "A").End(xlUp).Row + 1
Sh.Range(firstCol & fila & ":" & lastCol & fila).Copy wsDest.Range("A" & nextRow)
' Si Target está en una Tabla, borrar la fila de la tabla:
If Not Target.ListObject Is Nothing Then
Dim lo As ListObject, lr As ListRow
Set lo = Target.ListObject
Dim idx As Long
idx = fila - lo.HeaderRowRange.Row
If idx >= 1 And idx <= lo.ListRows.Count Then
lo.ListRows(idx).Delete
Else
Sh.Rows(fila).Delete
End If
Else
Sh.Rows(fila).Delete
End If
Salir:
Application.EnableEvents = True
End Sub
Copiar valores solamente, preservar fórmulas o mover con “Cortar”
- Solo valores: si no quieres llevarte fórmulas al destino, usa
PasteSpecial
valores:rngFila.Copy wsDest.Range("A" & nextRow).PasteSpecial xlPasteValues
- Incluir formatos: añade otro PasteSpecial para formatos:
wsDest.Range("A" & nextRow).PasteSpecial xlPasteFormats
- “Cortar” en lugar de copiar+borrar: puede ser más rápido y mantiene mejor ciertos formatos. Aun así, borra la fila origen para no dejar huecos:
rngFila.Cut Destination:=wsDest.Range("A" & nextRow) Sh.Rows(Target.Row).Delete
Rendimiento y estabilidad
Para listas grandes, puedes mejorar la velocidad desactivando actualización de pantalla y cálculo mientras se ejecuta la macro:
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' ... tu lógica ...
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Recuerda restaurar siempre estos valores al final, incluso si hay errores (úsalo dentro de bloques con On Error
y una etiqueta de salida).
Prevención de bucles y errores
- Reentradas: rodea tu lógica con
Application.EnableEvents = False/True
. Ya está aplicado en los ejemplos. - Cambios masivos: los ejemplos ignoran cambios con múltiples celdas (
Target.CountLarge > 1
). - Filas de encabezado: si no quieres reacción en encabezados, añade
If Target.Row = 1 Then Exit Sub
. - Estados vacíos: se ignoran con
Len(Target.Value) = 0
. - Hojas inexistentes: el código sale silenciosamente sin error. Si lo prefieres, créala automáticamente.
Crear la hoja automáticamente si no existe
Si haces crecer la lista de estados con frecuencia, puede ser útil crear la hoja destino en tiempo real (opcional):
Set wsDest = ObtenerHoja(CStr(Target.Value), True) ' True = crear si no existe
Auditoría: registrar los movimientos
Para rastrear cuándo y desde dónde se movió cada fila, puedes llevar un log en una hoja “Historial”:
Sub RegistrarMovimiento(ByVal origen As String, ByVal destino As String, ByVal filaOrigen As Long)
Dim ws As Worksheet, r As Long
Set ws = ObtenerHoja("Historial", True)
r = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
ws.Cells(r, "A").Value = Now
ws.Cells(r, "B").Value = origen
ws.Cells(r, "C").Value = destino
ws.Cells(r, "D").Value = filaOrigen
End Sub
Invoca RegistrarMovimiento Sh.Name, wsDest.Name, Target.Row
justo antes de borrar la fila origen.
Mapeo de estados a hojas con nombres distintos
Si tu Validación de datos muestra textos “bonitos” (“Zona 1”) pero tus hojas se llaman diferente (“Zone1”), usa un Select Case o un Diccionario en ResolverHojaDestino
:
Public Function ResolverHojaDestino(ByVal estado As String) As String
Select Case Trim$(UCase$(estado))
Case "REGISTRAR", "REGISTER": ResolverHojaDestino = "Register"
Case "ZONA 1": ResolverHojaDestino = "Zone1"
Case "ZONA 2": ResolverHojaDestino = "Zone2"
Case Else: ResolverHojaDestino = CStr(estado)
End Select
End Function
Preguntas frecuentes (FAQ)
¿Se puede deshacer (Ctrl+Z) después de que la macro mueva la fila?
Por norma general, no. Las macros limpian la pila de deshacer. Usa la hoja “Historial” y realiza pruebas en una copia.
¿Qué pasa si la hoja destino está protegida?
Podrías obtener un error al pegar o al insertar filas. Quita la protección temporalmente en el procedimiento y vuelve a protegerla al final.
¿Funcionará si el estado proviene de una fórmula?WorksheetChange
no se dispara por cambios de resultados de fórmula; necesitarías WorksheetCalculate
(lógica distinta) o desencadenar el cambio en una celda editable por el usuario.
¿Puedo mover más/menos columnas?
Sí. Cambia FIRSTCOL
y LASTCOL
.
¿Puedo mover la fila a un libro distinto?
Sí, pero requiere referenciar otro Workbook
y manejar aperturas y guardados. Empieza resolviendo wsDest
en el otro libro.
Checklist rápido antes de usar en producción
- El estado está en la columna correcta (
STATUS_COL
coincide). - El rango de columnas a mover es el que necesitas (
A:M
por defecto). - Los nombres de hojas y los valores del desplegable coinciden o están mapeados.
- La protección de hoja/libro no bloquea borrados o pegados.
- Eventos y pantalla se restauran aunque algo falle (bloques On Error y etiquetas de salida).
- Has probado el comportamiento con tres estados y varios registros.
Solución al caso “no pasa nada” al usar la columna G
Si cambias el estado en G y no sucede nada:
- Verifica que el código esté en el módulo correcto (la hoja “Register” para la Opción A, o ThisWorkbook para la Opción B).
- Cambia
STATUS_COL
a 7. - Asegúrate de que el valor del desplegable exista como hoja real.
- No pegues múltiples celdas a la vez (el código ignora cambios masivos).
- Comprueba que
Application.EnableEvents
no está desactivado por otro procedimiento. - Revisa si hay protección de hoja/libro que impida borrar filas.
Tabla comparativa: Opción A vs Opción B
Criterio | Opción A (Worksheet) | Opción B (Workbook) |
---|---|---|
Dónde actúa | Solo en Register | En todo el libro |
Complejidad | Baja | Media |
Riesgo de efectos colaterales | Menor | Mayor (controla más casos) |
Edición desde hojas destino | No mueve | Sí mueve |
Escalabilidad | Limitada | Alta (recomendado con versión modular) |
Ejemplo completo final (libro entero, con mapeo y log opcional)
Este es un ejemplo “todo en uno” que puedes adaptar rápidamente. Pega el módulo y el evento:
Módulo estándar modMoverFilas
Option Explicit
Public Sub MoverFilaPorEstadoConLog(ByVal Sh As Worksheet, ByVal fila As Long, \_
ByVal estado As String, \_
Optional ByVal firstCol As String = "A", \_
Optional ByVal lastCol As String = "M", \_
Optional ByVal crearSiNoExiste As Boolean = False, \_
Optional ByVal registrar As Boolean = True)```
Dim destino As String
destino = ResolverHojaDestino(estado)
If Len(destino) = 0 Then Exit Sub
Dim wsDest As Worksheet
Set wsDest = ObtenerHoja(destino, crearSiNoExiste)
If wsDest Is Nothing Then Exit Sub
If Sh.Name = wsDest.Name Then Exit Sub
Dim rng As Range
Set rng = Sh.Range(firstCol & fila & ":" & lastCol & fila)
Dim nextRow As Long
nextRow = SiguienteFilaLibre(wsDest, "A")
rng.Copy wsDest.Range("A" & nextRow)
Sh.Rows(fila).Delete
If registrar Then RegistrarMovimiento Sh.Name, wsDest.Name, fila
```
End Sub
Public Function ResolverHojaDestino(ByVal estado As String) As String
Select Case Trim\$(UCase\$(estado))
Case "REGISTER": ResolverHojaDestino = "Register"
Case "ZONE1", "ZONA 1": ResolverHojaDestino = "Zone1"
Case "ZONE2", "ZONA 2": ResolverHojaDestino = "Zone2"
Case Else: ResolverHojaDestino = CStr(estado)
End Select
End Function
Public Function ObtenerHoja(ByVal nombre As String, ByVal crear As Boolean) As Worksheet
On Error Resume Next
Set ObtenerHoja = ThisWorkbook.Worksheets(nombre)
On Error GoTo 0
If ObtenerHoja Is Nothing And crear Then
Set ObtenerHoja = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
ObtenerHoja.Name = nombre
End If
End Function
Public Function SiguienteFilaLibre(ByVal ws As Worksheet, ByVal col As String) As Long
Dim last As Long
last = ws.Cells(ws.Rows.Count, col).End(xlUp).Row
If last < 1 Then last = 1
SiguienteFilaLibre = IIf(Application.WorksheetFunction.CountA(ws.Rows(1)) = 0, 1, last + 1)
End Function
Public Sub RegistrarMovimiento(ByVal origen As String, ByVal destino As String, ByVal filaOrigen As Long)
Dim ws As Worksheet, r As Long
Set ws = ObtenerHoja("Historial", True)
r = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1
ws.Cells(r, "A").Value = Now
ws.Cells(r, "B").Value = origen
ws.Cells(r, "C").Value = destino
ws.Cells(r, "D").Value = filaOrigen
End Sub </code></pre>
<h3>Evento del libro <em>ThisWorkbook</em></h3>
<pre><code class="language-vba">Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const STATUS_COL As Long = 9 ' I
On Error GoTo Fin```
If Target.CountLarge > 1 Then Exit Sub
If Target.Column <> STATUS_COL Then Exit Sub
If Len(Target.Value) = 0 Then Exit Sub
Application.EnableEvents = False
Application.ScreenUpdating = False
' Si trabajas con tablas y necesitas borrar dentro de la tabla, puedes adaptar aquí
Call MoverFilaPorEstadoConLog(Sh, Target.Row, CStr(Target.Value), "A", "M", False, True)
```
Fin:
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Casos de uso recomendados
- Embudo de atención al cliente: Register → Asignaciones por zona.
- Gestión de tareas: Backlog → En curso → Hecho, con hojas por estado.
- RR. HH.: Candidatos → Entrevista → Oferta → Contratados.
- Logística: Pedidos → Preparación → En tránsito → Entregados.
Resumen
Con el evento Change
a nivel de hoja o libro y unas pocas líneas de VBA, puedes automatizar el movimiento de registros en Excel según un estado elegido en un desplegable. Empieza con la Opción A si solo editas en Register; pasa a la Opción B (o a la versión modular) cuando necesites que el sistema funcione desde cualquier hoja, con mapeos, tablas y registro de auditoría.
Plantilla rápida de decisiones
- ¿Editas solo en Register? → Opción A.
- ¿Editas desde cualquier hoja? → Opción B o versión modular.
- ¿Estados ≠ nombres de hojas? → Mapeo en
ResolverHojaDestino
. - ¿Datos en Tabla? → Borrado con
ListRows.Delete
. - ¿Necesitas historial? → Usa
RegistrarMovimiento
.