use std::collections::HashMap; use std::fmt; pub struct Simulation { mortgage: Mortgage, updates: MortgageUpdates, history: Vec, topay_total: f64, topay_interest: f64, payed_principal: f64, payed_interest: f64, payed_amortized: f64, } impl Simulation { pub fn new(principal: f64, i1: f64, years: u32) -> Self { let pending_quotas = years * 12; let i12 = i1 / 12.0; let mortgage = Mortgage { period: 0, principal, i12, monthly: Mortgage::monthly(principal, i12, pending_quotas), pending_quotas, }; let topay_total = mortgage.monthly * pending_quotas as f64; Simulation { mortgage, topay_total, topay_interest: topay_total - principal, payed_principal: 0., payed_interest: 0., payed_amortized: 0., history: vec![], updates: HashMap::new(), } } pub fn run(&mut self, updates: MortgageUpdates) { self.updates = updates; let mut mortgage = self.mortgage.clone(); while mortgage.pending_quotas > 0 && mortgage.principal > 0. { let quota = mortgage.step(); self.payed_principal += quota.principal; self.payed_interest += quota.interest; self.history.push(quota); self.apply_updates(&mut mortgage); } } 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.pending_quotas); self.payed_amortized += *principal; } None => (), } } pub fn render_table(&self) { let mortgage = &self.mortgage; println!("\n========================================================"); println!( "=== HIPOTECA: {}€, A {} AÑOS, INTERÉS FIJO {:.2}% ===", mortgage.principal, mortgage.pending_quotas / 12, mortgage.i12 * 12. * 100., ); println!("========================================================"); println!("\n\n# SIMULACIÓN CUOTAS\n"); println!("Year | Mon | Quota ( Amrtzd + Intrst) | Pending"); 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} ({:.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 Quota { period: u32, interest: f64, principal: f64, pending_principal: f64, } impl fmt::Display for Quota { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{:4} | {:3} | {:7.2} ({:7.2} + {:7.2}) | {:10.2}", if (self.period - 1) % 12 == 0 { format!("Y{}", (self.period / 12) + 1) } else { // return Ok(()); "".to_string() }, self.period, self.interest + self.principal, self.principal, self.interest, self.pending_principal ) } } #[derive(Clone)] pub struct Mortgage { period: u32, principal: f64, i12: f64, monthly: f64, pending_quotas: u32, } impl Mortgage { fn monthly(principal: f64, i12: f64, pending_quotas: u32) -> f64 { principal * i12 / (1.0 - (1.0 + i12).powi(-(pending_quotas as i32))) } fn step(&mut self) -> Quota { let interest = self.quota_interest(); let principal = self.quota_principal(); self.period += 1; self.pending_quotas -= 1; self.principal = (1.0 + self.i12) * self.principal - self.monthly; Quota { period: self.period, interest, principal, pending_principal: self.principal, } } 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 pending_quotas]") } } } }