VBA Una clase Flujo de Caja

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!

Deja un comentario