David Garner - Cashflow Rentals | USA Property Investments - Page 2
Ir al contenido
About David Garner
David Garner cuenta con más de 120 adquisiciones de propiedades personales en el mercado inmobiliario estadounidense como extranjero no residente, lo que aporta una amplia experiencia práctica a sus conocimientos sobre el mercado inmobiliario estadounidense. Está especializado en guiar a inversores internacionales a través de las complejidades del mercado inmobiliario estadounidense, centrándose en la creación de carteras de propiedades de alquiler rentables. Su profundo conocimiento del mercado, combinado con su enfoque centrado en el cliente, lo convierten en un asesor de confianza para los inversores internacionales que buscan establecer y hacer crecer su cartera inmobiliaria en Estados Unidos.
Enlace de carga de página
*/
(function(){
const $ = (id) => document.getElementById(id);
const fmt = (n, opts={}) => {
const v = (isFinite(n) ? n : 0);
const o = { style:'currency', currency:'USD', maximumFractionDigits:0, ...opts };
return new Intl.NumberFormat('en-US', o).format(v);
};
const pct = (n, d=2) => `${(isFinite(n)? n : 0).toFixed(d)}%`;function payment(principal, annualRatePct, years) {
const r = (annualRatePct/100)/12;
const n = years*12;
if (r === 0) return principal/n;
return principal * (r * Math.pow(1+r, n)) / (Math.pow(1+r, n) - 1);
}
function balanceAfter(principal, annualRatePct, years, monthsElapsed) {
const r = (annualRatePct/100)/12;
const n = years*12;
if (r === 0) return principal * (1 - monthsElapsed/n);
const pmt = payment(principal, annualRatePct, years);
return principal*Math.pow(1+r, monthsElapsed) - pmt*( (Math.pow(1+r, monthsElapsed) - 1)/r );
}
function cumulativePrincipalPaid(principal, annualRatePct, years, monthsElapsed){
return principal - balanceAfter(principal, annualRatePct, years, monthsElapsed);
}const ids = [
'price','downPct','rate','term','rent','otherIncome','vacancy','mgmtPct',
'taxesAnnual','insAnnual','hoaMonthly','maintPct','repairsMonthly','otherMonthly','closingPct','initialRepairs',
'holdYears','appreciation','rentGrowth','expGrowth','sellCostPct','landPct','incomeTaxPct','cgTaxPct'
];
const out = {
cashflow: $('m-cashflow'),
caprate: $('m-caprate'),
coc: $('m-coc'),
dscr: $('m-dscr'),
ads: $('m-ads'),
noi: $('m-noi'),gsr: $('o-gsr'),
vac: $('o-vac'),
egi: $('o-egi'),
opex:$('o-opex'),
mort:$('o-mort'),
oNoi:$('o-noi'),ltSale: $('lt-sale'),
ltBal: $('lt-balance'),
ltSell: $('lt-sellcosts'),
ltCF: $('lt-cf'),
ltPrin: $('lt-principal'),
ltNet: $('lt-net'),
ltProfit: $('lt-profit'),
ltRoi: $('lt-roi'),
ltAnn: $('lt-annualized'),cashNeeded: $('m-cashneeded')
};
if (!out.cashflow) return;// Defaults
const defaults = {
price: 300000, downPct: 25, rate: 7.25, term: 30,
rent: 2200, otherIncome: 0, vacancy: 5, mgmtPct: 8,
taxesAnnual: 3600, insAnnual: 1500, hoaMonthly: 0, maintPct: 8,
repairsMonthly: 100, otherMonthly: 0, closingPct: 3, initialRepairs: 0,
holdYears: 5, appreciation: 3, rentGrowth: 2, expGrowth: 2, sellCostPct: 8,
landPct: 20, incomeTaxPct: 24, cgTaxPct: 15
};// Initialize placeholders
ids.forEach(id => { const el=$(id); if(el && !el.value) el.value = defaults[id] ?? ''; });function valNum(id){
const el = $(id);
if(!el) return 0;
const v = parseFloat(String(el.value).replace(/,/g,''));
return isFinite(v) ? v : 0;
}function recalc(){
// Inputs
const price = valNum('price');
const downPct = valNum('downPct');
const rate = valNum('rate');
const term = valNum('term');const rent = valNum('rent');
const otherIncome = valNum('otherIncome');
const vacancy = valNum('vacancy');
const mgmtPct = valNum('mgmtPct');const taxesAnnual = valNum('taxesAnnual');
const insAnnual = valNum('insAnnual');
const hoaMonthly = valNum('hoaMonthly');
const maintPct = valNum('maintPct');const repairsMonthly = valNum('repairsMonthly');
const otherMonthly = valNum('otherMonthly');
const closingPct = valNum('closingPct');
const initialRepairs = valNum('initialRepairs');const holdYears = valNum('holdYears');
const appreciation = valNum('appreciation');
const rentGrowth = valNum('rentGrowth');
const expGrowth = valNum('expGrowth');
const sellCostPct = valNum('sellCostPct');// Financing
const downPayment = price * (downPct/100);
const loanAmount = Math.max(price - downPayment, 0);
const mortPmt = (loanAmount>0 && term>0) ? payment(loanAmount, rate, term) : 0;
const ads = mortPmt * 12;// Income (monthly)
const grossMonthly = rent + otherIncome;
const vacancyLoss = grossMonthly * (vacancy/100);
const egiMonthly = grossMonthly - vacancyLoss;// Operating expenses (monthly)
const mgmtMonthly = rent * (mgmtPct/100); // mgmt on rent only (common)
const maintMonthly = rent * (maintPct/100);
const taxesMonthly = taxesAnnual/12;
const insMonthly = insAnnual/12;const opexMonthly = mgmtMonthly + maintMonthly + taxesMonthly + insMonthly + hoaMonthly + repairsMonthly + otherMonthly;// NOI (annual, pre-debt)
const noiAnnual = (egiMonthly - opexMonthly) * 12;// Metrics
const capRate = price>0 ? (noiAnnual/price)*100 : 0;
const monthlyCashFlow = egiMonthly - opexMonthly - mortPmt;
const cashNeeded = downPayment + (price * (closingPct/100)) + initialRepairs;
const coc = cashNeeded>0 ? ((monthlyCashFlow*12)/cashNeeded)*100 : 0;
const dscr = ads>0 ? (noiAnnual/ads) : 0;// Long-term (simplified projections)
const months = holdYears*12;
const futurePrice = price * Math.pow(1 + (appreciation/100), holdYears);
const sellCosts = futurePrice * (sellCostPct/100);
const loanBal = balanceAfter(loanAmount, rate, term, months);
const principalPaid = cumulativePrincipalPaid(loanAmount, rate, term, months);// Grow rent & expenses with separate growth rates (straight-line monthly comp approx)
const growthRentMonthly = rent * Math.pow(1 + (rentGrowth/100), holdYears);
const avgRentMonthly = (rent + growthRentMonthly)/2; // simple average over period
const avgOtherIncMonthly = otherIncome; // assume flat
const avgGrossMonthly = avgRentMonthly + avgOtherIncMonthly;
const avgVacLoss = avgGrossMonthly * (vacancy/100);
const avgMgmtMonthly = avgRentMonthly * (mgmtPct/100);
const growthMaintMonthly = (rent * (maintPct/100)) * Math.pow(1 + (expGrowth/100), holdYears);
const avgMaintMonthly = (rent*(maintPct/100) + growthMaintMonthly)/2;const growthTaxesMonthly = taxesMonthly * Math.pow(1 + (expGrowth/100), holdYears);
const avgTaxesMonthly = (taxesMonthly + growthTaxesMonthly)/2;const growthInsMonthly = insMonthly * Math.pow(1 + (expGrowth/100), holdYears);
const avgInsMonthly = (insMonthly + growthInsMonthly)/2;const growthHOA = hoaMonthly * Math.pow(1 + (expGrowth/100), holdYears);
const avgHOA = (hoaMonthly + growthHOA)/2;const growthRep = repairsMonthly * Math.pow(1 + (expGrowth/100), holdYears);
const avgRep = (repairsMonthly + growthRep)/2;const growthOther = otherMonthly * Math.pow(1 + (expGrowth/100), holdYears);
const avgOther = (otherMonthly + growthOther)/2;const avgEGIMonthly = avgGrossMonthly - avgVacLoss;
const avgOpexMonthly = avgMgmtMonthly + avgMaintMonthly + avgTaxesMonthly + avgInsMonthly + avgHOA + avgRep + avgOther;
const avgNOIAnnual = (avgEGIMonthly - avgOpexMonthly) * 12;
const avgMonthlyCashFlow = avgEGIMonthly - avgOpexMonthly - mortPmt;const cfCumulative = avgMonthlyCashFlow * months;
const netProceeds = Math.max(futurePrice - sellCosts - loanBal, 0);
const totalProfitPreTax = cfCumulative + principalPaid + (netProceeds); // double counts? No: netProceeds already subtracts remaining loan; principalPaid counts via cash flow equity growth — we keep both cash flow + net proceeds (which is equity at sale). principalPaid is embedded in netProceeds vs original loan? Common approaches vary; we’ll show both CF and Net separately below and include principal explicitly.
// To avoid double counting principal: treat netProceeds as (future equity realized) and ONLY add CF. We'll present principalPaid separately in UI.
const totalProfit = cfCumulative + netProceeds; // preferred: CF + Net Sale (no double-count)
const roiTotal = cashNeeded>0 ? (totalProfit / cashNeeded) * 100 : 0;
const annualizedSimple = holdYears>0 ? (roiTotal/holdYears) : 0;// Outputs
out.cashflow.textContent = fmt(monthlyCashFlow, {maximumFractionDigits:0});
out.caprate.textContent = pct(capRate, 2);
out.noi.textContent = `NOI: ${fmt(noiAnnual, {maximumFractionDigits:0})}/yr`;
out.coc.textContent = pct(coc, 2);
out.cashNeeded.textContent = `Cash Invested: ${fmt(cashNeeded, {maximumFractionDigits:0})}`;
out.dscr.textContent = `${(isFinite(dscr)? dscr : 0).toFixed(2)}×`;
out.ads.textContent = `Annual Debt Service: ${fmt(ads, {maximumFractionDigits:0})}`;out.gsr.textContent = fmt(grossMonthly, {maximumFractionDigits:0});
out.vac.textContent = `−${fmt(vacancyLoss, {maximumFractionDigits:0})}`;
out.egi.textContent = fmt(egiMonthly, {maximumFractionDigits:0});
out.opex.textContent = `−${fmt(opexMonthly, {maximumFractionDigits:0})}`;
out.oNoi.textContent = fmt(noiAnnual, {maximumFractionDigits:0});
out.mort.textContent = `−${fmt(mortPmt, {maximumFractionDigits:0})}`;out.ltSale.textContent = fmt(futurePrice, {maximumFractionDigits:0});
out.ltBal.textContent = fmt(Math.max(loanBal,0), {maximumFractionDigits:0});
out.ltSell.textContent = `−${fmt(sellCosts, {maximumFractionDigits:0})}`;
out.ltCF.textContent = fmt(cfCumulative, {maximumFractionDigits:0});
out.ltPrin.textContent = fmt(Math.max(principalPaid,0), {maximumFractionDigits:0});
out.ltNet.textContent = fmt(netProceeds, {maximumFractionDigits:0});out.ltProfit.textContent = fmt(totalProfit, {maximumFractionDigits:0});
out.ltRoi.textContent = pct(roiTotal, 2);
out.ltAnn.textContent = pct(annualizedSimple, 2);
}// Wire up listeners
ids.forEach(id=>{
const el = $(id);
if(!el) return;
el.addEventListener('input', recalc);
el.addEventListener('change', recalc);
});// Buttons
const btnSample = document.getElementById('rpc-load-sample');
const btnReset = document.getElementById('rpc-reset');btnSample && btnSample.addEventListener('click', ()=>{
Object.entries(defaults).forEach(([k,v])=>{ const el=$(k); if(el) el.value=v; });
recalc();
const adv = document.getElementById('rpc-adv');
if(adv && !adv.open) adv.open = true;
});
btnReset && btnReset.addEventListener('click', ()=>{
ids.forEach(id=>{ const el=$(id); if(el){ el.value=''; }});
// sensible blank state
recalc();
});// Initial
if (out.cashflow) { recalc(); }
})();