use std::collections::HashMap; use std::fmt; pub struct Simulation { history: Vec, updates: MortgageUpdates, topay_total: f64, topay_interest: f64, payed_principal: f64, payed_interest: f64, payed_amortized: f64, } impl Simulation { fn run(&mut self, mut mortgage: Mortgage) { while mortgage.quotas > 0 && mortgage.principal > 0. { self.history.push(mortgage.clone()); self.apply_updates(&mut mortgage); self.payed_principal += mortgage.quota_principal(); self.payed_interest += mortgage.quota_interest(); mortgage.step(); } } fn apply_updates(&mut self, mortgage: &mut Mortgage) { match self.updates.get_mut(&mortgage.period) { Some(MortgageUpdate::Amortize(principal)) => { if *principal > mortgage.principal { *principal = mortgage.principal; }; mortgage.principal -= *principal; mortgage.monthly = Mortgage::monthly(mortgage.principal, mortgage.i12, mortgage.quotas); self.payed_amortized += *principal; } None => (), } } pub fn render_table(&self) { let mortgage = &self.history[0]; println!("\n========================================================"); println!( "=== HIPOTECA: {}€, A {} AÑOS, INTERÉS FIJO {:.2}% ===", mortgage.principal, mortgage.quotas / 12, mortgage.i12 * 12. * 100., ); println!("========================================================"); println!("\n\n# SIMULACIÓN CUOTAS\n"); println!("Year | Mon | Quota ( Intrst + Amrtzd) | Principal"); for mortgage in self.history.iter() { print!("{mortgage}"); if let Some(update) = self.updates.get(&mortgage.period) { print!(" {update}"); } println!(); } let payed_total = self.payed_principal + self.payed_interest + self.payed_amortized; println!("\n\n# A PRIORI\n"); println!( "== Total a pagar: {:.2} ({} cap + {:.2} int)", self.topay_total, mortgage.principal, self.topay_interest ); println!( "== Los intereses suponen un {:.2}% del total", 100. * self.topay_interest / self.topay_total ); println!("\n\n# RESULTADO FINAL\n"); println!( "== Total pagado: {:.2} ({:.2} cap + {:.2} int + {:.2} amortizado)", payed_total, self.payed_principal, self.payed_interest, self.payed_amortized ); println!( "== Los intereses suponen un {:.2}% del total", 100. * self.payed_interest / payed_total ); println!(); } } #[derive(Clone)] pub struct Mortgage { period: u32, principal: f64, i12: f64, monthly: f64, quotas: u32, } impl fmt::Display for Mortgage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{:4} | {:3} | {:7.2} ({:7.2} + {:7.2}) | {:10.2}", if self.period % 12 == 0 { format!("Y{}", (self.period / 12) + 1) } else { // return Ok(()); "".to_string() }, self.period, self.monthly, self.quota_interest(), self.quota_principal(), self.principal ) } } impl Mortgage { pub fn new(principal: f64, i1: f64, years: u32) -> Self { let quotas = years * 12; let i12 = i1 / 12.0; Mortgage { period: 0, principal, i12, monthly: Self::monthly(principal, i12, quotas), quotas, } } pub fn simulate(&mut self, updates: MortgageUpdates) -> Simulation { let topay_total = self.monthly * self.quotas as f64; let mut sim = Simulation { topay_total, topay_interest: topay_total - self.principal, payed_principal: 0., payed_interest: 0., payed_amortized: 0., history: vec![], updates, }; sim.run(self.clone()); sim } fn monthly(principal: f64, i12: f64, quotas: u32) -> f64 { principal * i12 / (1.0 - (1.0 + i12).powi(-(quotas as i32))) } fn step(&mut self) { self.period += 1; self.quotas -= 1; self.principal = (1.0 + self.i12) * self.principal - self.monthly; } fn quota_interest(&self) -> f64 { self.i12 * self.principal } fn quota_principal(&self) -> f64 { self.monthly - self.quota_interest() } } pub type MortgageUpdates = HashMap; #[derive(Clone)] pub enum MortgageUpdate { Amortize(f64), } impl fmt::Display for MortgageUpdate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[MORTGAGE UPDATE: ")?; match self { Self::Amortize(principal) => { write!(f, "{principal:.2} amortized to reduce quotas]") } } } }