Definir clases de objetos en VBA nos permite generalizar una estructura de datos y un conjunto de comportamientos (métodos) asociados a ella. Una vez creada, podemos generar infinitas instancias de esa clase que posean las propiedades y métodos que hemos definido, haciendo nuestro código más limpio (y abstracto).
Un ejemplo sería considerar el flujo de caja de un proyecto como una serie de tiempo desde el tiempo 0 hasta el plazo de evaluación, una estructura de datos tipo array, cuyos métodos podrían incluir el cálculo de valores presentes, futuros, tasas de retorno y payback.
Lo primero que hacemos es insertar un nuevo módulo de clase en el editor VBA que llamaremos FlujoCaja.

Comenzamos definiendo en el módulo las variables internas que almacenarán las propiedades de nuestra clase (la estructura de datos propiamente tal):
Option Explicit
Private arrFlujo As Variant
Private sngTasaDescuento As Single
Private dblValorPresente As Double
Private dblValorFuturo As Double
Private sngTIR As Single
Private sngPayback As Single
Private strUnidad As String
Private strPeriodo As String
Utilizaremos el método Initialize de la clase para iniciar todas las variables en cero. Este método se ejecutará cada vez que creemos una nueva instancia de nuestra clase, mediante la instrucción: Set fcNuevo = New FlujoCaja
Private Sub Class_Initialize()
arrFlujo = 0
sngTasaDescuento = 0
dblValorPresente = 0
dblValorFuturo = 0
sngTIR = 0
sngPayback = 0
strUnidad = ""
strPeriodo = ""
End Sub
Para cada una de las propiedades definimos métodos que permitan a su usuario leer los datos (Get) o escribir nuevos datos en ellas (Let), según corresponda.
'Propiedad Flujo
Public Property Get Flujo() As Variant
Flujo = arrFlujo
End Property
Public Property Let Flujo(vntArreglo As Variant)
arrFlujo = vntArreglo
End Property
'Propiedad Plazo
Public Property Get Plazo() As Integer
Plazo = UBound(arrFlujo)
End Property
'Propiedad TasaDescuento
Public Property Get TasaDescuento() As Single
TasaDescuento = sngTasaDescuento
End Property
Public Property Let TasaDescuento(sngTasa As Single)
sngTasaDescuento = sngTasa
End Property
'Propiedad ValorPresente
Public Property Get ValorPresente() As Double
ValorPresente = dblValorPresente
End Property
'Propiedad ValorFuturo
Public Property Get ValorFuturo() As Double
ValorFuturo = dblValorFuturo
End Property
'Propiedad TIR
Public Property Get TIR() As Single
TIR = sngTIR
End Property
'Propiedad Payback
Public Property Get Payback() As Single
Payback = sngPayback
End Property
'Propiedad Unidad
Public Property Get Unidad() As String
Unidad = strUnidad
End Property
Public Property Let Unidad(strU As String)
strUnidad = strU
End Property
'Propiedad Periodo
Public Property Get Periodo() As String
Periodo = strPeriodo
End Property
Public Property Let Periodo(strP As String)
strPeriodo = strP
End Property
Como vemos, algunas propiedades como FlujoCaja, Periodo, Unidad son de lectura y escritura, mientras que otras que se generan como resultado (Plazo, TIR, Payback, ValorPresente, ValorFuturo) son solo de lectura.
Una vez definida la estructura de datos que contendrá, le «enseñamos» a nuestra clase algunos trucos (métodos) que necesitará para ser útil al usuario.
Añadiremos un método que permita leer los datos del flujo a partir de un determinado rango de Excel (rango fila o columna). Definimos para ello un Public Sub que tendrá como argumento el rango a leer. Nuestro procedimiento almacenará en la variable interna arrFlujo el arreglo leído desde Excel para su posterior uso.
Public Sub LeerRango(ByRef rngRango As Range)
Dim arrAux() As Double
Dim i As Integer
Dim rngCelda As Range
ReDim arrAux(rngRango.Count)
i = 0
For Each rngCelda In rngRango
ReDim Preserve arrAux(i)
arrAux(i) = rngCelda.Value
i = i + 1
Next rngCelda
arrFlujo = arrAux
End Sub
Para calcular rentabilidades y valores presentes o futuros, contamos con dos alternativas. La primera es realizar todos los cálculos de una sola vez e ir almacenando los resultados en las variables internas para su posterior consulta. Podríamos crear un nuevo Public Sub que invoque cada uno de los cálculos:
Public Sub Evaluar()
CalcularValorPresente
CalcularTIR
CalcularPayback
CalcularValorFuturo
End Sub
Desde el código en que usemos una nueva instancia de nuestra clase, deberemos primero ejecutar éste método: fcNuevo.Evaluar y luego recién leer las propiedades respectivas.
Este camino es especialmente útil cuando los cálculos son intensivos en recursos y sólo queremos realizarlos una vez, mientras que los resultados pueden ser invocados en múltiples oportunidades.
En esta aplicación en particular, ninguno de los cálculos es muy intensivo en recursos y es más, queremos que se realicen dinámicamente para poder realizar algunas iteraciones. La segunda alternativa parece ser más eficaz en este caso: Reemplazaremos los métodos Get de cada una de estas propiedades por funciones que nos permitan calcularlas «en tiempo real»
'Propiedad ValorPresente
Public Property Get ValorPresente() As Double
Dim i As Integer
dblValorPresente = 0
For i = 0 To UBound(arrFlujo)
dblValorPresente = dblValorPresente + _
arrFlujo(i) / ((1 + sngTasaDescuento) ^ i)
Next i
ValorPresente = dblValorPresente
End Property
'Prpiedad ValorFuturo
Public Property Get ValorFuturo() As Double
Dim i As Integer
dblValorFuturo = 0
For i = 0 To UBound(arrFlujo)
dblValorFuturo = dblValorFuturo + _
arrFlujo(i) * ((1 + sngTasaDescuento) ^ i)
Next i
ValorFuturo = dblValorFuturo
End Property
'Propiedad TIR
Public Property Get TIR() As Single
sngTIR = Application.WorksheetFunction.IRR(arrFlujo)
TIR = sngTIR
End Property
Cuando el payback no es un número exacto de periodos, lo calculamos interpolando linealmente entre los dos periodos que se produce el cambio de signo al evaluar el flujo acumulado:
'Propiedad Payback
Public Property Get Payback() As Single
Dim i As Integer
Dim dblsuma As Double
dblsuma = 0
sngPayback = -1 'valor retornado si no se encuentra payback
For i = 0 To UBound(arrFlujo)
If dblsuma < 0 And dblsuma + arrFlujo(i) >= 0 Then
'existe un cambio de signo entre i-1 e i
'interpolamos el payback
sngPayback = i - 1 - dblsuma / arrFlujo(i)
End If
dblsuma = dblsuma + arrFlujo(i)
Next i
Payback = sngPayback
End Property
Ya creada nuestra clase FlujoCaja, estamos listos para usarla en futuros códigos, como en el sencillo ejemplo a continuación:

En un nuevo módulo creamos, por ejemplo, un procedimiento Test en el que creamos un nuevo objeto de nuestra clase FlujoCaja y usamos los procedimientos creados para leer los datos desde el rango señalado en amarillo y para calcular valor presente, futuro, tir y payback.
Sub Test()
Dim fcFlujo As FlujoCaja
Set fcFlujo = New FlujoCaja
fcFlujo.LeerRango Hoja1.Range("B2:E2")
fcFlujo.TasaDescuento = 0.1
Hoja1.Range("B3") = fcFlujo.ValorPresente
Hoja1.Range("B4") = fcFlujo.TIR
Hoja1.Range("B5") = fcFlujo.Payback
Hoja1.Range("B6") = fcFlujo.ValorFuturo
Hoja1.Range("B7") = fcFlujo.Plazo
End Sub
La simplicidad de este nuevo código es evidente. Nuestra nueva clase podemos exportarla a cada planilla Excel que necesitemos y, por supuesto, podemos seguirla mejorando e incorporando nuevas funcionalidades.
¿Qué usos se te vienen en mente para esta nueva clase? ¿Qué otras posibles mejoras o versiones podrían agregarle valor? ¡Nos vemos pronto!