// Fichero de funciones javascript para calculos de matemática financiera.
// Autor: Fco. Javier Aranda Granda (Unidad Internet-Intranet/BdE)
// Versión 070410

// Funciones de Rentas Uniformes Finitas.
// Es el caso habitual de los préstamos.
// Se entrega o se recibe un valor inicial a cambio de un contravalor periódico,
//   uniforme (la mísma cuota en cada periodo) y finito (un número limitado de cuotas).
// - rufFactorValorInicial(numPeriodos:int, interesPeriodo:double):double
//   Calcula el factor V0/C para una Renta Uniforme Finita
// - rufDuracion(factorValorInicial:double, interesPeriodo:double):double
//   Calcula la duración de una R.U.F. que tendría un depósito con V0/C y r indicados
// - rufInteresPeriodo(factorValorInicial:double, numPeriodos:int):double
//   Calcula el interés por periodo
// - rufDerivadaFactorValorInicialPorInteres(numPeriodos: int, interesPeriodo:double):double
//   Calcula la derivada (parcial en función de r) del factor V0/C.

// Funciones de conversión de intereses nominales y anuales
// - convNomATae(a_interesNom:double , a_periodosPorAnyo:double):double
//  Convierte un interés nominal a un interés TAE(o TIE)
// - convTaeANom(a_interesTae:double , a_periodosPorAnyo:double):double
//  Convierte un interés TAE a un interés Nominal
// - convTaeAPer(a_interesTae:double , a_periodosPorAnyo:double):double
//  Convierte un interés TAE a un interés periódico
// - convPerATae(a_interesPer:double , a_periodosPorAnyo:double):double
//  Convierte un interés periódico a un interés TAE
//
// Funciones adicionales
// - redondea(importe: double, decimales: int):double
//  Redondea un importe a la magnitud más cercana con n decimales fraccionarios.
// - rufCapitalPendienteEnPeriodo(v0:double,cuota:double,periodo:int,ratePeriodo:double):double
//  Obtiene el capital pendiente de amortizar en un periodo N.
// - redondea(importe:double, decimales:int)
// Redondea el importe/magnitud indicado al valor más cercano con el número de
//   decimales fraccionarios de precisión indicados.
// - rufCapitalPendienteEnPeriodo(a_v0:double, a_cuota:double, a_periodo:int, a_ratePeriodo:double)
//  Calcula el capital pendiente de amortizar tras liquidar un determinado periodo intermedio.


// ADVERTENCIA PARA MANTENIMIENTO / ENLACES INFORMATIVOS:
// - Esta implementación se basa en diversos conceptos de relativa complejidad.
//   A consecuencia de ello el personal destinado a mantenimiento previsiblemente
//   tendrá dificultades para comprender el funcionamiento.
// - De estos temas complejos no es nuestro cometido explicarlo detalladamente.
//   En cualquier el autor menciona otras fuentes para adquirir dichos conceptos,
//   y expresa las fórmulas concretas que se utilizan.
// Conceptos especiales utilizados.
// - Matemática financiera:Rentas Uniformes Finitas.
//   http://www.matematicas-financieras.com/
// - Método de aproximaciones sucesivas Newton-Raphson (para obtener el interés)
//   http://es.wikipedia.org/wiki/M%C3%A9todo_de_Newton
// - Derivadas (Necesarias para el método de Newton)
//   http://es.wikipedia.org/wiki/Derivada
//   http://www.google.es/search?hl=es&q=Tabla+derivadas

// ######
// FUNCIONES RENTAS UNIFORMES FINITAS
// Convención símbolos:
// V0: Valor recibido inicialmente en prestamo
// r: Interés por periodo (en tantos por uno). r de "rate"(interés)
// N: Número de periodos de la renta.
// ######

/**
 * Obtiene el factor V0/C de la renta.
 * Se puede utilizar para obtener el valor inicial a partir de las cuotas, y viceversa.
 * @param numPeriodos Número de cuotas de que consta la renta. Debe ser mayor que 0.
 * @param interesPeriodo Interés por periodo (No TAE ni nominal, y en tantos por uno).
 * @return Factor V0/C de la renta.
 */
function rufFactorValorInicial(numPeriodos, interesPeriodo) {
	// V0=c*((((1+r)^n)-1)/(i*(1+r)^n))
	// Caso especial i==0: V0/c == n
	// Precondición: interes > -1; numPeriodos > 0.
	if (interesPeriodo == 0.0) {
		return numPeriodos;
	} else {
		// factorFinal (V0=VF*factorFinal) = (1+r)^n
		var l_factorFinal = Math.pow(1 + interesPeriodo, numPeriodos);
		return (l_factorFinal - 1)/(interesPeriodo * l_factorFinal);
	}
}

/**
 * Obtiene la duración de una renta uniforme finita.
 * @param factorValorInicial Factor V0/C del préstamo. Mayor que 0.0.
 * @param interesPeriodo Interés por periodo en tantos por uno. Mayor que -1.0.
 * @return Duración en periodos. Puede no ser entero.
 *   Es responsabilidad del llamador de redondear la duración
 *   y recalcular las otras variables. Mayor que 0.0.
 */
function rufDuracion(factorValorInicial, interesPeriodo) {
	// n = -(Ln(1 - r * [V0/c]) / Ln(1+r))
	// Caso especial r==0 -> n = V0/c
	// Precondición: interes > -1; factorValorInicial > 0.
	if (interesPeriodo == 0.0) {
		return factorValorInicial;
	} else {
		return -(Math.log(1.0 - interesPeriodo * factorValorInicial) /
				Math.log(1.0+interesPeriodo));
	}
}

/**
 * Obtiene el interés periódico de una renta uniforme finita.
 * Es algo complicado porque la variable interés no se puede despejar de las ecuaciones, y se
 * necesita utilizar un algoritmo de aproximaciones sucesivas.
 * En la actualidad (2007-04-10) utilizamos un algoritmo Newton-Raphson.
 * Esta implementación NO permite intereses negativos (por simplificar)
 * @param factorValorInicial Factor V0/C a conseguir. Mayor que 0.0, menor o igual que numPeriodos.
 * @param numPeriodos Número de periodos de la renta. Mayor que 0.
 * @return Interés por periodo y en tantos por uno calculado. Mayor que 0,0.
 */
function rufInteresPeriodo(factorValorInicial, numPeriodos) {
	// F(r)= V0/c = (1 - (1 + r)^n) / r
	// La variable interés no se consigue despejar de las ecuaciones.
	// Ello nos obliga a utilizar métodos numéricos de "cero de función"/aproximaciones sucesivas.
	// Precondiciones: r >= 0; V0/c: (0,N]; N > 0;
	// Para el cálculo elegimos utilizar el método numérico de Newton-Raphson
	// F(r) > 0 para todo r; F(-1)==+Infinito;F(Infinito)==0;F'(r)<0 para todo i. F''(r)>0 para todo i.
	// Es decir: La función es decreciente y cóncava en todo el intervalo válido (-1;infinito).
	// Ello nos lleva a elegir hipótesis de partida bajas.
	// -1+Epsilon sería adecuado universalmente, pero ineficiente (al principio aproximará lento).
	// Para valores [V0/c] <= n (casi seguro) elegimos r inicial igual a 0.
	var l_interes = 0.0;
	// Diferencia en el factor V0/C obtenido desde la iteración anterior.
	var l_difValor = 1.0;
	// Si por algún motivo nos estancamos, evitamos un cuelgue.
	var l_maxIteracionesRestantes = 20;
	do {
		var l_valor = rufFactorValorInicial(numPeriodos, l_interes);
		var l_derivada = rufDerivadaFactorValorInicialPorInteres(numPeriodos, l_interes);
		l_difValor = l_valor - factorValorInicial;
		var l_delta = l_difValor / l_derivada;
		l_interes -= l_delta;
		l_maxIteracionesRestantes--;
	// Finalizamos cuando el error relativo sea despreciable,
	// o tengamos motivos para pensar que nos hemos quedado estancados.
	} while ((Math.abs(l_difValor / factorValorInicial) > 1E-8) &&
			(l_maxIteracionesRestantes > 0) );
	return l_interes;
}

/**
 * Calcula al derivada de V0/C en función del interés.
 * Sólo se espera que se utilice para calcular las aproximaciones sucesivas del cálculo del interés.
 * @param numPeriodos
 * @param interesPeriodo
 * @return
 */
function rufDerivadaFactorValorInicialPorInteres(numPeriodos, interesPeriodo) {
	// F(r) = (1 - (1 + r)^n) / r
	// F'(r) = (-(1+r)^(n+1) + (1+r) + n*r) / (r^2 * (1+r)^(n+1))
	// F'(0) = [(1+0)^-1 + (1+0)^-2 + ... + (1+0)^-n]' = -1 - 2 - ... - n = -n*(n+1)/2
	if (interesPeriodo == 0.0) {
		return -numPeriodos * (numPeriodos+1) / 2.0;
	} else {
		var l_1masR = 1.0 + interesPeriodo;
		var l_1masR_exp_Nmas1 = Math.pow(l_1masR, numPeriodos + 1);
		var l_resultado = (l_1masR + (numPeriodos * interesPeriodo) - l_1masR_exp_Nmas1) /
				(interesPeriodo * interesPeriodo * l_1masR_exp_Nmas1);
		return l_resultado;
	}
}


//############
// FUNCIONES DE CONVERSIÓN DE INTERESES
// Convención de símbolos, nombres.
// IN,Nom: Interés nominal (Interés periodo / #periodos por año)
// IA,TAE,TIE: Interés Anualizado.
// r,Per: Interés periódico.
// * Salvo que se lo contrario los intereses se manejan en tantos por uno y no en tantos por ciento.
//############
/**
 * Obtiene el interés nominal a partir del interés TAE.
 * @param a_interesTae Interés TAE en tantos por uno.
 * @param a_periodosPorAnyo Número de periodos de cómputo por año del interés nominal (por ejemplo 12 para mensual).
 * @return Interés Nominal en tantos por uno.
 */
function convNomATae(a_interesTae, a_periodosPorAnyo) {
	return a_periodosPorAnyo * ( Math.pow(1.0 + a_interesTae, 1.0 / a_periodosPorAnyo) - 1.0);
}

/**
 * Obtiene el interés TAE a partir del interés nominal.
 * @param a_interesNominal Interés Nominal en tantos por uno.
 * @param a_periodosPorAnyo Número de periodos de cómputo por año del interés nominal (por ejemplo 12 para mensual).
 * @return Interés TAE en tantos por uno.
 */
function convTaeANom(a_interesNominal, a_periodosPorAnyo) {
	return  Math.pow(1.0 + (a_interesNominal / a_periodosPorAnyo), a_periodosPorAnyo) - 1.0;
}

/**
 * Obtiene el interés TAE a partir del interés por periodo.
 * @param a_interesPeriodo Interés por periodo en tantos por uno.
 * @param a_periodosPorAnyo Número de periodos de cómputo por año (por ejemplo 12 para mensual).
 * @return Interés TAE en tantos por uno.
 */
function convTaeAPer(a_interesPeriodo, a_periodosPorAnyo) {
	return  Math.pow(1.0 + a_interesPeriodo, 1.0 / a_periodosPorAnyo) - 1.0;
}

/**
 * Obtiene el interés por periodo a partir del interés TAE.
 * @param a_interesTae Interés TAE en tantos por uno.
 * @param a_periodosPorAnyo Número de periodos de cómputo por año (por ejemplo 12 para mensual).
 * @return Interés periódico en tantos por uno.
 */
function convPerATae(a_interesTae, a_periodosPorAnyo) {
	return  Math.pow(1.0 + a_interesTae, a_periodosPorAnyo) - 1.0;
}


/**
 * Redondea el importe/magnitud indicado al valor más cercano con el número de
 *   decimales fraccionarios de precisión indicados.
 * En caso de equidistancia (por ejemplo 3,005), se redondeará hacia arriba (3,01)
 * @param importe El importe que deseamos redondear.
 * @param decimales Número de decimales fraccionarios de precisión del resultado.
 */
function redondea(importe, decimales) {
	var l_factorEscala = Math.pow(10.0, decimales);
	return Math.round(importe * l_factorEscala) / l_factorEscala;
}

/**
 * Calcula el capital pendiente de amortizar tras liquidar un determinado periodo intermedio.
 * @param a_v0 Capital inicial
 * @param a_cuota Importe pagado en cada periodo
 * @param a_periodo Número de periodos desde el inicio.
 * @param a_ratePeriodo Tipo de interés por periodo y en tantos por uno.
 * @return Capital pendiente tras liquidar el periodo indicado.
 */
function rufCapitalPendienteEnPeriodo(a_v0, a_cuota, a_periodo, a_ratePeriodo) {
	var l_factorApreciacion = Math.pow(1.0 + a_ratePeriodo, a_periodo);
	var l_valorInicialRentaCuotas = rufFactorValorInicial(a_periodo, a_ratePeriodo) * a_cuota;
	return l_factorApreciacion * (a_v0 - l_valorInicialRentaCuotas);
}
