martes, julio 20, 2010

Control de cambios personalizado en Excel – segunda nota

He recibido varios comentarios sobre la nota de ayer sobre el control de cambios personalizado. Entre ellos me piden dos modificaciones que encuentro muy apropiadas:

1 – que el último cambio aparezca en primer lugar en el cuadro de texto, de manera que siempre veamos los últimos cambios;

2 – que los cambios queden registrados en una hoja del cuaderno.

Ambas modificaciones son bastante sencillas y en esta nota mostraremos cómo hacerlas.

Para que el último cambio aparezca siempre en primer lugar en el cuadro de texto modificamos levemente el código de la macro "track_change"

Sub track_change()
    Dim strPrevious As String
    
    With ActiveCell
        If .Comment Is Nothing Then
            .AddComment Text:=Format(Now, "dd/mm/yy hh:mm") & " - " & .Value
        Else
            strPrevious = .Comment.Text
            .Comment.Text Text:=Format(Now, "dd/mm/yy hh:mm") & " - " & .Value _
                                & Chr(10) & strPrevious
        End If
    End With
    
    Call record_in_sheet
    
End Sub


Todo lo que hacemos es cambiar el orden de construcción del texto del comentario poniendo la variable "strPrevious" al final de la cadena de texto.

Para registrar los cambios en una hoja, empezamos, obviamente, por agregar la hoja al cuaderno



Como ven, hemos agregado la hoja "control" con cuatro campos (columnas): celda, fecha y hora, valor e introducido por.

El código para introducir los datos en esta hoja es el siguiente

Sub record_in_sheet()
    Dim lngFirstFreeRow As Long
  
    With Sheets("control")
        lngFirstFreeRow = WorksheetFunction.CountA(.Range("A:A")) + 1
        .Cells(lngFirstFreeRow, 1).Value = ActiveCell.Address
        .Cells(lngFirstFreeRow, 2).Value = Format(Now, "dd/mm/yy hh:mm")
        .Cells(lngFirstFreeRow, 3).Value = ActiveCell.Value
        .Cells(lngFirstFreeRow, 4).Value = Environ("username")
    End With
  
End Sub


Como ven, sencillo y sin muchas vueltas. Para activar el código hemos agregado la línea "Call record_in_sheet" en el código anterior. De esta manera, el evento de la hoja hace correr el código que crea el comentario con los cambios y este código a su vez, hace correr la macro que registra el cambio en la hoja "control".

Después de introducir y cambiar algunos valores en la lista de precios



La hoja de control los refleja de esta manera

lunes, julio 19, 2010

Control de cambios personalizado en Excel

Hay situaciones en las cuales queremos guardar una historia de los cambios que realizamos en una celda o rango determinado. Por ejemplo, en una lista de precios queremos guardar la historia de los precios anteriores de cada artículo.

Una posibilidad es usar la funcionalidad Control de Cambios que nos ofrece Excel



EL problema con esta funcionalidad es que convierte el cuaderno en compartido, con la consiguiente pérdida de funcionalidades.

Un cliente me pidió que encontrara una solución más práctica. Mi solución fue utilizar los comentarios para guardar la historia de los cambios, asignándole a cada valor en el comentario la fecha en que fue introducido en la celda.

Mi solución utiliza una macro para crear y agregar información a los comentarios y un evento para disparar la macro cada vez que se cambia el valor de una celda en el rango relevante.

Al ingresar por primera vez un precio en la lista, se genera un comentario como este



Después de realizar un cambio en la celda, el comentario refleja los dos valores "históricos" de la celda



El código da la macro es


Sub track_change()
    Dim strPrevious As String
   
    With ActiveCell
        If .Comment Is Nothing Then
            .AddComment Text:=Format(Now, "dd/mm/yy hh:mm") & " - " & .Value
       
        Else
            strPrevious = .Comment.Text
            .Comment.Text Text:=strPrevious & Chr(10) & _
                        Format(Now, "dd/mm/yy hh:mm") & " - " & .Value
        End If
   
    End With
   
   
End Sub


Este código hay que ponerlo en un módulo corriente del editor de Vb.

El evento hay que ponerlo en el módulo de la hoja relevante


Private Sub Worksheet_Change(ByVal Target As Range)

    If Union(Target, Range("rngPrecio")).Address = Range("rngPrecio").Address _
            Then Call track_change
   
End Sub


Como pueden apreciar, estoy utilizando un rango dinámico dentro de un nombre (rngPrecio) para controlar el rango donde aplicamos el control de cambios

=DESREF(Hoja1!$C$2,1,,CONTARA(Hoja1!$B:$B),1)



En nuestro ejemplo, el rango se aplica dinámicamente a la columna C que contiene los precios. Un detalle importante en la definición del nombre es "anclar" el rango en la celda C2. Esto es necesario para el caso que el usuario elimine todas las líneas de la lista de precios (estamos suponiendo que no borrará los encabezamientos).

Y un cambio más antes de publicar la nota



Un inconveniente con esta solución es que el tamaño del cuadro de texto de los comentarios no se adapta dinámicamente a la cantidad de texto contenido. Para visualizar todos los cambios, en algún momento deberemos cambiar el tamaño del cuadro manualmente.

martes, julio 13, 2010

Filtro Avanzado – valores únicos en otra hoja

Ya hemos mencionado en el pasado la posibilidad de extraer valores únicos de una lista con Filtro Avanzado.

En Excel 2007 usamos Datos-Ordenar y Filtrar-Avanzadas



El problema con este método, tanto con Excel Clásico como con Excel 2007, es que aparentemente podemos extraer la lista de valores únicos sólo a la hoja que contiene la lista



Pero si leemos con atención el mensaje de Excel, vemos que dice "Sólo se puede copiar los datos filtrados a la hoja activa". Y remarco "hoja activa", ya que esta es la clave para resolver el "problema".

Cuando queremos copiar valores únicos de una lista a una hoja distinta de la hoja que contiene los datos, sencillamente activamos el Filtro Avanzado desde la hoja que recibirá la lista de valores únicos. De esta manera la hoja activa es la que recibirá los valores únicos.

Veamos el caso en nuestro ejemplo. La lista de nombres, con varios valores repetidos, está en la hoja "lista"; nuestro objetivo es copiar la lista de valores únicos en la hoja "únicos".

Elegimos la celda de la hoja "únicos" donde queremos poner los valores únicos (A1 en nuestro caso) y activamos Filtro Avanzado y ponemos todas las definiciones excepto "Rango de la lista"



Ahora señalamos el rango de la lista



Al apretar "Aceptar" los valores únicos serán copiados al rango deseado.
Antes de descubrir esta solución (y supongo que no soy el primero en descubrirla) me había embarcado a escribir una macro para hacer la tarea. Moraleja: dedicar siempre unos segundos para evaluar si entendemos lo que nos dicen.