{"id":908,"date":"2026-05-09T07:45:34","date_gmt":"2026-05-09T07:45:34","guid":{"rendered":"https:\/\/sfai.pt\/?page_id=908"},"modified":"2026-05-09T07:45:34","modified_gmt":"2026-05-09T07:45:34","slug":"908-2","status":"publish","type":"page","link":"https:\/\/sfai.pt\/en\/908-2\/","title":{"rendered":""},"content":{"rendered":"<p><!DOCTYPE html><br \/>\n<html lang=\"pt-PT\"><br \/>\n<head><br \/>\n<meta charset=\"UTF-8\"><br \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, viewport-fit=cover\"><br \/>\n<title>Simulador IHRU: Investimento Habitacional<\/title><\/p>\n<p><!-- PWA --><br \/>\n<meta name=\"theme-color\" content=\"#4B384C\"><br \/>\n<meta name=\"description\" content=\"An\u00e1lise por cen\u00e1rio de descontos sobre rendas (Portaria 53\/2024) com financiamento IHRU 1.\u00ba Direito\"><\/p>\n<p><!-- iOS support --><br \/>\n<meta name=\"apple-mobile-web-app-capable\" content=\"yes\"><br \/>\n<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"><br \/>\n<meta name=\"apple-mobile-web-app-title\" content=\"Simulador IHRU\">\n<link rel=\"apple-touch-icon\" href=\"apple-touch-icon.png\">\n<p><!-- Favicons -->\n<link rel=\"icon\" type=\"image\/png\" sizes=\"32x32\" href=\"favicon-32.png\">\n<link rel=\"icon\" type=\"image\/png\" sizes=\"16x16\" href=\"favicon-16.png\">\n<p><script src=\"https:\/\/cdn.jsdelivr.net\/npm\/xlsx-js-style@1.2.0\/dist\/xlsx.bundle.js\"><\/script><\/p>\n<style>\n:root {\n  --primary: #4B384C;\n  --primary-light: #6B5C6C;\n  --secondary: #AF95A6;\n  --secondary-light: #D7C5CD;\n  --bg: #FAF8F9;\n  --surface: #FFFFFF;\n  --border: #E5DDE0;\n  --text: #2A2128;\n  --text-soft: #6B5C6C;\n  --input-bg: #FFFCDC;\n  --positive: #2D6A4F;\n  --negative: #9B2226;\n  --shadow: 0 1px 3px rgba(75, 56, 76, 0.08);\n}<\/p>\n<p>* { box-sizing: border-box; margin: 0; padding: 0; }<\/p>\n<p>body {\n  font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', sans-serif;\n  background: var(--bg);\n  color: var(--text);\n  line-height: 1.5;\n  font-size: 14px;\n}<\/p>\n<p>.container { max-width: 1280px; margin: 0 auto; padding: 24px; }<\/p>\n<p>header.app-header {\n  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);\n  color: #fff;\n  padding: 28px 32px;\n  border-radius: 6px;\n  margin-bottom: 24px;\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;\n  flex-wrap: wrap;\n  gap: 16px;\n}<\/p>\n<p>.app-header .title h1 { font-size: 22px; letter-spacing: 0.3px; margin-bottom: 4px; }\n.app-header .title p { color: var(--secondary-light); font-size: 13px; }<\/p>\n<p>.actions { display: flex; gap: 10px; flex-wrap: wrap; }<\/p>\n<p>.btn {\n  background: #fff;\n  color: var(--primary);\n  border: none;\n  padding: 10px 18px;\n  border-radius: 4px;\n  font-family: inherit;\n  font-size: 13px;\n  font-weight: bold;\n  cursor: pointer;\n  transition: all 0.2s;\n  letter-spacing: 0.3px;\n}<\/p>\n<p>.btn:hover { background: var(--secondary-light); transform: translateY(-1px); }<\/p>\n<p>.btn-secondary {\n  background: rgba(255,255,255,0.15);\n  color: #fff;\n  border: 1px solid rgba(255,255,255,0.4);\n}<\/p>\n<p>.btn-secondary:hover { background: rgba(255,255,255,0.25); }<\/p>\n<p>\/* ========= Cart\u00e3o compacto de ac\u00e7\u00f5es no cabe\u00e7alho ========= *\/\n.actions-card{\n  display:inline-flex; align-items:center; gap:2px;\n  padding:4px 6px;\n  background:rgba(255,255,255,0.12);\n  border:1px solid rgba(255,255,255,0.35);\n  border-radius:8px;\n  -webkit-backdrop-filter:saturate(140%) blur(2px);\n  backdrop-filter:saturate(140%) blur(2px);\n  font-size:12px;\n  align-self:flex-start;\n}\n.action-btn{\n  display:inline-flex; align-items:center; gap:6px;\n  background:transparent; border:0; cursor:pointer;\n  color:#fff; font-family:inherit; font-weight:600; letter-spacing:.2px;\n  padding:6px 10px; border-radius:6px; line-height:1;\n  transition: background 0.2s;\n}\n.action-btn:hover{ background:rgba(255,255,255,0.18); }\n.action-btn:focus-visible{ outline:2px solid #fff; outline-offset:1px; }\n.action-ico{ font-size:14px; }\n.action-sep{ width:1px; height:16px; background:rgba(255,255,255,0.35); }\n@media (max-width:520px){\n  .actions-card{ padding:3px 4px; gap:0; align-self:stretch; justify-content:center; }\n  .action-btn{ padding:6px 10px; }\n  .action-txt{ display:none; }\n  .action-ico{ font-size:15px; }\n}\n@media print{ .actions-card{ display:none !important; } }<\/p>\n<p>.section {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  margin-bottom: 20px;\n  overflow: hidden;\n  box-shadow: var(--shadow);\n}<\/p>\n<p>.section-header {\n  background: var(--primary);\n  color: #fff;\n  padding: 12px 20px;\n  font-size: 14px;\n  font-weight: bold;\n  letter-spacing: 0.5px;\n  text-transform: uppercase;\n}<\/p>\n<p>.section-body { padding: 20px; }<\/p>\n<p>.grid { display: grid; gap: 14px; }\n.grid-2 { grid-template-columns: repeat(2, 1fr); }\n.grid-3 { grid-template-columns: repeat(3, 1fr); }\n.grid-4 { grid-template-columns: repeat(4, 1fr); }\n.grid-5 { grid-template-columns: repeat(5, 1fr); }<\/p>\n<p>.field { display: flex; flex-direction: column; gap: 4px; }\n.field label { font-size: 12px; color: var(--text-soft); font-weight: bold; }\n.field input {\n  padding: 8px 10px;\n  border: 1px solid var(--border);\n  border-radius: 3px;\n  font-family: inherit;\n  font-size: 13px;\n  background: var(--input-bg);\n  color: var(--primary);\n  font-weight: bold;\n}<\/p>\n<p>.field select {\n  padding: 8px 10px;\n  border: 1px solid var(--border);\n  border-radius: 3px;\n  font-family: inherit;\n  font-size: 13px;\n  background: var(--input-bg);\n  color: var(--primary);\n  font-weight: bold;\n  cursor: pointer;\n  appearance: auto;\n}<\/p>\n<p>.field select:focus {\n  outline: 2px solid var(--secondary);\n  outline-offset: -1px;\n}<\/p>\n<p>.field input:focus {\n  outline: 2px solid var(--secondary);\n  outline-offset: -1px;\n}<\/p>\n<p>.field-readonly input {\n  background: #F0EBED;\n  color: var(--text);\n  font-weight: normal;\n}<\/p>\n<p>.subsection-title {\n  font-size: 13px;\n  color: var(--primary);\n  font-weight: bold;\n  margin: 18px 0 10px 0;\n  padding-bottom: 6px;\n  border-bottom: 2px solid var(--secondary-light);\n  text-transform: uppercase;\n  letter-spacing: 0.3px;\n}<\/p>\n<p>.subsection-title:first-child { margin-top: 0; }<\/p>\n<p>table { width: 100%; border-collapse: collapse; font-size: 13px; }<\/p>\n<p>table th, table td {\n  padding: 8px 10px;\n  text-align: right;\n  border-bottom: 1px solid var(--border);\n}<\/p>\n<p>table th {\n  background: var(--secondary-light);\n  color: var(--primary);\n  font-weight: bold;\n  text-align: center;\n  font-size: 12px;\n  text-transform: uppercase;\n  letter-spacing: 0.3px;\n}<\/p>\n<p>table td:first-child, table th:first-child { text-align: left; }<\/p>\n<p>table tr.total-row td {\n  background: #FFF4D9;\n  font-weight: bold;\n  border-top: 2px solid var(--primary);\n}<\/p>\n<p>table tr.subtotal-row td {\n  background: #F5EFE0;\n  font-weight: bold;\n}<\/p>\n<p>.scrollable {\n  max-height: 460px;\n  overflow-y: auto;\n  border: 1px solid var(--border);\n  border-radius: 3px;\n}<\/p>\n<p>.scrollable table thead th {\n  position: sticky;\n  top: 0;\n  z-index: 2;\n}<\/p>\n<p>.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }<\/p>\n<p>.kpi {\n  background: linear-gradient(135deg, #fff 0%, #FAF6F8 100%);\n  border: 1px solid var(--border);\n  border-radius: 5px;\n  padding: 14px 16px;\n  border-left: 4px solid var(--secondary);\n}<\/p>\n<p>.kpi.kpi-primary { border-left-color: var(--primary); }<\/p>\n<p>.kpi-label {\n  font-size: 11px;\n  color: var(--text-soft);\n  text-transform: uppercase;\n  letter-spacing: 0.5px;\n  margin-bottom: 4px;\n  font-weight: bold;\n}<\/p>\n<p>.kpi-value {\n  font-size: 18px;\n  font-weight: bold;\n  color: var(--primary);\n}<\/p>\n<p>.kpi-sub {\n  font-size: 11px;\n  color: var(--text-soft);\n  margin-top: 2px;\n}<\/p>\n<p>.scenario-headers th { background: var(--primary); color: #fff; }<\/p>\n<p>.positive { color: var(--positive); font-weight: bold; }\n.negative { color: var(--negative); font-weight: bold; }<\/p>\n<p>.note {\n  font-size: 12px;\n  color: var(--text-soft);\n  font-style: italic;\n  margin-top: 10px;\n  padding: 10px 14px;\n  background: #F5EFE0;\n  border-left: 3px solid var(--secondary);\n  border-radius: 3px;\n}<\/p>\n<p>\/* ===== Charts ===== *\/\n.chart-grid {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 18px;\n  margin-bottom: 14px;\n}<\/p>\n<p>.chart-card {\n  background: linear-gradient(180deg, #fff 0%, #FAF6F8 100%);\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  padding: 16px 18px 14px;\n  box-shadow: var(--shadow);\n}<\/p>\n<p>.chart-title {\n  font-size: 13px;\n  font-weight: bold;\n  color: var(--primary);\n  margin-bottom: 4px;\n  text-transform: uppercase;\n  letter-spacing: 0.4px;\n}<\/p>\n<p>.chart-subtitle {\n  font-size: 11px;\n  color: var(--text-soft);\n  margin-bottom: 12px;\n}<\/p>\n<p>.chart-svg-wrap {\n  width: 100%;\n  position: relative;\n}<\/p>\n<p>.chart-svg-wrap svg {\n  width: 100%;\n  height: auto;\n  display: block;\n  font-family: inherit;\n}<\/p>\n<p>.chart-svg-wrap svg text {\n  font-family: 'Trebuchet MS', sans-serif;\n  fill: var(--text-soft);\n  font-size: 10px;\n}<\/p>\n<p>.chart-svg-wrap svg .axis-label {\n  fill: var(--text-soft);\n  font-size: 9px;\n}<\/p>\n<p>.chart-svg-wrap svg .grid-line {\n  stroke: var(--border);\n  stroke-width: 0.5;\n}<\/p>\n<p>.chart-svg-wrap svg .axis-zero {\n  stroke: var(--primary-light);\n  stroke-width: 1;\n  stroke-dasharray: 2 2;\n}<\/p>\n<p>.chart-svg-wrap svg .bar-positive { fill: var(--secondary); }\n.chart-svg-wrap svg .bar-negative { fill: #C97B7E; }<\/p>\n<p>.chart-legend {\n  display: flex;\n  gap: 14px;\n  flex-wrap: wrap;\n  margin-top: 10px;\n  font-size: 11px;\n  color: var(--text-soft);\n  padding-top: 10px;\n  border-top: 1px solid var(--border);\n}<\/p>\n<p>.legend-item {\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  white-space: nowrap;\n}<\/p>\n<p>.legend-swatch {\n  width: 14px;\n  height: 4px;\n  border-radius: 2px;\n  display: inline-block;\n}<\/p>\n<p>.legend-swatch.dot {\n  height: 10px;\n  width: 10px;\n  border-radius: 50%;\n}<\/p>\n<p>\/* Cen\u00e1rio colors for line charts *\/\n.scenario-1-color { stroke: #4B384C; }    \/* primary *\/\n.scenario-2-color { stroke: #7B5E7C; }    \/* lighter purple *\/\n.scenario-3-color { stroke: #AF95A6; }    \/* secondary *\/\n.scenario-4-color { stroke: #C9A8B6; }    \/* lighter mauve *\/<\/p>\n<p>.scenario-1-fill { fill: #4B384C; }\n.scenario-2-fill { fill: #7B5E7C; }\n.scenario-3-fill { fill: #AF95A6; }\n.scenario-4-fill { fill: #C9A8B6; }<\/p>\n<p>.tooltip {\n  position: absolute;\n  background: var(--primary);\n  color: #fff;\n  padding: 8px 12px;\n  border-radius: 4px;\n  font-size: 11px;\n  font-weight: bold;\n  pointer-events: none;\n  white-space: nowrap;\n  box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n  opacity: 0;\n  transition: opacity 0.15s;\n  z-index: 10;\n  transform: translate(-50%, -100%);\n}<\/p>\n<p>.tooltip.visible { opacity: 1; }\n.tooltip::after {\n  content: '';\n  position: absolute;\n  top: 100%;\n  left: 50%;\n  transform: translateX(-50%);\n  border: 5px solid transparent;\n  border-top-color: var(--primary);\n}<\/p>\n<p>.chart-svg-wrap circle.hover-target {\n  fill: transparent;\n  cursor: pointer;\n}<\/p>\n<p>.chart-summary-stats {\n  display: grid;\n  grid-template-columns: repeat(4, 1fr);\n  gap: 8px;\n  margin-top: 12px;\n  padding-top: 10px;\n  border-top: 1px solid var(--border);\n}<\/p>\n<p>.cs-stat {\n  text-align: center;\n  padding: 6px 4px;\n}<\/p>\n<p>.cs-stat-label {\n  font-size: 9px;\n  color: var(--text-soft);\n  text-transform: uppercase;\n  letter-spacing: 0.3px;\n  font-weight: bold;\n  margin-bottom: 2px;\n  line-height: 1.2;\n}<\/p>\n<p>.cs-stat-value {\n  font-size: 12px;\n  font-weight: bold;\n  color: var(--primary);\n}<\/p>\n<p>.cs-stat-value.negative { color: var(--negative); }<\/p>\n<p>.toggle-button {\n  background: var(--secondary-light);\n  color: var(--primary);\n  border: 1px solid var(--secondary);\n  padding: 6px 12px;\n  border-radius: 3px;\n  font-family: inherit;\n  font-size: 12px;\n  cursor: pointer;\n  font-weight: bold;\n}<\/p>\n<p>.toggle-button:hover { background: var(--secondary); color: #fff; }<\/p>\n<p>.amort-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }<\/p>\n<p>.amort-row {\n  display: grid;\n  grid-template-columns: 100px 1fr 40px;\n  gap: 10px;\n  align-items: end;\n}<\/p>\n<p>.amort-row .field { margin: 0; }<\/p>\n<p>.remove-btn {\n  background: var(--negative);\n  color: #fff;\n  border: none;\n  border-radius: 3px;\n  width: 32px;\n  height: 32px;\n  font-size: 16px;\n  font-weight: bold;\n  cursor: pointer;\n  align-self: end;\n}<\/p>\n<p>.remove-btn:hover { background: #6B0709; }<\/p>\n<p>.add-btn {\n  background: var(--primary);\n  color: #fff;\n  border: none;\n  padding: 8px 14px;\n  border-radius: 3px;\n  font-family: inherit;\n  font-size: 12px;\n  cursor: pointer;\n  font-weight: bold;\n  margin-top: 6px;\n}<\/p>\n<p>.add-btn:hover { background: var(--primary-light); }<\/p>\n<p>.empty-msg {\n  font-size: 12px;\n  color: var(--text-soft);\n  font-style: italic;\n  padding: 10px;\n}<\/p>\n<p>footer {\n  text-align: center;\n  color: var(--text-soft);\n  font-size: 11px;\n  padding: 20px;\n  margin-top: 24px;\n}<\/p>\n<p>\/* Bottom tab navigation: sempre vis\u00edvel, adapta-se entre mobile e desktop *\/\n.bottom-nav {\n  display: block;\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  background: #fff;\n  border-top: 1px solid var(--border);\n  box-shadow: 0 -2px 10px rgba(75, 56, 76, 0.08);\n  z-index: 100;\n  padding-bottom: env(safe-area-inset-bottom, 0);\n}<\/p>\n<p>\/* Desktop: barra compacta flutuante no canto inferior direito, s\u00f3 com Excel\/PDF *\/\n@media (min-width: 761px) {\n  .bottom-nav {\n    left: auto;\n    right: 16px;\n    bottom: 16px;\n    border-radius: 10px;\n    border: 1px solid var(--border);\n    box-shadow: 0 4px 16px rgba(75, 56, 76, 0.12);\n    padding-bottom: 0;\n  }\n  .bottom-nav-inner { grid-template-columns: repeat(2, auto); }\n  .bottom-nav .tab-btn:not(.tab-action) { display: none; }\n  .bottom-nav .tab-btn.tab-action {\n    flex-direction: row;\n    padding: 10px 16px;\n    font-size: 12px;\n    border-top: 0;\n    border-radius: 10px;\n  }\n  .bottom-nav .tab-btn.tab-action .tab-icon { width: 18px; height: 18px; }\n  .bottom-nav .tab-btn.tab-action:first-of-type::before { display: none; }\n}<\/p>\n<p>.bottom-nav-inner {\n  display: grid;\n  grid-template-columns: repeat(6, 1fr);\n}<\/p>\n<p>\/* Ac\u00e7\u00f5es (Excel, PDF) na barra inferior: separador visual subtil das tabs *\/\n.tab-btn.tab-action {\n  position: relative;\n  color: var(--secondary);\n}\n.tab-btn.tab-action:first-of-type::before {\n  content: \"\";\n  position: absolute;\n  left: 0;\n  top: 18%;\n  bottom: 18%;\n  width: 1px;\n  background: var(--border);\n}\n.tab-btn.tab-action.active { border-top-color: transparent; color: var(--primary); }<\/p>\n<p>.tab-btn {\n  background: transparent;\n  border: none;\n  padding: 10px 6px 8px;\n  font-family: inherit;\n  font-size: 10.5px;\n  color: var(--text-soft);\n  cursor: pointer;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 4px;\n  font-weight: bold;\n  border-top: 3px solid transparent;\n  transition: all 0.15s;\n  letter-spacing: 0.2px;\n}<\/p>\n<p>.tab-btn:active { background: var(--secondary-light); }<\/p>\n<p>.tab-btn.active {\n  color: var(--primary);\n  border-top-color: var(--primary);\n}<\/p>\n<p>.tab-icon {\n  width: 22px;\n  height: 22px;\n  display: block;\n}<\/p>\n<p>.tab-icon svg { width: 100%; height: 100%; }<\/p>\n<p>\/* Floating scroll-to-top button *\/\n.scroll-top {\n  display: none;\n  position: fixed;\n  right: 16px;\n  bottom: 80px;\n  width: 44px;\n  height: 44px;\n  border-radius: 50%;\n  background: var(--primary);\n  color: #fff;\n  border: none;\n  cursor: pointer;\n  font-size: 18px;\n  box-shadow: 0 4px 12px rgba(75, 56, 76, 0.3);\n  z-index: 99;\n  font-weight: bold;\n}<\/p>\n<p>.scroll-top.visible { display: block; }<\/p>\n<p>\/* Install banner *\/\n.install-banner {\n  display: none;\n  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);\n  color: #fff;\n  padding: 12px 16px;\n  border-radius: 6px;\n  margin-bottom: 16px;\n  align-items: center;\n  gap: 12px;\n  font-size: 13px;\n  box-shadow: var(--shadow);\n}<\/p>\n<p>.install-banner.show { display: flex; }<\/p>\n<p>.install-banner-text { flex: 1; }<\/p>\n<p>.install-banner-text strong { display: block; margin-bottom: 2px; font-size: 14px; }<\/p>\n<p>.install-banner button {\n  background: #fff;\n  color: var(--primary);\n  border: none;\n  padding: 8px 14px;\n  border-radius: 4px;\n  font-family: inherit;\n  font-weight: bold;\n  cursor: pointer;\n  font-size: 13px;\n  white-space: nowrap;\n}<\/p>\n<p>.install-banner-close {\n  background: transparent !important;\n  color: rgba(255,255,255,0.8) !important;\n  font-size: 20px !important;\n  padding: 0 !important;\n  width: 28px;\n  height: 28px;\n  line-height: 1;\n}<\/p>\n<p>\/* Print stylesheet *\/\n@media print {\n  @page {\n    size: A4;\n    margin: 1.4cm 1.2cm;\n  }<\/p>\n<p>  body {\n    background: #fff;\n    font-size: 10pt;\n  }<\/p>\n<p>  .container { max-width: 100%; padding: 0; }<\/p>\n<p>  .no-print, .actions, .toggle-button { display: none !important; }<\/p>\n<p>  header.app-header {\n    background: var(--primary) !important;\n    color: #fff !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n    padding: 12px 16px;\n    border-radius: 0;\n    margin-bottom: 16px;\n  }<\/p>\n<p>  .app-header .title h1 { font-size: 14pt; }\n  .app-header .title p { font-size: 9pt; }<\/p>\n<p>  .section {\n    box-shadow: none;\n    border: 1px solid var(--border);\n    margin-bottom: 12px;\n    page-break-inside: avoid;\n    break-inside: avoid;\n  }<\/p>\n<p>  .section.allow-break {\n    page-break-inside: auto;\n    break-inside: auto;\n  }<\/p>\n<p>  .section-header {\n    background: var(--primary) !important;\n    color: #fff !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n    padding: 6px 12px;\n    font-size: 10pt;\n  }<\/p>\n<p>  .section-body { padding: 10px 12px; }<\/p>\n<p>  .page-break-before { page-break-before: always; }<\/p>\n<p>  table { font-size: 8.5pt; }\n  table th, table td { padding: 4px 6px; }\n  table th {\n    background: var(--secondary-light) !important;\n    color: var(--primary) !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n  }\n  table tr.total-row td {\n    background: #FFF4D9 !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n  }\n  table tr.subtotal-row td {\n    background: #F5EFE0 !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n  }<\/p>\n<p>  .scrollable { max-height: none; overflow: visible; border: none; }<\/p>\n<p>  .kpi {\n    background: #fff !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n  }\n  .kpi-value { font-size: 12pt; }<\/p>\n<p>  .field input {\n    background: transparent !important;\n    border: none;\n    border-bottom: 1px solid var(--border);\n    color: var(--text) !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n  }<\/p>\n<p>  .scenario-headers th {\n    background: var(--primary) !important;\n    color: #fff !important;\n    -webkit-print-color-adjust: exact;\n    print-color-adjust: exact;\n  }\n}<\/p>\n<p>@media (max-width: 900px) {\n  .grid-3, .grid-4, .grid-5 { grid-template-columns: repeat(2, 1fr); }\n  .kpi-grid { grid-template-columns: repeat(2, 1fr); }\n}<\/p>\n<p>\/* Tablet and small screens: only the detailed 50-year table scrolls horizontally *\/\n@media (max-width: 760px) {\n  .scrollable {\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n  }\n}<\/p>\n<p>\/* Phone layout *\/\n@media (max-width: 600px) {\n  body {\n    font-size: 13px;\n    padding-bottom: 64px;  \/* espa\u00e7o para a barra inferior *\/\n  }<\/p>\n<p>  .container { padding: 10px; }<\/p>\n<p>  header.app-header {\n    flex-direction: column;\n    align-items: stretch;\n    padding: 16px 16px;\n    gap: 12px;\n    margin-bottom: 16px;\n    border-radius: 0 0 8px 8px;\n  }\n  .app-header .title h1 { font-size: 17px; line-height: 1.3; }\n  .app-header .title p { font-size: 11px; line-height: 1.4; }<\/p>\n<p>  .actions {\n    width: 100%;\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    gap: 8px;\n  }\n  .btn {\n    width: 100%;\n    padding: 12px 6px;\n    font-size: 12px;\n    text-align: center;\n    letter-spacing: 0.1px;\n  }<\/p>\n<p>  .grid-2, .grid-3, .grid-4, .grid-5 {\n    grid-template-columns: 1fr;\n    gap: 10px;\n  }<\/p>\n<p>  .kpi-grid {\n    grid-template-columns: 1fr;\n    gap: 10px;\n  }<\/p>\n<p>  .section { margin-bottom: 14px; }\n  .section-header {\n    font-size: 11px;\n    padding: 10px 12px;\n    letter-spacing: 0.3px;\n  }\n  .section-body { padding: 14px 12px; }<\/p>\n<p>  .subsection-title {\n    font-size: 12px;\n    margin: 16px 0 10px 0;\n  }\n  .subsection-title:first-child { margin-top: 0; }<\/p>\n<p>  .field label { font-size: 11px; }\n  .field input {\n    font-size: 16px;  \/* impede zoom autom\u00e1tico no iOS ao focar input *\/\n    padding: 10px 12px;\n  }<\/p>\n<p>  \/* Tabelas em modo cart\u00e3o: cada linha vira um cart\u00e3o com a coluna como label *\/\n  table { font-size: 12px; }<\/p>\n<p>  \/* Tabelas com cen\u00e1rios (4 colunas de cen\u00e1rios + 1 label):\n     transformam-se em cart\u00f5es com grelha 2x2 dos cen\u00e1rios *\/\n  table.scenario-table,\n  table.scenario-table thead,\n  table.scenario-table tbody,\n  table.scenario-table tr,\n  table.scenario-table th,\n  table.scenario-table td {\n    display: block;\n    width: 100%;\n    padding: 0;\n    margin: 0;\n    border: none;\n    text-align: left;\n  }<\/p>\n<p>  table.scenario-table thead { display: none; }<\/p>\n<p>  table.scenario-table tr {\n    background: #fff;\n    border: 1px solid var(--border);\n    border-radius: 5px;\n    margin-bottom: 10px;\n    padding: 10px 12px;\n    box-shadow: var(--shadow);\n  }<\/p>\n<p>  table.scenario-table tr.total-row {\n    background: #FFF4D9;\n    border-color: var(--secondary);\n    border-width: 2px;\n  }<\/p>\n<p>  table.scenario-table tr.subtotal-row { background: #F5EFE0; }<\/p>\n<p>  table.scenario-table td:first-child {\n    font-weight: bold;\n    font-size: 12px;\n    color: var(--primary);\n    text-transform: uppercase;\n    letter-spacing: 0.3px;\n    padding-bottom: 8px;\n    margin-bottom: 8px;\n    border-bottom: 1px solid var(--border);\n  }<\/p>\n<p>  \/* Cells dos cen\u00e1rios: grelha 2x2 com label + valor empilhados *\/\n  table.scenario-table td:not(:first-child) {\n    display: inline-block;\n    width: 49%;\n    vertical-align: top;\n    padding: 6px 4px;\n    font-size: 13px;\n    font-weight: bold;\n    color: var(--text);\n    box-sizing: border-box;\n  }<\/p>\n<p>  table.scenario-table td:not(:first-child)::before {\n    content: attr(data-label);\n    display: block;\n    font-size: 9.5px;\n    color: var(--text-soft);\n    font-weight: bold;\n    text-transform: uppercase;\n    letter-spacing: 0.3px;\n    margin-bottom: 2px;\n  }<\/p>\n<p>  \/* Tabela do servi\u00e7o da d\u00edvida (label + valor \u00fanico) *\/\n  table.kv-table,\n  table.kv-table tbody,\n  table.kv-table tr,\n  table.kv-table td {\n    display: block;\n    width: 100%;\n    padding: 0;\n    margin: 0;\n    border: none;\n  }<\/p>\n<p>  table.kv-table tr {\n    background: #fff;\n    border-bottom: 1px solid var(--border);\n    padding: 8px 0;\n    display: grid;\n    grid-template-columns: 1fr auto;\n    gap: 8px;\n    align-items: baseline;\n  }<\/p>\n<p>  table.kv-table tr:last-child { border-bottom: none; }<\/p>\n<p>  table.kv-table td:first-child {\n    font-size: 11.5px;\n    color: var(--text-soft);\n    text-align: left;\n    padding-right: 8px;\n  }<\/p>\n<p>  table.kv-table td:not(:first-child) {\n    font-size: 13px;\n    font-weight: bold;\n    color: var(--primary);\n    text-align: right;\n    white-space: nowrap;\n  }<\/p>\n<p>  \/* Tabela das amortiza\u00e7\u00f5es registadas *\/\n  table.amort-table,\n  table.amort-table thead,\n  table.amort-table tbody,\n  table.amort-table tr,\n  table.amort-table th,\n  table.amort-table td {\n    display: block; width: 100%; padding: 0; margin: 0;\n    border: none; text-align: left;\n  }\n  table.amort-table thead { display: none; }\n  table.amort-table tr {\n    background: #fff;\n    border: 1px solid var(--border);\n    border-radius: 5px;\n    margin-bottom: 8px;\n    padding: 10px 12px;\n    position: relative;\n  }\n  table.amort-table td:first-child {\n    \/* \"#\" badge no canto superior direito *\/\n    position: absolute;\n    top: 8px; right: 12px;\n    width: auto;\n    background: var(--secondary);\n    color: #fff;\n    border-radius: 50%;\n    font-size: 11px;\n    width: 22px; height: 22px;\n    text-align: center;\n    line-height: 22px;\n    font-weight: bold;\n  }\n  table.amort-table td:first-child::before { display: none; }\n  table.amort-table td:not(:first-child) {\n    display: inline-block; width: 49%;\n    padding: 4px 0;\n    font-size: 13px;\n    font-weight: bold;\n    box-sizing: border-box;\n  }\n  table.amort-table td:not(:first-child)::before {\n    content: attr(data-label);\n    display: block;\n    font-size: 9.5px;\n    color: var(--text-soft);\n    font-weight: bold;\n    text-transform: uppercase;\n    margin-bottom: 2px;\n  }<\/p>\n<p>  \/* Resumo de tesouraria: cabe\u00e7alhos especiais (CEN\u00c1RIO X com Receita\/Fluxo) *\/\n  table.cashflow-table,\n  table.cashflow-table thead,\n  table.cashflow-table tbody,\n  table.cashflow-table tr,\n  table.cashflow-table th,\n  table.cashflow-table td {\n    display: block; width: 100%; padding: 0; margin: 0;\n    border: none; text-align: left;\n  }\n  table.cashflow-table thead { display: none; }\n  table.cashflow-table tr {\n    background: #fff;\n    border: 1px solid var(--border);\n    border-radius: 5px;\n    margin-bottom: 10px;\n    padding: 10px 12px;\n  }\n  table.cashflow-table tr.total-row {\n    background: #FFF4D9;\n    border-color: var(--secondary);\n    border-width: 2px;\n  }\n  table.cashflow-table tr.subtotal-row { background: #F5EFE0; }\n  table.cashflow-table td:first-child {\n    font-weight: bold;\n    font-size: 12px;\n    color: var(--primary);\n    text-transform: uppercase;\n    letter-spacing: 0.3px;\n    padding-bottom: 8px;\n    margin-bottom: 8px;\n    border-bottom: 1px solid var(--border);\n  }\n  table.cashflow-table td:not(:first-child) {\n    display: inline-block; width: 49%;\n    padding: 6px 0;\n    font-size: 12px;\n    font-weight: bold;\n    box-sizing: border-box;\n    line-height: 1.2;\n  }\n  table.cashflow-table td:nth-child(odd):not(:first-child) {\n    border-left: 2px solid var(--secondary);\n    padding-left: 6px;\n  }\n  table.cashflow-table td:not(:first-child)::before {\n    content: attr(data-label);\n    display: block;\n    font-size: 9px;\n    color: var(--text-soft);\n    font-weight: bold;\n    text-transform: uppercase;\n    margin-bottom: 2px;\n    letter-spacing: 0.2px;\n  }<\/p>\n<p>  \/* Tabela de detalhe (50 anos): mant\u00e9m scroll horizontal por ser muito densa *\/\n  .scrollable { max-height: 380px; overflow-x: auto; -webkit-overflow-scrolling: touch; }\n  .scrollable table { min-width: 1400px; font-size: 10.5px; }\n  .scrollable table th { font-size: 9.5px; padding: 5px 6px; }\n  .scrollable table td { padding: 5px 6px; }<\/p>\n<p>  .amort-row {\n    grid-template-columns: minmax(70px, 1fr) 2fr 38px;\n    gap: 8px;\n  }<\/p>\n<p>  .add-btn {\n    width: 100%;\n    padding: 12px;\n    font-size: 13px;\n  }\n  .remove-btn { width: 38px; height: 38px; }<\/p>\n<p>  .kpi { padding: 12px 14px; border-left-width: 3px; }\n  .kpi-value { font-size: 16px; }\n  .kpi-label { font-size: 10px; }\n  .kpi-sub { font-size: 10px; }<\/p>\n<p>  .scrollable { max-height: 380px; }<\/p>\n<p>  .chart-grid { grid-template-columns: 1fr; gap: 14px; }\n  .chart-card { padding: 14px 12px 12px; }\n  .chart-summary-stats { grid-template-columns: repeat(2, 1fr); gap: 6px; }\n  .cs-stat-value { font-size: 11px; }\n  .chart-legend { font-size: 10px; gap: 10px; }<\/p>\n<p>  .toggle-button { width: 100%; margin-top: 6px; padding: 8px 12px; }\n  .toggle-button[style*=\"float: right\"] { float: none !important; display: block; }<\/p>\n<p>  .note { font-size: 11px; padding: 8px 10px; }<\/p>\n<p>  footer { padding: 16px 8px 8px; font-size: 10px; }<\/p>\n<p>  \/* Em mobile, as ac\u00e7\u00f5es Excel\/PDF est\u00e3o na barra inferior, n\u00e3o no cabe\u00e7alho *\/\n  .actions-card { display: none; }<\/p>\n<p>  \/* Cabe\u00e7alho fica colado no topo *\/\n  header.app-header {\n    position: sticky;\n    top: 0;\n    z-index: 50;\n    margin-top: -10px;\n    padding-top: calc(16px + env(safe-area-inset-top, 0));\n  }<\/p>\n<p>  \/* Filtragem de sec\u00e7\u00f5es por separador ativo *\/\n  body[data-tab=\"config\"] section[data-tab]:not([data-tab~=\"config\"]) { display: none; }\n  body[data-tab=\"resumo\"] section[data-tab]:not([data-tab~=\"resumo\"]) { display: none; }\n  body[data-tab=\"anual\"] section[data-tab]:not([data-tab~=\"anual\"]) { display: none; }\n  body[data-tab=\"detalhe\"] section[data-tab]:not([data-tab~=\"detalhe\"]) { display: none; }\n  body[data-tab=\"config\"] footer,\n  body[data-tab=\"anual\"] footer,\n  body[data-tab=\"detalhe\"] footer { display: none; }<\/p>\n<p>  \/* Aplica espa\u00e7amento ao primeiro section vis\u00edvel *\/\n  body[data-tab] section { animation: fadein 0.18s ease-out; }\n  @keyframes fadein {\n    from { opacity: 0; transform: translateY(4px); }\n    to { opacity: 1; transform: translateY(0); }\n  }\n}<\/p>\n<p>\/* Very small phones *\/\n@media (max-width: 360px) {\n  .actions { grid-template-columns: 1fr; }\n  .app-header .title h1 { font-size: 15px; }\n  .tab-btn { font-size: 9px; padding: 7px 1px 5px; gap: 2px; }\n  .tab-icon { width: 17px; height: 17px; }\n}<\/p>\n<p>\/* Phones below 480px: tighter padding for 6-column nav *\/\n@media (max-width: 480px) {\n  .tab-btn { padding: 8px 2px 6px; font-size: 9.5px; gap: 3px; }\n  .tab-icon { width: 19px; height: 19px; }\n}<\/p>\n<p>\/* ============================================================\n   SFAI Portugal: marca discreta + disclaimer\n   ============================================================ *\/\n.brand-mark {\n  display: inline-flex;\n  align-items: center;\n  height: 38px;\n  margin-left: auto;\n  flex-shrink: 0;\n}\n.brand-mark img {\n  height: 100%;\n  width: auto;\n  opacity: 0.95;\n  filter: brightness(0) invert(1);  \/* logo branco sobre o gradiente do header *\/\n}\nheader.app-header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 16px;\n}\nheader.app-header .title { flex: 1; min-width: 0; }<\/p>\n<p>\/* Marca subtil no in\u00edcio do t\u00edtulo de cada sec\u00e7\u00e3o *\/\n.section-header {\n  display: flex;\n  align-items: center;\n  gap: 10px;\n}\n.section-header::before {\n  content: '';\n  display: inline-block;\n  width: 18px;\n  height: 18px;\n  background: url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABCGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGA8wQAELAYMDLl5JUVB7k4KEZFRCuwPGBiBEAwSk4sLGHADoKpv1yBqL+viUYcLcKakFicD6Q9ArFIEtBxopAiQLZIOYWuA2EkQtg2IXV5SUAJkB4DYRSFBzkB2CpCtkY7ETkJiJxcUgdT3ANk2uTmlyQh3M\/Ck5oUGA2kOIJZhKGYIYnBncAL5H6IkfxEDg8VXBgbmCQixpJkMDNtbGRgkbiHEVBYwMPC3MDBsO48QQ4RJQWJRIliIBYiZ0tIYGD4tZ2DgjWRgEL7AwMAVDQsIHG5TALvNnSEfCNMZchhSgSKeDHkMyQx6QJYRgwGDIYMZAKbWPz9HbOBQAAAAflBMVEVuVnRJM1OGbYl8Y4E6HUf08\/WjmqaYh5udkZ+sp61tYXNtbW0vEzuGeIp8coLZ0tuGeouibqLEu8dvZnbQttJ9dYL\/AP8AAGp\/P3+zruI\/Pz8A\/wBVVaq9ssC9s8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5NibAAAAAIHRSTlP8\/vz9\/hJf6J0cogP9pZxQYQ5YZxpmAQIEDwQBA1qBAJbOFXYAAAPeSURBVHjajZfZdusgDEUFWBhInanznfj\/v7ySMDaTk\/LQtRr7bI6EkQBiO9DLX\/tuAJSaJgWgrU\/PPHavQ\/vDh6iDaF9IrmC5noQrlNsTAMkRzyBaVofrNzLQafJDTugf\/whwYrkSMXtffNzVMsyVfzkAIJLewcvLKrcy96y1hnJcsDJROcB7lk\/KsVurteFREejJbQTwiNfsfoI7GXKG5jZG13pKxaUgbIC3iPOUByBGmh1keoqhRdg9EZDjx8tm\/4X0uLAoAx4QYJvfZPuTwpj0CaFlNAQXT1gASAG7\/hXjGTb9CtCdB78DeMYtfjZgoQe0Ydj4ZwOc0E3ZgKIMRj0CtB5Q0gDy\/d3VHoAKGGEIaAga8QMZ8HHCXZ8cHAAagqPFYsBXDEUCHoTQETyZB95AZQAEsOMk9oSAAsBQBMAjxOjMAcA0K3EiG5ftE1Bp0IajXTkG6CaPEU5xqednAq2Q02NATfAIlIFavXqgzWTSXmgIVRAL5cAOAAqumMJo528sGHIQBnqVytndUS15TLCQIlDdkMoTqR5q8wiwgB8CtuIliAcEA7goaR8DPT12b1TrOkRVJjHeA7eRkZ5z6GhBsLVRAmyq3meAgV5Wwckbbj4AyDd9yxBo9LKM8+ykw7h53gn7K2nOkN55D52cATTYBveZFbITCt\/h1fM7S7GZkz7N6laGE0iKwhSRi\/UgLdjbmR\/l+bNrrZeNsX7UHYB3mFsZutavj8\/CiPhXICMAURNDAu5CZpu0UWJ2MiJI7BLLJSfeNDnfjOAQIJHoeevvptavL4frG71wCJCmRnWXev4MvT5DLmDUAcHIVnaysvNQz8PA66GF7Uvmj\/BAP3FFOLCwlzNxsQzkosAjQFlReVfbISEABvUgC1s1otpyHxAogHgYQ13TaUF8TwAu6z8DaO2x9xAi\/IrheQ7SrsB4bQGWO5OHnwGIgO2XhJ\/AK3TwHXRdycZzk0JqrnG3AM8ALr7WGcATsoPo4GcA3SxZOmDwCQOGH3IHmGsHS\/zOhywG19phEm25YlM6pqVjHhPgqYNqFUh\/u20n1VsMvYEO4Pf14pNUqmvrUfcTdVMKOsB8K1I4SeMtT+uxJLDOgGn2Qnzf9eo9X1vyfcHTdQHqhlKeb7g+usH8xYUDc9kxxQk\/n09IXmx71vv+ysOHieqGsHYjaUdYVb6DSxcdOy9zCaCuYmbuD5VcLYjFzQ+aa6PLem5KTroLtf3dfboNPrr6okvqJI4+QJF73n\/0yu9jAJvDfzb3vgXqcsVN0T9xkEnVoSd1IcTBiz2AiicNuv6HsLXSEOwbP\/vqGf8BMLp7KXzS2+EAAAAASUVORK5CYII=) no-repeat center \/ contain;\n  opacity: 0.55;\n  filter: brightness(0) invert(1);  \/* fica branco sobre o roxo *\/\n  flex-shrink: 0;\n}<\/p>\n<p>\/* Footer com logo discreto *\/\nfooter.app-footer {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 8px;\n  padding: 18px 16px 24px;\n  font-size: 11px;\n  color: var(--text-soft);\n}\nfooter.app-footer .footer-link { color: var(--primary); text-decoration: none; font-weight: bold; }\nfooter.app-footer .footer-link:hover { text-decoration: underline; }\nfooter.app-footer img {\n  height: 28px;\n  width: auto;\n  opacity: 0.65;\n}<\/p>\n<p>\/* Sec\u00e7\u00e3o de disclaimer *\/\n.disclaimer-section .section-header {\n  background: var(--secondary-light);\n  color: var(--primary);\n}\n.disclaimer-section .section-header::before {\n  filter: none;\n  opacity: 0.85;\n}\n.disclaimer-body {\n  display: flex;\n  gap: 20px;\n  align-items: flex-start;\n}\n.disclaimer-logo {\n  width: 140px;\n  height: auto;\n  flex-shrink: 0;\n}\n.disclaimer-text {\n  flex: 1;\n  font-size: 12.5px;\n  line-height: 1.55;\n  color: var(--text);\n}\n.disclaimer-text p { margin: 0 0 10px 0; }\n.disclaimer-text p:last-child { margin-bottom: 0; }<\/p>\n<p>.disclaimer-contact {\n  margin: 12px 0;\n  padding: 10px 12px;\n  background: var(--input-bg);\n  border-left: 3px solid var(--secondary);\n  border-radius: 3px;\n  font-size: 12.5px;\n}\n.disclaimer-contact-row {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 6px 10px;\n  align-items: center;\n  line-height: 1.6;\n}\n.disclaimer-contact a {\n  color: var(--primary);\n  text-decoration: none;\n  font-weight: bold;\n}\n.disclaimer-contact a:hover { text-decoration: underline; }\n.disclaimer-sep {\n  color: var(--text-soft);\n  font-weight: normal;\n}<\/p>\n<p>.disclaimer-confidential {\n  font-style: italic;\n  color: var(--text-soft);\n  border-top: 1px solid var(--border);\n  padding-top: 8px;\n  margin-top: 12px !important;\n}\n@media (max-width: 600px) {\n  .disclaimer-body { flex-direction: column; align-items: center; text-align: left; }\n  .disclaimer-logo { width: 120px; }\n  .brand-mark { height: 26px; }\n}\n@media print {\n  .brand-mark img { opacity: 1; }\n  .disclaimer-section { page-break-before: always; }\n  footer.app-footer .footer-link { color: var(--primary); text-decoration: none; font-weight: bold; }\nfooter.app-footer .footer-link:hover { text-decoration: underline; }\nfooter.app-footer img { opacity: 0.9; }\n}<\/p>\n<p>\/* ============================================================\n   Painel: Fluxo Financeiro L\u00edquido\n   ============================================================ *\/\n.flux-hero {\n  display: grid;\n  grid-template-columns: repeat(4, minmax(0, 1fr));\n  gap: 12px;\n  margin-bottom: 24px;\n}\n.flux-hero-card {\n  border: 1px solid var(--border);\n  border-radius: 6px;\n  padding: 14px 16px;\n  background: var(--surface);\n  border-left: 4px solid var(--scen-color, var(--primary));\n}\n.flux-hero-card[data-scen=\"0\"] { --scen-color: #4B384C; }\n.flux-hero-card[data-scen=\"1\"] { --scen-color: #7B5E7C; }\n.flux-hero-card[data-scen=\"2\"] { --scen-color: #AF95A6; }\n.flux-hero-card[data-scen=\"3\"] { --scen-color: #C9A8B6; }<\/p>\n<p>.flux-scen-name {\n  font-size: 12px;\n  font-weight: bold;\n  color: var(--primary);\n  text-transform: uppercase;\n  letter-spacing: 0.4px;\n}\n.flux-scen-disc { font-size: 11px; color: var(--text-soft); margin-bottom: 6px; }\n.flux-total {\n  font-size: 20px;\n  font-weight: bold;\n  margin: 4px 0;\n  letter-spacing: 0.2px;\n}\n.flux-positive { color: #2e7d32; }\n.flux-negative { color: #c62828; }\n.flux-total-label { font-size: 11px; color: var(--text-soft); margin-bottom: 10px; }\n.flux-stats {\n  font-size: 12px;\n  border-top: 1px solid var(--border);\n  padding-top: 8px;\n  display: grid;\n  gap: 4px;\n}\n.flux-stat-label { color: var(--text-soft); }\n.flux-be-ok { color: #2e7d32; font-weight: bold; }\n.flux-be-no { color: #c62828; font-weight: bold; }<\/p>\n<p>.flux-period h3 {\n  font-size: 13px;\n  margin: 0 0 10px 0;\n  color: var(--primary);\n  text-transform: uppercase;\n  letter-spacing: 0.3px;\n  font-weight: bold;\n}\n.flux-period table { width: 100%; border-collapse: collapse; }\n.flux-period th, .flux-period td {\n  padding: 9px 12px;\n  text-align: right;\n  border-bottom: 1px solid var(--border);\n  font-size: 13px;\n}\n.flux-period th:first-child, .flux-period td:first-child {\n  text-align: left;\n  font-weight: bold;\n}\n.flux-period thead th { background: var(--input-bg); font-size: 12px; text-transform: uppercase; letter-spacing: 0.3px; }<\/p>\n<p>.flux-period .flux-single-year td:first-child {\n  padding-left: 18px;\n  font-weight: normal;\n  color: var(--text-soft);\n}\n.flux-period tr:nth-child(5) td { border-bottom: 2px solid var(--secondary-light); }<\/p>\n<p>.flux-total-row td {\n  border-top: 2px solid var(--primary);\n  background: var(--input-bg);\n  font-size: 14px;\n}<\/p>\n<p>@media (max-width: 900px) {\n  .flux-hero { grid-template-columns: repeat(2, 1fr); }\n}\n@media (max-width: 480px) {\n  .flux-hero { grid-template-columns: 1fr; gap: 10px; }\n  .flux-hero-card { padding: 12px 14px; }\n  .flux-total { font-size: 18px; }\n  .flux-period th, .flux-period td { padding: 7px 8px; font-size: 12px; }\n}<\/p>\n<\/style>\n<p><\/head><br \/>\n<body><\/p>\n<div class=\"container\">\n<p>  <!-- Banner de instala\u00e7\u00e3o PWA (mostra quando o browser oferece) --><\/p>\n<div class=\"install-banner\" id=\"installBanner\">\n<div class=\"install-banner-text\">\n      <strong>Instale a aplica\u00e7\u00e3o<\/strong><br \/>\n      Funciona offline e tem \u00edcone pr\u00f3prio no telem\u00f3vel\n    <\/div>\n<p>    <button onclick=\"installPWA()\">Instalar<\/button><br \/>\n    <button class=\"install-banner-close\" onclick=\"dismissInstall()\">\u00d7<\/button>\n  <\/div>\n<header class=\"app-header\">\n<div class=\"title\">\n<h1>Simulador de Investimento Habitacional<\/h1>\n<\/p><\/div>\n<p>    <a href=\"https:\/\/sfai.pt\/en\/\" target=\"_blank\" rel=\"noopener\" class=\"brand-mark\" aria-label=\"SFAI Portugal\"><img decoding=\"async\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABKCAMAAADJ\/ut\/AAABCGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGA8wQAELAYMDLl5JUVB7k4KEZFRCuwPGBiBEAwSk4sLGHADoKpv1yBqL+viUYcLcKakFicD6Q9ArFIEtBxopAiQLZIOYWuA2EkQtg2IXV5SUAJkB4DYRSFBzkB2CpCtkY7ETkJiJxcUgdT3ANk2uTmlyQh3M\/Ck5oUGA2kOIJZhKGYIYnBncAL5H6IkfxEDg8VXBgbmCQixpJkMDNtbGRgkbiHEVBYwMPC3MDBsO48QQ4RJQWJRIliIBYiZ0tIYGD4tZ2DgjWRgEL7AwMAVDQsIHG5TALvNnSEfCNMZchhSgSKeDHkMyQx6QJYRgwGDIYMZAKbWPz9HbOBQAAAAflBMVEUAAABHNVFoVG6vkKSGbYn29fZ8Y4F0c3WwrrA6H0emmqamnahqYW\/d1N63ebd8dIGEeohrZG9oMmjjseMvFDqGfIvGusUeHnLCqrpzc7XBs7zTxs\/5uLjGt8NEPEm7uMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYelwjAAAAIHRSTlMA\/vn5\/A\/8BQ39mGGiUgWRomUMFfxqWwbyBFqLCJCOJyJs1g4AAAvlSURBVHja1ZuJduOoEkARUgkMyGvspJdM\/v8vp6rYBZKdeWfm9OMk3bEsS1zVQi1YiO5Q+AsAdpRSDsNwOAxyNlYrflOD+D8ZjIEQRPB2QIpByvNd\/xCMF0744wdo\/GcxciAEGoN0d\/AM1kxSjhZf6T+fA38WFxDo1\/Eh4SHCMHjo+seTLG54i9KQJ3\/MnqeMQcO+aicAWXhK\/UfWBXATcJYZw5FFoCxGxphLkmmBV\/SrOaXwFO+d899L+wNE92P1AEA1oxFHxhjkgzEmophxVCJBLwavGD0algsfcJYp9H8gELgnrXobzkCzmD3FNK04cIyLOD675nJm\/+0HfuYsIDhGLQfZGyY+Hry9Gf34AAGloGBsRk0yHtIY2ArGSc7TzCArI\/GWIq6w7TVQLy+ZIsFYeveHMM1bfsxR5bR4fESQT3HLV\/4tfn1sg6A4QL5ljk+chiUpzHMEaUlmuy9hNxzamaLlqW0Q5FTBDiBP15RajCA7EtGiMI\/DAQ0AzmHmCaSjXga2PBE+mKk71YPEa2+DZMsrFajUrV0QLUp54L2O4hSnnUFaocxmy3hv6P+2n\/keSHRvj2KeP4vb7IEg8PRWGogQ9\/T4S5CpYye673dvpZUP2eZJIOgl+iDyEi+AZxTzNHAsrG9pbOQjcZwL+0CBAGQ1qkBaEt31wkqcC\/u2StiLYxh5pyVyE0SDippZzbTQLQozcCTOT3yxhLuCOZQgk8D7bIA0JFNvZcSbxYmiQ40HrZSHmaeUQaR7V+rqB658kExs8Y86PPxbcxMbQW61oReKNTgQbhOkQ1K5+XifCHLmO2hWDTDBKxUg540IPEz0I\/qtYxnYHvUxgVh8ocPTGwoOChR3QeYXXJfKE80Co0AiQJcSyaqZA5S06JmoQaBWNmgbR6DEqcIYDvOeanVIQOkGxMWJLqW4rtmU4\/unno0lkM+fH9FI9BMQBbehUiz29HbeBmmUy3WsxCUn1cson4Fo8TPozRKnC09BhKs5EATd73kHZHrBB7v1Uv0tEBGd76KT3wLYBcGrHFYgaO20ru6ATK3ngg3VYiNR3wRRaVnPXri+RQESnyJMhzXHQIlTIumBrM3kUQu+MPZhoNTluCJ9AqLT4p1B6ls0IOh6hzVGiBRgnDZB1iJxK6fCYXoKrgzsSMSJrublyCNFIWoP5F24Dgc5GzRSG4PfpyTzyqkUCyKSeAmXProAMTs+C1OeHHJViVyrWilYbGM7TBjHLYmsScyqGqHEpYp45QUoC2lBBpnCkNPZ+mXvt3ikyecgeCmTkhbEDocuCC65tHhZNIcux\/TE3DG9qUUs712JVMMlC0t5SDaSMnDsgLg+Bt+aZLwYQnlOsg6GcHmSneQw+NANkL9WAaMBrXMUXD6rBgSf2wbHwCkgydaQm3oGYpsEHtDe6wvLEzQhSkciGKdnEznmGS\/Fs9oA6WPIhGKnrkjmKuDqGK2Tq0vLMJldkCwEoMLn7+S2YE+1jKSK4hYHab9HMS1IXSNqQ2ASkZSrPNeT7KpWtu9VzaRY3FuJoBE4ucOBQkEUejRUp2tISgcMvforuvBSwTCOU1CDVEHbtZy6pTVQZ5BbdsAdEM54TvTkDl0OclroPmhSj3Eat0DksRczqYBSDK6IFCDukoem22j4jIu50koV1l6EdA3IRI2PBCM7HJJ91jj6sx6VXEoS2y3X0YoPj1Lm8gtJ9hOr7KZ8lS+XGK6bIJKn7e4Ua0OEWYeEE5OM5uFPynIpQGazVUE9sgPLKAbeq1hL5VQXqoCxHVl9eyBBBifLqqbttFYsz+FZFp98m3Fco5w2S8Fso5eqrrUTNKZUpDNsWtwbkNrAL0HLzJqjsA3vZ5llTJ4L\/3F7NW1VJPGDFmoPBMwmSK6iPQGhMrNn0b6vEzGSLnFF2+rYwRqDUPB3F0TAFcpUaxtkT7MK3doDGaIMzkHJkl0XHOU6yQVFw2dR12C\/y\/AjiURexHUbRBe23Y5H1K0XQAILZP3J9lAHJSEZR0+G8di0BoFVzgg6g7zvgZgdEBEDxybVlf1VEPXHZJapAYntjigYWGybm69eq1wW3VatosJodBq\/Gt3aBWnWQbNA6Les09v8ibP98mt4x1WpOhOSL9jIDZasMkXnJoPoNcjjCYg3dS+WX2OtWKsPnSyIVdLODRfWqPDyCCbVd3dAyqgdc1Q\/iihFhGW3AXG7IOR8SSyIsoERPzkttU1Qrht6v++KJp4TLQnbxYcqRCwcxWc+quih4cjGTq+EGXZIeI2Y5pGDRjvugIQQqqrdUoLrUh9bu7yyq1dATJbx78KTpcy9SFNoXHZ1KyrY6BOsbQz86NrQZbyiM\/dLEWFTz2ITJAeMQWMaOZGzBWNM9m0f+OJTLHJHJLxshxCFUc5y2AJZV3V00ecZyk+d9uta3W7brfDJVPTqNUP3QEI1yGe65lE2C5pxX3nRy8aZkitgP7rV+GpZL03ud1Yk9AHd1hucXgIJURasJZg\/WBcfNMhD\/zzf4PrRl4hOdaC6kVvqFppEt4do5RNzL4pBqLcAT5qYqQgteyjz4ue3AVI0Dn9WDwayyn30JSLgGyAslD7JuVMuXHmGA5XKwqyLyKt49MWD\/1hQnwrdAvORdWvp2EjRtHwFZLrjZWS3iQmdJNfVQYAWeX+Gdn+5v\/DH6arNacKwzbJUvJP+ToO7e\/JVG\/HqBeLe8Vlt6UFdfd5yN44GT+36L+69u1aL+3MQ0q52h4mFTrdd6et2FBnL9++rtCqO9mJhHMPKXg+6+ENuL4gdEBSJ6Zg6bO4sVL7Pqf\/lbWcYu7jthb3XUrDivhaJ\/RP2a2rxJb8FYmqz4lX9j9h3qmOALV8DOa9ADp32dMdgeEcfRIWPexxUPF7u6gt5jIobnlShlpBsZnO7hZSvSkTVHOafCaRwsLC\/7\/X1LbK8Oq0Rtoz9S1RhzcHBCzfTVPz0J1I9N64T+qiuV8qfROmAwn4Pdl20E09z2h1yXJ+xge5tdOUebCOM19wvKtYLeoXpAhfxaPcsrqhcd+JkOO6+haKsD5L3feB\/HEaa0G71ZUg38w4EPqdXq3ErknlLtZDDVBzNmt6vz2EscpKcjABV3fFv6gQrPExbExY4OXfGwAtXTjqBmw8IgvaD+Zy7GDmG6Fj6PXYg36B7I5h6mtWCGF2FKIdug6cHIrmrhMEvnLiRy38TCPWDMAnwh0T4fwkSUVTLBxE341wx7\/RbKVArtlauaZ2t94PGcmccbfYAeE0i3E8fcRb+mWIqTEcQRFGaiunvEVMEmrMqQVgsALT8q8Doe\/qbILdiz0Ys+c7rJjvmmfBVcdxf\/v4D706n5oPlVArdLAN5iSCNugWQSiIe+ysXAqTxurUtEd3bfVJwzD6Gt0WWwXoFr4I4WE40CQSB96TtmCE6am4r0QexLBba+8exjpOYelr+9AG2dxybZvMJGwtTjDRpOJdtNPn56v5wxbVZ\/+UAksg77\/HwIBjo8+70BkR7kNn7KHmmnjueQZ\/bBaG7mXbvSXjFjrzMww+DfH2pYq\/FF6GCo4QfcMR5OgZBIc90QgPyRSB0jBTB+TgI\/YT3EnsgFFnHPRvF+ofiMNq3Nev1HEB\/A8S32BQElaIU8Ry8luP9XhUIEl+jjSx8H7Is8GsEpaMIwtfth9RwxEWCdSn1E0bDFPwdn1UG8r0vO7GPIueDztZ4bbEMAuCVrAKxvLlEcjHNAZdvHBsM5ZTcCmeQ\/QjIjLNvf4auIR67r+pyvEVGfEu1YpGBrfx8v3s3ShKh\/MYRSFityTfJixX+mwskhbu1HBU4v7\/ZeSceEt+9ryaZccJTfDUX4O5kZRu4GC\/f\/BoIa3fa0mVZQ7gQwarlRaAjCOXkIYhRoGLw4jTEFYiEFMpN+EQBdr4slrYR+wZ8Xa623Gv4bkadw24VvCmt1tqH6pow0EY1xEK+vVgdbBDjQ3qBI7xPHwJl1bu4XnefqOZcQPNzSds7Dt5\/3r\/+9wQoGGjefvrKU7n9o0TZS9hezqmew814f8fr9+VRNEpSmlXWHpS\/Y5Qa2n\/xzSo4+nQLJaLyTmj8ub7nysXf6Y6e8\/76JF0AAAAASUVORK5CYII=\" alt=\"SFAI Portugal\"><\/a><br \/>\n  <\/header>\n<p>  <!-- INPUTS --><\/p>\n<section class=\"section no-print\" id=\"inputs-section\" data-tab=\"config\">\n<div class=\"section-header\">1. Pressupostos edit\u00e1veis<\/div>\n<div class=\"section-body\">\n<div class=\"subsection-title\">Tipologia e quantidade de fogos<\/div>\n<div class=\"grid grid-4\">\n<div class=\"field\"><label>T1 (renda \u20ac\/m\u00eas)<\/label><input type=\"number\" id=\"renda_T1\" value=\"600\" step=\"1\"><\/div>\n<div class=\"field\"><label>T2 (renda \u20ac\/m\u00eas)<\/label><input type=\"number\" id=\"renda_T2\" value=\"775\" step=\"1\"><\/div>\n<div class=\"field\"><label>T3 (renda \u20ac\/m\u00eas)<\/label><input type=\"number\" id=\"renda_T3\" value=\"925\" step=\"1\"><\/div>\n<div class=\"field\"><label>T4 (renda \u20ac\/m\u00eas)<\/label><input type=\"number\" id=\"renda_T4\" value=\"1025\" step=\"1\"><\/div>\n<div class=\"field\"><label>Quantidade T1<\/label><input type=\"number\" id=\"qty_T1\" value=\"25\" step=\"1\"><\/div>\n<div class=\"field\"><label>Quantidade T2<\/label><input type=\"number\" id=\"qty_T2\" value=\"15\" step=\"1\"><\/div>\n<div class=\"field\"><label>Quantidade T3<\/label><input type=\"number\" id=\"qty_T3\" value=\"27\" step=\"1\"><\/div>\n<div class=\"field\"><label>Quantidade T4<\/label><input type=\"number\" id=\"qty_T4\" value=\"5\" step=\"1\"><\/div>\n<\/p><\/div>\n<div class=\"subsection-title\">Investimento e financiamento<\/div>\n<div class=\"grid grid-3\">\n<div class=\"field\"><label>CAPEX Total (\u20ac)<\/label><input type=\"number\" id=\"capex\" value=\"16657076.80\" step=\"0.01\"><\/div>\n<div class=\"field\"><label>Comparticipa\u00e7\u00e3o IHRU (%)<\/label><input type=\"number\" id=\"ihru_pct\" value=\"60\" step=\"0.5\" min=\"0\" max=\"100\"><\/div>\n<div class=\"field\"><label>Prazo do empr\u00e9stimo (anos)<\/label><input type=\"number\" id=\"prazo\" value=\"50\" step=\"1\"><\/div>\n<div class=\"field\"><label>Per\u00edodo de car\u00eancia (anos)<\/label><input type=\"number\" id=\"carencia\" value=\"3\" step=\"1\"><\/div>\n<div class=\"field\"><label>Spread (%)<\/label><input type=\"number\" id=\"spread\" value=\"0.25\" step=\"0.001\"><\/div>\n<div class=\"field\"><label>Euribor 12M (%)<\/label><input type=\"number\" id=\"euribor\" value=\"2.132\" step=\"0.001\"><\/div>\n<div class=\"field\"><label>M\u00e9todo de amortiza\u00e7\u00e3o<\/label><br \/>\n          <select id=\"metodo_amort\"><option value=\"constante\">Presta\u00e7\u00e3o constante (anuidade)<\/option><option value=\"linear\">Capital constante (linear)<\/option><\/select>\n        <\/div>\n<\/p><\/div>\n<div class=\"subsection-title\">Opera\u00e7\u00e3o e indexa\u00e7\u00e3o<\/div>\n<div class=\"grid grid-3\">\n<div class=\"field\"><label>Infla\u00e7\u00e3o (%)<\/label><input type=\"number\" id=\"inflacao\" value=\"2.0\" step=\"0.1\"><\/div>\n<div class=\"field\"><label>Atualiza\u00e7\u00e3o rendas anual (%)<\/label><input type=\"number\" id=\"atualizacao\" value=\"1.02\" step=\"0.001\"><\/div>\n<div class=\"field\"><label>OPEX anual (\u20ac)<\/label><input type=\"number\" id=\"opex\" value=\"150000\" step=\"1000\"><\/div>\n<div class=\"field\"><label>Anos de garantia da obra<\/label><input type=\"number\" id=\"anos_garantia\" value=\"5\" step=\"1\"><\/div>\n<div class=\"field\"><label>Ponderador OPEX durante garantia (%)<\/label><input type=\"number\" id=\"ponderador\" value=\"25\" step=\"1\"><\/div>\n<div class=\"field\"><label>Taxa de incobr\u00e1veis (%)<\/label><input type=\"number\" id=\"incobraveis\" value=\"1.5\" step=\"0.1\"><\/div>\n<div class=\"field\"><label>Prazo de obra (meses)<\/label><input type=\"number\" id=\"prazo_obra\" value=\"18\" step=\"1\"><\/div>\n<div class=\"field\"><label>In\u00edcio cobran\u00e7a rendas (meses ap\u00f3s obra)<\/label><input type=\"number\" id=\"inicio_cobranca\" value=\"3\" step=\"1\"><\/div>\n<\/p><\/div>\n<div class=\"subsection-title\">Cen\u00e1rios de desconto sobre rendas<\/div>\n<div class=\"grid grid-4\">\n<div class=\"field\"><label>Cen\u00e1rio 1 (%)<\/label><input type=\"number\" id=\"desc_1\" value=\"0\" step=\"1\" min=\"0\" max=\"100\"><\/div>\n<div class=\"field\"><label>Cen\u00e1rio 2 (%)<\/label><input type=\"number\" id=\"desc_2\" value=\"10\" step=\"1\" min=\"0\" max=\"100\"><\/div>\n<div class=\"field\"><label>Cen\u00e1rio 3 (%)<\/label><input type=\"number\" id=\"desc_3\" value=\"20\" step=\"1\" min=\"0\" max=\"100\"><\/div>\n<div class=\"field\"><label>Cen\u00e1rio 4 (%)<\/label><input type=\"number\" id=\"desc_4\" value=\"30\" step=\"1\" min=\"0\" max=\"100\"><\/div>\n<\/p><\/div>\n<div class=\"subsection-title\">Amortiza\u00e7\u00f5es extraordin\u00e1rias do empr\u00e9stimo<\/div>\n<p style=\"font-size:12px;color:var(--text-soft);margin-bottom:10px;\">\n        Pagamentos antecipados de capital aplicados no in\u00edcio do ano indicado. Reduzem o saldo em d\u00edvida e os juros futuros, refletindo-se no fluxo l\u00edquido do ano em que ocorrem (sa\u00edda adicional de tesouraria) e nos anos seguintes (menores juros). Aplicam-se igualmente aos 4 cen\u00e1rios, dado que o financiamento \u00e9 \u00fanico. <strong>S\u00f3 se aplicam depois de iniciada a amortiza\u00e7\u00e3o<\/strong> (a partir do ano <span id=\"primeiroAnoAmortExtra\">4<\/span>, ou seja, depois de terminar tanto a fase de obra como o per\u00edodo de car\u00eancia): enquanto o capital ainda n\u00e3o est\u00e1 a ser amortizado, n\u00e3o h\u00e1 saldo em amortiza\u00e7\u00e3o sobre o qual antecipar pagamentos.\n      <\/p>\n<div class=\"amort-rows\" id=\"amortRows\"><\/div>\n<p>      <button type=\"button\" class=\"add-btn\" onclick=\"addAmortRow()\">+ Adicionar amortiza\u00e7\u00e3o extraordin\u00e1ria<\/button><\/p><\/div>\n<\/section>\n<p>    <!-- FLUXO FINANCEIRO L\u00cdQUIDO --><\/p>\n<section class=\"section flux-section\" data-tab=\"resumo\">\n<div class=\"section-header\">Fluxo Financeiro L\u00edquido (50 anos por cen\u00e1rio)<\/div>\n<div class=\"section-body\">\n<div class=\"flux-hero\" id=\"fluxHero\"><\/div>\n<div class=\"flux-period\">\n<h3>Fluxo l\u00edquido (primeiros 5 anos detalhados, depois agregados por d\u00e9cada)<\/h3>\n<table>\n<thead>\n<tr>\n<th>Per\u00edodo<\/th>\n<th>Cen\u00e1rio 1 (0%)<\/th>\n<th>Cen\u00e1rio 2 (10%)<\/th>\n<th>Cen\u00e1rio 3 (20%)<\/th>\n<th>Cen\u00e1rio 4 (30%)<\/th>\n<\/tr>\n<\/thead>\n<tbody id=\"fluxPeriodBody\"><\/tbody>\n<\/table><\/div>\n<\/p><\/div>\n<\/section>\n<p>  <!-- KPI SUMMARY --><\/p>\n<section class=\"section\" data-tab=\"resumo\">\n<div class=\"section-header\">2. Indicadores-resumo do investimento<\/div>\n<div class=\"section-body\">\n<div class=\"kpi-grid\" id=\"kpiSummary\"><\/div>\n<\/p><\/div>\n<\/section>\n<p>  <!-- AN\u00c1LISE POR CEN\u00c1RIO --><\/p>\n<section class=\"section\" data-tab=\"resumo\">\n<div class=\"section-header\">3. An\u00e1lise por cen\u00e1rio<\/div>\n<div class=\"section-body\">\n<table class=\"scenario-table\">\n<thead>\n<tr id=\"analysisHeader\"><\/tr>\n<\/thead>\n<tbody id=\"analysisBody\"><\/tbody>\n<\/table><\/div>\n<\/section>\n<p>  <!-- SERVI\u00c7O DA D\u00cdVIDA --><\/p>\n<section class=\"section\" data-tab=\"detalhe\">\n<div class=\"section-header\">4. Servi\u00e7o da d\u00edvida (id\u00eantico aos 4 cen\u00e1rios)<\/div>\n<div class=\"section-body\">\n<p style=\"font-size: 12px; color: var(--text-soft); margin-bottom: 14px;\">\n        O CAPEX e a percentagem IHRU s\u00e3o iguais nos quatro cen\u00e1rios, pelo que o financiamento da CM e o respetivo servi\u00e7o de d\u00edvida s\u00e3o id\u00eanticos. A diverg\u00eancia entre cen\u00e1rios surge apenas do lado das receitas (descontos nas rendas).\n      <\/p>\n<table class=\"kv-table\">\n<tbody id=\"debtBody\"><\/tbody>\n<\/table><\/div>\n<\/section>\n<p>  <!-- INDICADORES POR CEN\u00c1RIO --><\/p>\n<section class=\"section\" data-tab=\"resumo detalhe\">\n<div class=\"section-header\">5. Indicadores-chave por cen\u00e1rio<\/div>\n<div class=\"section-body\">\n<table class=\"scenario-table\">\n<thead>\n<tr id=\"indicatorsHeader\"><\/tr>\n<\/thead>\n<tbody id=\"indicatorsBody\"><\/tbody>\n<\/table><\/div>\n<\/section>\n<p>  <!-- AMORTIZA\u00c7\u00d5ES EXTRAORDIN\u00c1RIAS RESUMO --><\/p>\n<section class=\"section\" id=\"amortSummarySection\" data-tab=\"resumo detalhe\" style=\"display:none;\">\n<div class=\"section-header\">Amortiza\u00e7\u00f5es extraordin\u00e1rias registadas<\/div>\n<div class=\"section-body\">\n<table class=\"amort-table\">\n<thead>\n<tr>\n<th>#<\/th>\n<th>Ano<\/th>\n<th>Valor (\u20ac)<\/th>\n<th>Saldo d\u00edvida ANTES<\/th>\n<th>Saldo d\u00edvida AP\u00d3S<\/th>\n<\/tr>\n<\/thead>\n<tbody id=\"amortSummaryBody\"><\/tbody>\n<\/table>\n<div class=\"note\" id=\"amortImpactNote\"><\/div>\n<\/p><\/div>\n<\/section>\n<p>  <!-- COMPARA\u00c7\u00c3O --><\/p>\n<section class=\"section\" data-tab=\"resumo\">\n<div class=\"section-header\">6. Compara\u00e7\u00e3o entre cen\u00e1rios (refer\u00eancia: Cen\u00e1rio 1)<\/div>\n<div class=\"section-body\">\n<table class=\"scenario-table\">\n<thead>\n<tr id=\"comparisonHeader\"><\/tr>\n<\/thead>\n<tbody id=\"comparisonBody\"><\/tbody>\n<\/table><\/div>\n<\/section>\n<p>  <!-- GR\u00c1FICOS DE FLUXO FINANCEIRO --><\/p>\n<section class=\"section\" data-tab=\"resumo\">\n<div class=\"section-header\">7. Visualiza\u00e7\u00e3o do fluxo financeiro<\/div>\n<div class=\"section-body\">\n<div class=\"chart-grid\">\n<p>        <!-- Chart 1: Fluxo anual primeiros 10 anos --><\/p>\n<div class=\"chart-card\">\n<div class=\"chart-title\">Fluxo l\u00edquido anual (anos 1-10)<\/div>\n<div class=\"chart-subtitle\">Receita l\u00edquida menos encargo total. Barras abaixo do zero indicam sa\u00edda de tesouraria.<\/div>\n<div class=\"chart-svg-wrap\" id=\"chartFluxoAnual\"><\/div>\n<div class=\"chart-legend\" id=\"legendFluxoAnual\"><\/div>\n<\/p><\/div>\n<p>        <!-- Chart 2: Fluxo acumulado 50 anos --><\/p>\n<div class=\"chart-card\">\n<div class=\"chart-title\">Fluxo acumulado (50 anos)<\/div>\n<div class=\"chart-subtitle\">Posi\u00e7\u00e3o de tesouraria acumulada por cen\u00e1rio. Cruzamento com o eixo zero marca o ponto de equil\u00edbrio.<\/div>\n<div class=\"chart-svg-wrap\" id=\"chartFluxoAcumulado\"><\/div>\n<div class=\"chart-legend\" id=\"legendFluxoAcumulado\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>      <!-- Chart 3: Composi\u00e7\u00e3o encargo vs receita primeiros anos --><\/p>\n<div class=\"chart-card\" style=\"margin-top: 14px;\">\n<div class=\"chart-title\">Composi\u00e7\u00e3o anual: receita vs encargo (anos 1-15)<\/div>\n<div class=\"chart-subtitle\">Cen\u00e1rio 1 (sem desconto). Mostra como o encargo total se decomp\u00f5e em presta\u00e7\u00e3o, OPEX e amortiza\u00e7\u00f5es extra ao longo do tempo, comparado com a receita l\u00edquida.<\/div>\n<div class=\"chart-svg-wrap\" id=\"chartComposicao\"><\/div>\n<div class=\"chart-legend\" id=\"legendComposicao\"><\/div>\n<\/p><\/div>\n<p>      <!-- Chart 4: Saldo d\u00edvida ao longo do tempo --><\/p>\n<div class=\"chart-card\" style=\"margin-top: 14px;\">\n<div class=\"chart-title\">Evolu\u00e7\u00e3o do saldo em d\u00edvida (50 anos)<\/div>\n<div class=\"chart-subtitle\">Capital em d\u00edvida ao longo do prazo do empr\u00e9stimo. Amortiza\u00e7\u00f5es extraordin\u00e1rias produzem saltos para baixo na curva.<\/div>\n<div class=\"chart-svg-wrap\" id=\"chartSaldoDivida\"><\/div>\n<div class=\"chart-summary-stats\" id=\"statsSaldoDivida\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/section>\n<p>  <!-- RESUMO TESOURARIA 10 ANOS --><\/p>\n<section class=\"section page-break-before\" data-tab=\"anual\">\n<div class=\"section-header\">8. Resumo de tesouraria (anos 1-10 + totais 50 anos)<\/div>\n<div class=\"section-body\">\n<table class=\"cashflow-table\">\n<thead>\n<tr id=\"cashflowSummaryHeaderTop\"><\/tr>\n<tr id=\"cashflowSummaryHeaderBottom\"><\/tr>\n<\/thead>\n<tbody id=\"cashflowSummaryBody\"><\/tbody>\n<\/table><\/div>\n<\/section>\n<p>  <!-- EVOLU\u00c7\u00c3O DETALHADA --><\/p>\n<section class=\"section allow-break no-print\" data-tab=\"detalhe\">\n<div class=\"section-header\">9. Evolu\u00e7\u00e3o detalhada da tesouraria (50 anos)<br \/>\n      <button class=\"toggle-button\" style=\"float: right;\" onclick=\"toggleDetail()\">Mostrar \/ ocultar<\/button>\n    <\/div>\n<div class=\"section-body\" id=\"detailContainer\" style=\"display: none;\">\n<div class=\"scrollable\">\n<table>\n<thead>\n<tr id=\"detailHeader\"><\/tr>\n<\/thead>\n<tbody id=\"detailBody\"><\/tbody>\n<\/table><\/div>\n<\/p><\/div>\n<\/section>\n<p>  <!-- DISCLAIMER --><\/p>\n<section class=\"section disclaimer-section\">\n<div class=\"section-header\">Aviso de responsabilidade e confidencialidade<\/div>\n<div class=\"section-body disclaimer-body\">\n      <img decoding=\"async\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABKCAMAAADJ\/ut\/AAABCGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGA8wQAELAYMDLl5JUVB7k4KEZFRCuwPGBiBEAwSk4sLGHADoKpv1yBqL+viUYcLcKakFicD6Q9ArFIEtBxopAiQLZIOYWuA2EkQtg2IXV5SUAJkB4DYRSFBzkB2CpCtkY7ETkJiJxcUgdT3ANk2uTmlyQh3M\/Ck5oUGA2kOIJZhKGYIYnBncAL5H6IkfxEDg8VXBgbmCQixpJkMDNtbGRgkbiHEVBYwMPC3MDBsO48QQ4RJQWJRIliIBYiZ0tIYGD4tZ2DgjWRgEL7AwMAVDQsIHG5TALvNnSEfCNMZchhSgSKeDHkMyQx6QJYRgwGDIYMZAKbWPz9HbOBQAAAAflBMVEUAAABHNVFoVG6vkKSGbYn29fZ8Y4F0c3WwrrA6H0emmqamnahqYW\/d1N63ebd8dIGEeohrZG9oMmjjseMvFDqGfIvGusUeHnLCqrpzc7XBs7zTxs\/5uLjGt8NEPEm7uMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYelwjAAAAIHRSTlMA\/vn5\/A\/8BQ39mGGiUgWRomUMFfxqWwbyBFqLCJCOJyJs1g4AAAvlSURBVHja1ZuJduOoEkARUgkMyGvspJdM\/v8vp6rYBZKdeWfm9OMk3bEsS1zVQi1YiO5Q+AsAdpRSDsNwOAxyNlYrflOD+D8ZjIEQRPB2QIpByvNd\/xCMF0744wdo\/GcxciAEGoN0d\/AM1kxSjhZf6T+fA38WFxDo1\/Eh4SHCMHjo+seTLG54i9KQJ3\/MnqeMQcO+aicAWXhK\/UfWBXATcJYZw5FFoCxGxphLkmmBV\/SrOaXwFO+d899L+wNE92P1AEA1oxFHxhjkgzEmophxVCJBLwavGD0algsfcJYp9H8gELgnrXobzkCzmD3FNK04cIyLOD675nJm\/+0HfuYsIDhGLQfZGyY+Hry9Gf34AAGloGBsRk0yHtIY2ArGSc7TzCArI\/GWIq6w7TVQLy+ZIsFYeveHMM1bfsxR5bR4fESQT3HLV\/4tfn1sg6A4QL5ljk+chiUpzHMEaUlmuy9hNxzamaLlqW0Q5FTBDiBP15RajCA7EtGiMI\/DAQ0AzmHmCaSjXga2PBE+mKk71YPEa2+DZMsrFajUrV0QLUp54L2O4hSnnUFaocxmy3hv6P+2n\/keSHRvj2KeP4vb7IEg8PRWGogQ9\/T4S5CpYye673dvpZUP2eZJIOgl+iDyEi+AZxTzNHAsrG9pbOQjcZwL+0CBAGQ1qkBaEt31wkqcC\/u2StiLYxh5pyVyE0SDippZzbTQLQozcCTOT3yxhLuCOZQgk8D7bIA0JFNvZcSbxYmiQ40HrZSHmaeUQaR7V+rqB658kExs8Y86PPxbcxMbQW61oReKNTgQbhOkQ1K5+XifCHLmO2hWDTDBKxUg540IPEz0I\/qtYxnYHvUxgVh8ocPTGwoOChR3QeYXXJfKE80Co0AiQJcSyaqZA5S06JmoQaBWNmgbR6DEqcIYDvOeanVIQOkGxMWJLqW4rtmU4\/unno0lkM+fH9FI9BMQBbehUiz29HbeBmmUy3WsxCUn1cson4Fo8TPozRKnC09BhKs5EATd73kHZHrBB7v1Uv0tEBGd76KT3wLYBcGrHFYgaO20ru6ATK3ngg3VYiNR3wRRaVnPXri+RQESnyJMhzXHQIlTIumBrM3kUQu+MPZhoNTluCJ9AqLT4p1B6ls0IOh6hzVGiBRgnDZB1iJxK6fCYXoKrgzsSMSJrublyCNFIWoP5F24Dgc5GzRSG4PfpyTzyqkUCyKSeAmXProAMTs+C1OeHHJViVyrWilYbGM7TBjHLYmsScyqGqHEpYp45QUoC2lBBpnCkNPZ+mXvt3ikyecgeCmTkhbEDocuCC65tHhZNIcux\/TE3DG9qUUs712JVMMlC0t5SDaSMnDsgLg+Bt+aZLwYQnlOsg6GcHmSneQw+NANkL9WAaMBrXMUXD6rBgSf2wbHwCkgydaQm3oGYpsEHtDe6wvLEzQhSkciGKdnEznmGS\/Fs9oA6WPIhGKnrkjmKuDqGK2Tq0vLMJldkCwEoMLn7+S2YE+1jKSK4hYHab9HMS1IXSNqQ2ASkZSrPNeT7KpWtu9VzaRY3FuJoBE4ucOBQkEUejRUp2tISgcMvforuvBSwTCOU1CDVEHbtZy6pTVQZ5BbdsAdEM54TvTkDl0OclroPmhSj3Eat0DksRczqYBSDK6IFCDukoem22j4jIu50koV1l6EdA3IRI2PBCM7HJJ91jj6sx6VXEoS2y3X0YoPj1Lm8gtJ9hOr7KZ8lS+XGK6bIJKn7e4Ua0OEWYeEE5OM5uFPynIpQGazVUE9sgPLKAbeq1hL5VQXqoCxHVl9eyBBBifLqqbttFYsz+FZFp98m3Fco5w2S8Fso5eqrrUTNKZUpDNsWtwbkNrAL0HLzJqjsA3vZ5llTJ4L\/3F7NW1VJPGDFmoPBMwmSK6iPQGhMrNn0b6vEzGSLnFF2+rYwRqDUPB3F0TAFcpUaxtkT7MK3doDGaIMzkHJkl0XHOU6yQVFw2dR12C\/y\/AjiURexHUbRBe23Y5H1K0XQAILZP3J9lAHJSEZR0+G8di0BoFVzgg6g7zvgZgdEBEDxybVlf1VEPXHZJapAYntjigYWGybm69eq1wW3VatosJodBq\/Gt3aBWnWQbNA6Les09v8ibP98mt4x1WpOhOSL9jIDZasMkXnJoPoNcjjCYg3dS+WX2OtWKsPnSyIVdLODRfWqPDyCCbVd3dAyqgdc1Q\/iihFhGW3AXG7IOR8SSyIsoERPzkttU1Qrht6v++KJp4TLQnbxYcqRCwcxWc+quih4cjGTq+EGXZIeI2Y5pGDRjvugIQQqqrdUoLrUh9bu7yyq1dATJbx78KTpcy9SFNoXHZ1KyrY6BOsbQz86NrQZbyiM\/dLEWFTz2ITJAeMQWMaOZGzBWNM9m0f+OJTLHJHJLxshxCFUc5y2AJZV3V00ecZyk+d9uta3W7brfDJVPTqNUP3QEI1yGe65lE2C5pxX3nRy8aZkitgP7rV+GpZL03ud1Yk9AHd1hucXgIJURasJZg\/WBcfNMhD\/zzf4PrRl4hOdaC6kVvqFppEt4do5RNzL4pBqLcAT5qYqQgteyjz4ue3AVI0Dn9WDwayyn30JSLgGyAslD7JuVMuXHmGA5XKwqyLyKt49MWD\/1hQnwrdAvORdWvp2EjRtHwFZLrjZWS3iQmdJNfVQYAWeX+Gdn+5v\/DH6arNacKwzbJUvJP+ToO7e\/JVG\/HqBeLe8Vlt6UFdfd5yN44GT+36L+69u1aL+3MQ0q52h4mFTrdd6et2FBnL9++rtCqO9mJhHMPKXg+6+ENuL4gdEBSJ6Zg6bO4sVL7Pqf\/lbWcYu7jthb3XUrDivhaJ\/RP2a2rxJb8FYmqz4lX9j9h3qmOALV8DOa9ADp32dMdgeEcfRIWPexxUPF7u6gt5jIobnlShlpBsZnO7hZSvSkTVHOafCaRwsLC\/7\/X1LbK8Oq0Rtoz9S1RhzcHBCzfTVPz0J1I9N64T+qiuV8qfROmAwn4Pdl20E09z2h1yXJ+xge5tdOUebCOM19wvKtYLeoXpAhfxaPcsrqhcd+JkOO6+haKsD5L3feB\/HEaa0G71ZUg38w4EPqdXq3ErknlLtZDDVBzNmt6vz2EscpKcjABV3fFv6gQrPExbExY4OXfGwAtXTjqBmw8IgvaD+Zy7GDmG6Fj6PXYg36B7I5h6mtWCGF2FKIdug6cHIrmrhMEvnLiRy38TCPWDMAnwh0T4fwkSUVTLBxE341wx7\/RbKVArtlauaZ2t94PGcmccbfYAeE0i3E8fcRb+mWIqTEcQRFGaiunvEVMEmrMqQVgsALT8q8Doe\/qbILdiz0Ys+c7rJjvmmfBVcdxf\/v4D706n5oPlVArdLAN5iSCNugWQSiIe+ysXAqTxurUtEd3bfVJwzD6Gt0WWwXoFr4I4WE40CQSB96TtmCE6am4r0QexLBba+8exjpOYelr+9AG2dxybZvMJGwtTjDRpOJdtNPn56v5wxbVZ\/+UAksg77\/HwIBjo8+70BkR7kNn7KHmmnjueQZ\/bBaG7mXbvSXjFjrzMww+DfH2pYq\/FF6GCo4QfcMR5OgZBIc90QgPyRSB0jBTB+TgI\/YT3EnsgFFnHPRvF+ofiMNq3Nev1HEB\/A8S32BQElaIU8Ry8luP9XhUIEl+jjSx8H7Is8GsEpaMIwtfth9RwxEWCdSn1E0bDFPwdn1UG8r0vO7GPIueDztZ4bbEMAuCVrAKxvLlEcjHNAZdvHBsM5ZTcCmeQ\/QjIjLNvf4auIR67r+pyvEVGfEu1YpGBrfx8v3s3ShKh\/MYRSFityTfJixX+mwskhbu1HBU4v7\/ZeSceEt+9ryaZccJTfDUX4O5kZRu4GC\/f\/BoIa3fa0mVZQ7gQwarlRaAjCOXkIYhRoGLw4jTEFYiEFMpN+EQBdr4slrYR+wZ8Xa623Gv4bkadw24VvCmt1tqH6pow0EY1xEK+vVgdbBDjQ3qBI7xPHwJl1bu4XnefqOZcQPNzSds7Dt5\/3r\/+9wQoGGjefvrKU7n9o0TZS9hezqmew814f8fr9+VRNEpSmlXWHpS\/Y5Qa2n\/xzSo4+nQLJaLyTmj8ub7nysXf6Y6e8\/76JF0AAAAASUVORK5CYII=\" class=\"disclaimer-logo\" alt=\"SFAI Portugal\"><\/p>\n<div class=\"disclaimer-text\">\n<p>Este simulador foi desenvolvido pela <strong>SFAI PMVC RG, Lda.<\/strong> (rede SFAI Portugal) para apoio \u00e0 an\u00e1lise econ\u00f3mico-financeira de investimentos habitacionais ao abrigo do Programa 1.\u00ba Direito (IHRU) e da Portaria 53\/2024.<\/p>\n<p>Os resultados s\u00e3o meramente indicativos e dependem inteiramente dos pressupostos param\u00e9tricos definidos pelo utilizador (Euribor, spread, CAPEX, OPEX, descontos, atualiza\u00e7\u00f5es, m\u00e9todo de amortiza\u00e7\u00e3o, etc.). Os valores devem ser validados \u00e0 data da an\u00e1lise face \u00e0s condi\u00e7\u00f5es efetivamente dispon\u00edveis junto da entidade financiadora.<\/p>\n<p>Esta ferramenta <strong>n\u00e3o substitui aconselhamento financeiro, jur\u00eddico ou contabil\u00edstico formal<\/strong>. A SFAI PMVC RG, Lda. e os seus consultores n\u00e3o assumem responsabilidade por decis\u00f5es tomadas com base exclusiva nos resultados aqui apresentados.<\/p>\n<div class=\"disclaimer-contact\">\n<div class=\"disclaimer-contact-row\">\n            <a href=\"mailto:ruicoutinho@sfai.pt\">ruicoutinho@sfai.pt<\/a><br \/>\n            <span class=\"disclaimer-sep\">\u00b7<\/span><br \/>\n            <a href=\"mailto:gildasilva@sfai.pt\">gildasilva@sfai.pt<\/a>\n          <\/div>\n<div class=\"disclaimer-contact-row\">\n            <a href=\"tel:+351938890429\">tlm. 938 890 429<\/a><br \/>\n            <span class=\"disclaimer-sep\">\u00b7<\/span><br \/>\n            <a href=\"https:\/\/sfai.pt\/en\/\" target=\"_blank\" rel=\"noopener\">www.sfai-portugal.pt<\/a>\n          <\/div>\n<\/p><\/div>\n<p class=\"disclaimer-confidential\">Documento confidencial: distribui\u00e7\u00e3o limitada \u00e0s partes interessadas no projeto.<\/p>\n<\/p><\/div>\n<\/p><\/div>\n<\/section>\n<footer class=\"app-footer\">\n    <img decoding=\"async\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABKCAMAAADJ\/ut\/AAABCGlDQ1BJQ0MgUHJvZmlsZQAAeJxjYGA8wQAELAYMDLl5JUVB7k4KEZFRCuwPGBiBEAwSk4sLGHADoKpv1yBqL+viUYcLcKakFicD6Q9ArFIEtBxopAiQLZIOYWuA2EkQtg2IXV5SUAJkB4DYRSFBzkB2CpCtkY7ETkJiJxcUgdT3ANk2uTmlyQh3M\/Ck5oUGA2kOIJZhKGYIYnBncAL5H6IkfxEDg8VXBgbmCQixpJkMDNtbGRgkbiHEVBYwMPC3MDBsO48QQ4RJQWJRIliIBYiZ0tIYGD4tZ2DgjWRgEL7AwMAVDQsIHG5TALvNnSEfCNMZchhSgSKeDHkMyQx6QJYRgwGDIYMZAKbWPz9HbOBQAAAAflBMVEUAAABHNVFoVG6vkKSGbYn29fZ8Y4F0c3WwrrA6H0emmqamnahqYW\/d1N63ebd8dIGEeohrZG9oMmjjseMvFDqGfIvGusUeHnLCqrpzc7XBs7zTxs\/5uLjGt8NEPEm7uMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYelwjAAAAIHRSTlMA\/vn5\/A\/8BQ39mGGiUgWRomUMFfxqWwbyBFqLCJCOJyJs1g4AAAvlSURBVHja1ZuJduOoEkARUgkMyGvspJdM\/v8vp6rYBZKdeWfm9OMk3bEsS1zVQi1YiO5Q+AsAdpRSDsNwOAxyNlYrflOD+D8ZjIEQRPB2QIpByvNd\/xCMF0744wdo\/GcxciAEGoN0d\/AM1kxSjhZf6T+fA38WFxDo1\/Eh4SHCMHjo+seTLG54i9KQJ3\/MnqeMQcO+aicAWXhK\/UfWBXATcJYZw5FFoCxGxphLkmmBV\/SrOaXwFO+d899L+wNE92P1AEA1oxFHxhjkgzEmophxVCJBLwavGD0algsfcJYp9H8gELgnrXobzkCzmD3FNK04cIyLOD675nJm\/+0HfuYsIDhGLQfZGyY+Hry9Gf34AAGloGBsRk0yHtIY2ArGSc7TzCArI\/GWIq6w7TVQLy+ZIsFYeveHMM1bfsxR5bR4fESQT3HLV\/4tfn1sg6A4QL5ljk+chiUpzHMEaUlmuy9hNxzamaLlqW0Q5FTBDiBP15RajCA7EtGiMI\/DAQ0AzmHmCaSjXga2PBE+mKk71YPEa2+DZMsrFajUrV0QLUp54L2O4hSnnUFaocxmy3hv6P+2n\/keSHRvj2KeP4vb7IEg8PRWGogQ9\/T4S5CpYye673dvpZUP2eZJIOgl+iDyEi+AZxTzNHAsrG9pbOQjcZwL+0CBAGQ1qkBaEt31wkqcC\/u2StiLYxh5pyVyE0SDippZzbTQLQozcCTOT3yxhLuCOZQgk8D7bIA0JFNvZcSbxYmiQ40HrZSHmaeUQaR7V+rqB658kExs8Y86PPxbcxMbQW61oReKNTgQbhOkQ1K5+XifCHLmO2hWDTDBKxUg540IPEz0I\/qtYxnYHvUxgVh8ocPTGwoOChR3QeYXXJfKE80Co0AiQJcSyaqZA5S06JmoQaBWNmgbR6DEqcIYDvOeanVIQOkGxMWJLqW4rtmU4\/unno0lkM+fH9FI9BMQBbehUiz29HbeBmmUy3WsxCUn1cson4Fo8TPozRKnC09BhKs5EATd73kHZHrBB7v1Uv0tEBGd76KT3wLYBcGrHFYgaO20ru6ATK3ngg3VYiNR3wRRaVnPXri+RQESnyJMhzXHQIlTIumBrM3kUQu+MPZhoNTluCJ9AqLT4p1B6ls0IOh6hzVGiBRgnDZB1iJxK6fCYXoKrgzsSMSJrublyCNFIWoP5F24Dgc5GzRSG4PfpyTzyqkUCyKSeAmXProAMTs+C1OeHHJViVyrWilYbGM7TBjHLYmsScyqGqHEpYp45QUoC2lBBpnCkNPZ+mXvt3ikyecgeCmTkhbEDocuCC65tHhZNIcux\/TE3DG9qUUs712JVMMlC0t5SDaSMnDsgLg+Bt+aZLwYQnlOsg6GcHmSneQw+NANkL9WAaMBrXMUXD6rBgSf2wbHwCkgydaQm3oGYpsEHtDe6wvLEzQhSkciGKdnEznmGS\/Fs9oA6WPIhGKnrkjmKuDqGK2Tq0vLMJldkCwEoMLn7+S2YE+1jKSK4hYHab9HMS1IXSNqQ2ASkZSrPNeT7KpWtu9VzaRY3FuJoBE4ucOBQkEUejRUp2tISgcMvforuvBSwTCOU1CDVEHbtZy6pTVQZ5BbdsAdEM54TvTkDl0OclroPmhSj3Eat0DksRczqYBSDK6IFCDukoem22j4jIu50koV1l6EdA3IRI2PBCM7HJJ91jj6sx6VXEoS2y3X0YoPj1Lm8gtJ9hOr7KZ8lS+XGK6bIJKn7e4Ua0OEWYeEE5OM5uFPynIpQGazVUE9sgPLKAbeq1hL5VQXqoCxHVl9eyBBBifLqqbttFYsz+FZFp98m3Fco5w2S8Fso5eqrrUTNKZUpDNsWtwbkNrAL0HLzJqjsA3vZ5llTJ4L\/3F7NW1VJPGDFmoPBMwmSK6iPQGhMrNn0b6vEzGSLnFF2+rYwRqDUPB3F0TAFcpUaxtkT7MK3doDGaIMzkHJkl0XHOU6yQVFw2dR12C\/y\/AjiURexHUbRBe23Y5H1K0XQAILZP3J9lAHJSEZR0+G8di0BoFVzgg6g7zvgZgdEBEDxybVlf1VEPXHZJapAYntjigYWGybm69eq1wW3VatosJodBq\/Gt3aBWnWQbNA6Les09v8ibP98mt4x1WpOhOSL9jIDZasMkXnJoPoNcjjCYg3dS+WX2OtWKsPnSyIVdLODRfWqPDyCCbVd3dAyqgdc1Q\/iihFhGW3AXG7IOR8SSyIsoERPzkttU1Qrht6v++KJp4TLQnbxYcqRCwcxWc+quih4cjGTq+EGXZIeI2Y5pGDRjvugIQQqqrdUoLrUh9bu7yyq1dATJbx78KTpcy9SFNoXHZ1KyrY6BOsbQz86NrQZbyiM\/dLEWFTz2ITJAeMQWMaOZGzBWNM9m0f+OJTLHJHJLxshxCFUc5y2AJZV3V00ecZyk+d9uta3W7brfDJVPTqNUP3QEI1yGe65lE2C5pxX3nRy8aZkitgP7rV+GpZL03ud1Yk9AHd1hucXgIJURasJZg\/WBcfNMhD\/zzf4PrRl4hOdaC6kVvqFppEt4do5RNzL4pBqLcAT5qYqQgteyjz4ue3AVI0Dn9WDwayyn30JSLgGyAslD7JuVMuXHmGA5XKwqyLyKt49MWD\/1hQnwrdAvORdWvp2EjRtHwFZLrjZWS3iQmdJNfVQYAWeX+Gdn+5v\/DH6arNacKwzbJUvJP+ToO7e\/JVG\/HqBeLe8Vlt6UFdfd5yN44GT+36L+69u1aL+3MQ0q52h4mFTrdd6et2FBnL9++rtCqO9mJhHMPKXg+6+ENuL4gdEBSJ6Zg6bO4sVL7Pqf\/lbWcYu7jthb3XUrDivhaJ\/RP2a2rxJb8FYmqz4lX9j9h3qmOALV8DOa9ADp32dMdgeEcfRIWPexxUPF7u6gt5jIobnlShlpBsZnO7hZSvSkTVHOafCaRwsLC\/7\/X1LbK8Oq0Rtoz9S1RhzcHBCzfTVPz0J1I9N64T+qiuV8qfROmAwn4Pdl20E09z2h1yXJ+xge5tdOUebCOM19wvKtYLeoXpAhfxaPcsrqhcd+JkOO6+haKsD5L3feB\/HEaa0G71ZUg38w4EPqdXq3ErknlLtZDDVBzNmt6vz2EscpKcjABV3fFv6gQrPExbExY4OXfGwAtXTjqBmw8IgvaD+Zy7GDmG6Fj6PXYg36B7I5h6mtWCGF2FKIdug6cHIrmrhMEvnLiRy38TCPWDMAnwh0T4fwkSUVTLBxE341wx7\/RbKVArtlauaZ2t94PGcmccbfYAeE0i3E8fcRb+mWIqTEcQRFGaiunvEVMEmrMqQVgsALT8q8Doe\/qbILdiz0Ys+c7rJjvmmfBVcdxf\/v4D706n5oPlVArdLAN5iSCNugWQSiIe+ysXAqTxurUtEd3bfVJwzD6Gt0WWwXoFr4I4WE40CQSB96TtmCE6am4r0QexLBba+8exjpOYelr+9AG2dxybZvMJGwtTjDRpOJdtNPn56v5wxbVZ\/+UAksg77\/HwIBjo8+70BkR7kNn7KHmmnjueQZ\/bBaG7mXbvSXjFjrzMww+DfH2pYq\/FF6GCo4QfcMR5OgZBIc90QgPyRSB0jBTB+TgI\/YT3EnsgFFnHPRvF+ofiMNq3Nev1HEB\/A8S32BQElaIU8Ry8luP9XhUIEl+jjSx8H7Is8GsEpaMIwtfth9RwxEWCdSn1E0bDFPwdn1UG8r0vO7GPIueDztZ4bbEMAuCVrAKxvLlEcjHNAZdvHBsM5ZTcCmeQ\/QjIjLNvf4auIR67r+pyvEVGfEu1YpGBrfx8v3s3ShKh\/MYRSFityTfJixX+mwskhbu1HBU4v7\/ZeSceEt+9ryaZccJTfDUX4O5kZRu4GC\/f\/BoIa3fa0mVZQ7gQwarlRaAjCOXkIYhRoGLw4jTEFYiEFMpN+EQBdr4slrYR+wZ8Xa623Gv4bkadw24VvCmt1tqH6pow0EY1xEK+vVgdbBDjQ3qBI7xPHwJl1bu4XnefqOZcQPNzSds7Dt5\/3r\/+9wQoGGjefvrKU7n9o0TZS9hezqmew814f8fr9+VRNEpSmlXWHpS\/Y5Qa2n\/xzSo4+nQLJaLyTmj8ub7nysXf6Y6e8\/76JF0AAAAASUVORK5CYII=\" alt=\"SFAI Portugal\"><\/p>\n<div>Simulador de investimento habitacional ao abrigo do Programa 1.\u00ba Direito (IHRU). Modelo param\u00e9trico: as c\u00e9lulas azuis s\u00e3o edit\u00e1veis.<\/div>\n<div>&copy; SFAI PMVC RG, Lda. (rede SFAI Portugal) \u00b7 <a href=\"https:\/\/sfai.pt\/en\/\" target=\"_blank\" rel=\"noopener\" class=\"footer-link\">www.sfai-portugal.pt<\/a><\/div>\n<\/footer>\n<\/div>\n<p><!-- Bot\u00e3o flutuante: voltar ao topo --><br \/>\n<button class=\"scroll-top no-print\" id=\"scrollTopBtn\" onclick=\"scrollToTop()\" aria-label=\"Voltar ao topo\">\u2191<\/button><\/p>\n<p><!-- Barra de navega\u00e7\u00e3o inferior (apenas em telem\u00f3vel) --><\/p>\n<nav class=\"bottom-nav no-print\" aria-label=\"Navega\u00e7\u00e3o\">\n<div class=\"bottom-nav-inner\">\n    <button class=\"tab-btn active\" data-tab-target=\"config\" onclick=\"switchTab('config')\"><br \/>\n      <span class=\"tab-icon\"><br \/>\n        <svg viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"\/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"\/><\/svg><br \/>\n      <\/span><br \/>\n      Pressupostos<br \/>\n    <\/button><br \/>\n    <button class=\"tab-btn\" data-tab-target=\"resumo\" onclick=\"switchTab('resumo')\"><br \/>\n      <span class=\"tab-icon\"><br \/>\n        <svg viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"10\"\/><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"4\"\/><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"14\"\/><\/svg><br \/>\n      <\/span><br \/>\n      Resumo<br \/>\n    <\/button><br \/>\n    <button class=\"tab-btn\" data-tab-target=\"detalhe\" onclick=\"switchTab('detalhe')\"><br \/>\n      <span class=\"tab-icon\"><br \/>\n        <svg viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><polyline points=\"14 2 14 8 20 8\"\/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"\/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"\/><polyline points=\"10 9 9 9 8 9\"\/><\/svg><br \/>\n      <\/span><br \/>\n      Detalhe<br \/>\n    <\/button><br \/>\n    <button class=\"tab-btn\" data-tab-target=\"anual\" onclick=\"switchTab('anual')\"><br \/>\n      <span class=\"tab-icon\"><br \/>\n        <svg viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"\/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"\/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"\/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"\/><\/svg><br \/>\n      <\/span><br \/>\n      Anual<br \/>\n    <\/button><br \/>\n    <button class=\"tab-btn tab-action\" type=\"button\" onclick=\"downloadXlsx()\" aria-label=\"Descarregar Excel\"><br \/>\n      <span class=\"tab-icon\"><br \/>\n        <svg viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"\/><polyline points=\"7 10 12 15 17 10\"\/><line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"\/><\/svg><br \/>\n      <\/span><br \/>\n      Excel<br \/>\n    <\/button><br \/>\n    <button class=\"tab-btn tab-action\" type=\"button\" onclick=\"window.print()\" aria-label=\"Imprimir ou guardar PDF\"><br \/>\n      <span class=\"tab-icon\"><br \/>\n        <svg viewbox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"6 9 6 2 18 2 18 9\"\/><path d=\"M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2\"\/><rect x=\"6\" y=\"14\" width=\"12\" height=\"8\"\/><\/svg><br \/>\n      <\/span><br \/>\n      PDF<br \/>\n    <\/button>\n  <\/div>\n<\/nav>\n<p><script>\n\/\/ ============================================================\n\/\/ FORMATTING UTILITIES (pt-PT locale)\n\/\/ ============================================================\nconst fmtEUR = new Intl.NumberFormat('pt-PT', { style: 'currency', currency: 'EUR', maximumFractionDigits: 0 });\nconst fmtEUR2 = new Intl.NumberFormat('pt-PT', { style: 'currency', currency: 'EUR', minimumFractionDigits: 2, maximumFractionDigits: 2 });\nconst fmtNum = new Intl.NumberFormat('pt-PT', { maximumFractionDigits: 0 });\nconst fmtPct1 = new Intl.NumberFormat('pt-PT', { style: 'percent', minimumFractionDigits: 1, maximumFractionDigits: 1 });\nconst fmtPct3 = new Intl.NumberFormat('pt-PT', { style: 'percent', minimumFractionDigits: 3, maximumFractionDigits: 3 });<\/p>\n<p>function eur(v) { return fmtEUR.format(v || 0); }\nfunction eur2(v) { return fmtEUR2.format(v || 0); }\nfunction pct(v) { return fmtPct1.format(v || 0); }\nfunction pct3(v) { return fmtPct3.format(v || 0); }<\/p>\n<p>\/\/ ============================================================\n\/\/ READ INPUTS\n\/\/ ============================================================\nfunction readParams() {\n  const p = {\n    rendas: [\n      Number(document.getElementById('renda_T1').value),\n      Number(document.getElementById('renda_T2').value),\n      Number(document.getElementById('renda_T3').value),\n      Number(document.getElementById('renda_T4').value)\n    ],\n    fogos: [\n      Number(document.getElementById('qty_T1').value),\n      Number(document.getElementById('qty_T2').value),\n      Number(document.getElementById('qty_T3').value),\n      Number(document.getElementById('qty_T4').value)\n    ],\n    capex: Number(document.getElementById('capex').value),\n    ihruPct: Number(document.getElementById('ihru_pct').value) \/ 100,\n    prazo: Number(document.getElementById('prazo').value),\n    carencia: Number(document.getElementById('carencia').value),\n    spread: Number(document.getElementById('spread').value) \/ 100,\n    euribor: Number(document.getElementById('euribor').value) \/ 100,\n    inflacao: Number(document.getElementById('inflacao').value) \/ 100,\n    atualizacao: Number(document.getElementById('atualizacao').value) \/ 100,\n    opex: Number(document.getElementById('opex').value),\n    anosGarantia: Number(document.getElementById('anos_garantia').value),\n    ponderador: Number(document.getElementById('ponderador').value) \/ 100,\n    incobraveis: Number(document.getElementById('incobraveis').value) \/ 100,\n    prazoObra: Number(document.getElementById('prazo_obra').value),\n    inicioCobranca: Number(document.getElementById('inicio_cobranca').value),\n    metodoAmort: document.getElementById('metodo_amort').value,\n    descontos: [\n      Number(document.getElementById('desc_1').value) \/ 100,\n      Number(document.getElementById('desc_2').value) \/ 100,\n      Number(document.getElementById('desc_3').value) \/ 100,\n      Number(document.getElementById('desc_4').value) \/ 100\n    ],\n    amortizacoesExtra: [],\n    amortizacoesExtraRejeitadas: []\n  };\n  \/\/ 1.\u00ba ano de amortiza\u00e7\u00e3o = depois da car\u00eancia E depois da obra\n  const firstAmortYear = Math.max(p.carencia, Math.ceil(p.prazoObra \/ 12)) + 1;\n  document.querySelectorAll('.amort-row').forEach(row => {\n    const year = Number(row.querySelector('.amort-year').value);\n    const value = Number(row.querySelector('.amort-value').value);\n    if (value <= 0) return;\n    if (year >= firstAmortYear && year <= p.prazo) {\n      p.amortizacoesExtra.push({ year, value });\n    } else if (year >= 1 && year <= 50) {\n      p.amortizacoesExtraRejeitadas.push({ year, value });\n    }\n  });\n  return p;\n}\n\n\/\/ ============================================================\n\/\/ AMORT EXTRA UI MANAGEMENT\n\/\/ ============================================================\nlet amortRowCounter = 0;\nfunction addAmortRow(year='', value='') {\n  amortRowCounter++;\n  const container = document.getElementById('amortRows');\n  const row = document.createElement('div');\n  row.className = 'amort-row';\n  row.innerHTML = `\n    \n\n<div class=\"field\">\n      <label>Ano<\/label>\n      <input type=\"number\" class=\"amort-year\" min=\"1\" max=\"50\" step=\"1\" value=\"${year}\" placeholder=\"ex: 10\">\n    <\/div>\n<div class=\"field\">\n      <label>Valor (\u20ac)<\/label>\n      <input type=\"number\" class=\"amort-value\" min=\"0\" step=\"1000\" value=\"${value}\" placeholder=\"ex: 500000\">\n    <\/div>\n<p>    <button type=\"button\" class=\"remove-btn\" title=\"Remover\" onclick=\"removeAmortRow(this)\">\u00d7<\/button>\n  `;\n  container.appendChild(row);\n  row.querySelectorAll('input').forEach(inp => inp.addEventListener('input', render));\n  render();\n}<\/p>\n<p>function removeAmortRow(btn) {\n  btn.parentElement.remove();\n  render();\n}<\/p>\n<p>\/\/ ============================================================\n\/\/ CALCULATIONS (replicate Excel formulas)\n\/\/ ============================================================\nfunction pmt(rate, nper, pv) {\n  if (rate === 0) return pv \/ nper;\n  return pv * rate \/ (1 - Math.pow(1 + rate, -nper));\n}<\/p>\n<p>function calculate(p) {\n  const totalFogos = p.fogos.reduce((a, b) => a + b, 0);\n  const ihruEur = p.capex * p.ihruPct;\n  const finCM = p.capex - ihruEur;\n  const taxaJuro = p.spread + p.euribor;\n  const receitaAnualBruta = p.rendas.reduce((sum, r, i) => sum + r * p.fogos[i] * 12, 0);\n  const nAmort = p.prazo - p.carencia;  \/\/ anos de amortiza\u00e7\u00e3o\n  const prestacaoAnual = pmt(taxaJuro, nAmort, finCM);  \/\/ anuidade (presta\u00e7\u00e3o constante)\n  const amortizacaoLinear = finCM \/ nAmort;             \/\/ amortiza\u00e7\u00e3o constante de capital\n  \/\/ Componentes da 1.\u00aa presta\u00e7\u00e3o ap\u00f3s car\u00eancia\n  const componenteJuros1 = finCM * taxaJuro;  \/\/ igual nos dois m\u00e9todos no 1.\u00ba ano\n  const componenteCapital1 = (p.metodoAmort === 'linear')\n    ? amortizacaoLinear\n    : prestacaoAnual - componenteJuros1;\n  \/\/ Presta\u00e7\u00e3o representativa para KPIs (ano 1, em linear \u00e9 a mais alta)\n  const prestacao1Ano = (p.metodoAmort === 'linear')\n    ? amortizacaoLinear + componenteJuros1\n    : prestacaoAnual;<\/p>\n<p>  \/\/ Year 1 to 50 cash flow\n  const years = [];\n  let saldoAnterior = finCM;  \/\/ J6 in Excel\n  let encargoAcumulado = 0;\n  const fluxoAcumulado = [0, 0, 0, 0];<\/p>\n<p>  \/\/ Map of extra amortizations by year for quick lookup\n  const extraPorAno = {};\n  p.amortizacoesExtra.forEach(a => {\n    extraPorAno[a.year] = (extraPorAno[a.year] || 0) + a.value;\n  });\n  \/\/ Track effective extras applied (after capping)\n  const extrasAplicadasInfo = [];<\/p>\n<p>  for (let year = 1; year <= 50; year++) {\n    \/\/ Saldo Inicial\n    const saldoInicial = year === 1 ? 0 : saldoAnterior;\n\n    \/\/ Saldo Final and amortization\n    let saldoFinal, amortizacao, juros, amortExtra = 0;\n    let saldoAntesExtra = saldoInicial;\n\n    if ((year - 1) * 12 < p.prazoObra) {\n      \/\/ Construction phase: capital released proportionally\n      saldoFinal = Math.min(finCM, finCM * year * 12 \/ p.prazoObra);\n      juros = (saldoInicial + saldoFinal) \/ 2 * taxaJuro;\n      amortizacao = 0;\n    } else if (year <= p.carencia) {\n      \/\/ Car\u00eancia period\n      saldoFinal = saldoInicial;\n      juros = saldoInicial * taxaJuro;\n      amortizacao = 0;\n    } else {\n      \/\/ Amortization phase\n      if (saldoInicial < 0.01) {\n        \/\/ Loan already paid off\n        juros = 0;\n        amortizacao = 0;\n        saldoFinal = 0;\n      } else {\n        juros = saldoInicial * taxaJuro;\n        if (p.metodoAmort === 'linear') {\n          \/\/ Capital constante: amortiza\u00e7\u00e3o linear\n          amortizacao = Math.min(amortizacaoLinear, saldoInicial);\n        } else {\n          \/\/ Presta\u00e7\u00e3o constante (anuidade)\n          amortizacao = Math.max(0, Math.min(prestacaoAnual - juros, saldoInicial));\n        }\n        const saldoApos = saldoInicial - amortizacao;\n        saldoAntesExtra = saldoApos;\n        \/\/ Apply extra amortization (capped at remaining balance)\n        const extraDesteAno = extraPorAno[year] || 0;\n        amortExtra = Math.min(extraDesteAno, saldoApos);\n        saldoFinal = saldoApos - amortExtra;\n        if (extraDesteAno > 0) {\n          extrasAplicadasInfo.push({\n            year, requested: extraDesteAno, applied: amortExtra,\n            saldoAntes: saldoApos, saldoApos: saldoFinal\n          });\n        }\n      }\n    }<\/p>\n<p>    const prestacao = juros + amortizacao;<\/p>\n<p>    \/\/ OPEX: warranty period uses ponderador, otherwise 100%\n    const opexCoef = year <= p.anosGarantia ? p.ponderador : 1;\n    const opexAno = p.opex * Math.pow(1 + p.inflacao, year - 1) * opexCoef;\n\n    \/\/ Encargo includes extra amortization as additional cash outflow\n    const encargo = prestacao + amortExtra + opexAno;\n    encargoAcumulado += encargo;\n\n    \/\/ Fraction of year with rent collection\n    const fracaoAno = Math.max(0, Math.min(year * 12, 600) - Math.max((year - 1) * 12, p.prazoObra + p.inicioCobranca)) \/ 12;\n    const receitaBaseAno = receitaAnualBruta * Math.pow(1 + p.atualizacao, year - 1) * fracaoAno;\n\n    const scenarios = p.descontos.map((d, idx) => {\n      const receitaBruta = receitaBaseAno * (1 - d);\n      const incobraveisVal = receitaBruta * p.incobraveis;\n      const receitaLiquida = receitaBruta - incobraveisVal;\n      const fluxoLiquido = receitaLiquida - encargo;\n      fluxoAcumulado[idx] += fluxoLiquido;\n      return { receitaBruta, incobraveis: incobraveisVal, receitaLiquida, fluxoLiquido, fluxoAcumulado: fluxoAcumulado[idx] };\n    });<\/p>\n<p>    years.push({ year, saldoInicial, juros, amortizacao, amortExtra, prestacao, opex: opexAno, encargo, encargoAcumulado, saldoFinal, scenarios });\n    saldoAnterior = saldoFinal;\n  }<\/p>\n<p>  \/\/ Aggregate totals per scenario\n  const totais = p.descontos.map((d, idx) => {\n    let receitaBruta = 0, receitaLiquida = 0, fluxoLiquido = 0;\n    let receitaBruta10 = 0, receitaLiquida10 = 0, fluxoLiquido10 = 0;\n    years.forEach(y => {\n      receitaBruta += y.scenarios[idx].receitaBruta;\n      receitaLiquida += y.scenarios[idx].receitaLiquida;\n      fluxoLiquido += y.scenarios[idx].fluxoLiquido;\n      if (y.year <= 10) {\n        receitaBruta10 += y.scenarios[idx].receitaBruta;\n        receitaLiquida10 += y.scenarios[idx].receitaLiquida;\n        fluxoLiquido10 += y.scenarios[idx].fluxoLiquido;\n      }\n    });\n    return {\n      desconto: d,\n      receitaAnualBaseDescontada: receitaAnualBruta * (1 - d),\n      receitaLiquidaAnual: receitaAnualBruta * (1 - d) * (1 - p.incobraveis),\n      receitaBruta50: receitaBruta,\n      receitaLiquida50: receitaLiquida,\n      fluxoLiquido50: fluxoLiquido,\n      receitaLiquida10: receitaLiquida10,\n      fluxoLiquido10: fluxoLiquido10,\n      margemAnual: receitaAnualBruta * (1 - d) * (1 - p.incobraveis) - (prestacao1Ano + p.opex)\n    };\n  });\n\n  \/\/ Total juros (vida do empr\u00e9stimo): all juros accumulated\n  let totalJurosVida = 0;\n  let totalAmortCapital = 0;\n  let totalAmortExtra = 0;\n  let totalOpex = 0;\n  let totalEncargo = 0;\n  years.forEach(y => {\n    totalJurosVida += y.juros;\n    totalAmortCapital += y.amortizacao;\n    totalAmortExtra += y.amortExtra;\n    totalOpex += y.opex;\n    totalEncargo += y.encargo;\n  });<\/p>\n<p>  return {\n    p, totalFogos, ihruEur, finCM, taxaJuro, receitaAnualBruta,\n    prestacaoAnual, amortizacaoLinear, prestacao1Ano,\n    componenteCapital1, componenteJuros1,\n    totalJurosVida, totalAmortCapital, totalAmortExtra, totalOpex, totalEncargo,\n    custoTotalFin: finCM + totalJurosVida,\n    extrasAplicadasInfo,\n    years, totais\n  };\n}<\/p>\n<p>\/\/ ============================================================\n\/\/ RENDER UI\n\/\/ ============================================================\nfunction render() {\n  const p = readParams();\n  const c = calculate(p);<\/p>\n<p>  \/\/ Atualiza a nota informativa com o primeiro ano v\u00e1lido para amortiza\u00e7\u00f5es extraordin\u00e1rias\n  const primAnoEl = document.getElementById('primeiroAnoAmortExtra');\n  const firstAmortYear = Math.max(p.carencia, Math.ceil(p.prazoObra \/ 12)) + 1;\n  if (primAnoEl) primAnoEl.textContent = firstAmortYear;<\/p>\n<p>  \/\/ KPI summary\n  const kpiHtml = `<\/p>\n<div class=\"kpi kpi-primary\">\n<div class=\"kpi-label\">CAPEX Total<\/div>\n<div class=\"kpi-value\">${eur(p.capex)}<\/div>\n<div class=\"kpi-sub\">${c.totalFogos} fogos: ${eur(p.capex \/ c.totalFogos)} \/ fogo<\/div>\n<\/p><\/div>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Comparticipa\u00e7\u00e3o IHRU<\/div>\n<div class=\"kpi-value\">${eur(c.ihruEur)}<\/div>\n<div class=\"kpi-sub\">${pct(p.ihruPct)} do CAPEX<\/div>\n<\/p><\/div>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Financiamento CM<\/div>\n<div class=\"kpi-value\">${eur(c.finCM)}<\/div>\n<div class=\"kpi-sub\">${pct(1 - p.ihruPct)} do CAPEX<\/div>\n<\/p><\/div>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Receita anual bruta integral<\/div>\n<div class=\"kpi-value\">${eur(c.receitaAnualBruta)}<\/div>\n<div class=\"kpi-sub\">Sem desconto, sem incobr\u00e1veis<\/div>\n<\/p><\/div>\n<div class=\"kpi kpi-primary\">\n<div class=\"kpi-label\">${p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o 1.\u00ba ano' : 'Presta\u00e7\u00e3o anual'}<\/div>\n<div class=\"kpi-value\">${eur(c.prestacao1Ano)}<\/div>\n<div class=\"kpi-sub\">${pct3(c.taxaJuro)}, ${p.prazo - p.carencia} anos${p.metodoAmort === 'linear' ? ', linear (decresce)' : ', anuidade (constante)'}<\/div>\n<\/p><\/div>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Total juros vida do empr\u00e9stimo<\/div>\n<div class=\"kpi-value\">${eur(c.totalJurosVida)}<\/div>\n<div class=\"kpi-sub\">Inclui juros de constru\u00e7\u00e3o e car\u00eancia<\/div>\n<\/p><\/div>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Custo total do financiamento<\/div>\n<div class=\"kpi-value\">${eur(c.custoTotalFin)}<\/div>\n<div class=\"kpi-sub\">Capital + juros<\/div>\n<\/p><\/div>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Total OPEX 50 anos (nominal acumulado)<\/div>\n<div class=\"kpi-value\">${eur(c.totalOpex)}<\/div>\n<div class=\"kpi-sub\">Soma anual com infla\u00e7\u00e3o e ponderador na garantia<\/div>\n<\/p><\/div>\n<p>    ${c.totalAmortExtra > 0 ? `<\/p>\n<div class=\"kpi\">\n<div class=\"kpi-label\">Amortiza\u00e7\u00f5es extraordin\u00e1rias<\/div>\n<div class=\"kpi-value\">${eur(c.totalAmortExtra)}<\/div>\n<div class=\"kpi-sub\">${c.extrasAplicadasInfo.length} pagamento(s) antecipado(s)<\/div>\n<\/p><\/div>\n<p>` : ''}\n  `;\n  document.getElementById('kpiSummary').innerHTML = kpiHtml;<\/p>\n<p>  \/\/ An\u00e1lise por cen\u00e1rio\n  const scenarioCols = ['Cen\u00e1rio 1', 'Cen\u00e1rio 2', 'Cen\u00e1rio 3', 'Cen\u00e1rio 4'];\n  let analysisHeader = '<\/p>\n<th>Item<\/th>\n<p>';\n  scenarioCols.forEach(s => analysisHeader += `<\/p>\n<th>${s}<\/th>\n<p>`);\n  document.getElementById('analysisHeader').innerHTML = analysisHeader;<\/p>\n<p>  const analysisRows = [\n    ['CAPEX Total (\u20ac)', () => p.capex, eur2],\n    ['Comparticipa\u00e7\u00e3o IHRU (\u20ac)', () => c.ihruEur, eur2],\n    ['Financiamento CM (\u20ac)', () => c.finCM, eur2],\n    ['OPEX anual nominal (\u20ac)', () => p.opex, eur2],\n    ['Financiamento CM + OPEX anual nominal', () => c.finCM + p.opex, eur2],\n    ['% Desconto nas rendas', (i) => p.descontos[i], pct],\n    ['Receita anual bruta c\/ desconto', (i) => c.totais[i].receitaAnualBaseDescontada, eur2],\n    ['Receita anual l\u00edquida (ap\u00f3s incob.)', (i) => c.totais[i].receitaLiquidaAnual, eur2]\n  ];<\/p>\n<p>  let analysisBody = '';\n  analysisRows.forEach(([label, fn, fmt]) => {\n    analysisBody += `<\/p>\n<tr>\n<td>${label}<\/td>\n<p>`;\n    for (let i = 0; i < 4; i++) {\n      const val = fn(i);\n      analysisBody += `\n\n<td data-label=\"${scenarioCols[i]}\">${fmt(val)}<\/td>\n<p>`;\n    }\n    analysisBody += '<\/tr>\n<p>';\n  });\n  document.getElementById('analysisBody').innerHTML = analysisBody;<\/p>\n<p>  \/\/ Servi\u00e7o da d\u00edvida\n  const jurosConstrucaoCarencia = c.years\n    .filter(y => y.year <= p.carencia)\n    .reduce((sum, y) => sum + y.juros, 0);\n  let debtRows = [\n    ['Capital em d\u00edvida (Financiamento CM)', c.finCM],\n    ['Taxa de juro total (Euribor + spread)', c.taxaJuro, 'pct'],\n    ['M\u00e9todo de amortiza\u00e7\u00e3o', p.metodoAmort === 'linear' ? 'Capital constante (linear)' : 'Presta\u00e7\u00e3o constante (anuidade)', 'text'],\n    ['Juros anuais (com capital totalmente libertado)', c.finCM * c.taxaJuro],\n    ['Total juros constru\u00e7\u00e3o + car\u00eancia (' + p.carencia + ' anos, calculados ano a ano)', jurosConstrucaoCarencia],\n    [(p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o 1.\u00ba ano (a mais alta)' : 'Presta\u00e7\u00e3o anual') + ' (ap\u00f3s car\u00eancia, ' + (p.prazo - p.carencia) + ' anos)', c.prestacao1Ano],\n    ['  dos quais: componente de capital (1.\u00aa presta\u00e7\u00e3o)', c.componenteCapital1],\n    ['  dos quais: componente de juros (1.\u00aa presta\u00e7\u00e3o)', c.componenteJuros1],\n    ['Total juros pagos (vida do empr\u00e9stimo)', c.totalJurosVida],\n    ['Custo total do financiamento (capital + juros)', c.custoTotalFin]\n  ];<\/p>\n<p>  let debtHtml = '';\n  debtRows.forEach(([label, val, type]) => {\n    let formatted;\n    if (type === 'pct') formatted = pct3(val);\n    else if (type === 'text') formatted = val;\n    else formatted = eur2(val);\n    debtHtml += `<\/p>\n<tr>\n<td>${label}<\/td>\n<td>${formatted}<\/td>\n<\/tr>\n<p>`;\n  });\n  document.getElementById('debtBody').innerHTML = debtHtml;<\/p>\n<p>  \/\/ Indicadores por cen\u00e1rio\n  let indicatorsHeader = '<\/p>\n<th>Indicador<\/th>\n<p>';\n  scenarioCols.forEach(s => indicatorsHeader += `<\/p>\n<th>${s}<\/th>\n<p>`);\n  document.getElementById('indicatorsHeader').innerHTML = indicatorsHeader;<\/p>\n<p>  const indicators = [\n    ['CAPEX por fogo (\u20ac)', () => p.capex \/ c.totalFogos],\n    ['Financiamento CM por fogo (\u20ac)', () => c.finCM \/ c.totalFogos],\n    ['Poupan\u00e7a CM por fogo (vs 100% CAPEX)', () => c.ihruEur \/ c.totalFogos],\n    [(p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o mensal\/fogo (1.\u00ba ano)' : 'Presta\u00e7\u00e3o mensal por fogo (\u20ac)'), () => c.prestacao1Ano \/ 12 \/ c.totalFogos],\n    [(p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o mensal total CM (1.\u00ba ano)' : 'Presta\u00e7\u00e3o mensal total CM (\u20ac)'), () => c.prestacao1Ano \/ 12],\n    ['Encargo anual total CM (Presta\u00e7\u00e3o + OPEX nominal)', () => c.prestacao1Ano + p.opex],\n    ['Receita anual bruta c\/ desconto (\u20ac)', (i) => c.totais[i].receitaAnualBaseDescontada],\n    ['Receita anual l\u00edquida (\u20ac)', (i) => c.totais[i].receitaLiquidaAnual],\n    ['Margem anual (Rec. L\u00edq. - Encargo)', (i) => c.totais[i].margemAnual]\n  ];<\/p>\n<p>  let indicatorsBody = '';\n  indicators.forEach(([label, fn]) => {\n    indicatorsBody += `<\/p>\n<tr>\n<td>${label}<\/td>\n<p>`;\n    for (let i = 0; i < 4; i++) {\n      const val = fn(i);\n      const cls = (label.startsWith('Margem') &#038;&#038; val < 0) ? 'class=\"negative\"' : '';\n      indicatorsBody += `\n\n<td ${cls} data-label=\"${scenarioCols[i]}\">${eur2(val)}<\/td>\n<p>`;\n    }\n    indicatorsBody += '<\/tr>\n<p>';\n  });\n  document.getElementById('indicatorsBody').innerHTML = indicatorsBody;<\/p>\n<p>  \/\/ Compara\u00e7\u00e3o\n  let comparisonHeader = '<\/p>\n<th>Compara\u00e7\u00e3o<\/th>\n<p>';\n  scenarioCols.forEach(s => comparisonHeader += `<\/p>\n<th>${s}<\/th>\n<p>`);\n  document.getElementById('comparisonHeader').innerHTML = comparisonHeader;<\/p>\n<p>  const ref = c.totais[0];\n  const compRows = [\n    ['Receita anual bruta c\/ desconto (\u20ac)', (i) => c.totais[i].receitaAnualBaseDescontada, eur2],\n    ['Diferen\u00e7a anual vs Cen\u00e1rio 1 (\u20ac)', (i) => c.totais[i].receitaAnualBaseDescontada - ref.receitaAnualBaseDescontada, eur2],\n    ['Diferen\u00e7a % vs Cen\u00e1rio 1', (i) => ref.receitaAnualBaseDescontada !== 0 ? (c.totais[i].receitaAnualBaseDescontada - ref.receitaAnualBaseDescontada) \/ ref.receitaAnualBaseDescontada : 0, pct],\n    ['Margem anual (Rec. L\u00edq. - Encargo)', (i) => c.totais[i].margemAnual, eur2],\n    ['Diferen\u00e7a margem anual vs Cen\u00e1rio 1', (i) => c.totais[i].margemAnual - ref.margemAnual, eur2],\n    ['Fluxo l\u00edquido total 50 anos', (i) => c.totais[i].fluxoLiquido50, eur2],\n    ['Diferen\u00e7a fluxo 50 anos vs Cen\u00e1rio 1', (i) => c.totais[i].fluxoLiquido50 - ref.fluxoLiquido50, eur2]\n  ];<\/p>\n<p>  let comparisonBody = '';\n  compRows.forEach(([label, fn, fmt]) => {\n    comparisonBody += `<\/p>\n<tr>\n<td>${label}<\/td>\n<p>`;\n    for (let i = 0; i < 4; i++) {\n      const val = fn(i);\n      let cls = '';\n      if (label.includes('Diferen\u00e7a') &#038;&#038; i > 0) {\n        cls = val < 0 ? 'class=\"negative\"' : (val > 0 ? 'class=\"positive\"' : '');\n      }\n      comparisonBody += `<\/p>\n<td ${cls} data-label=\"${scenarioCols[i]}\">${fmt(val)}<\/td>\n<p>`;\n    }\n    comparisonBody += '<\/tr>\n<p>';\n  });\n  document.getElementById('comparisonBody').innerHTML = comparisonBody;<\/p>\n<p>  \/\/ Summary das amortiza\u00e7\u00f5es extras\n  const amortSect = document.getElementById('amortSummarySection');\n  const amortBody = document.getElementById('amortSummaryBody');\n  const amortNote = document.getElementById('amortImpactNote');\n  const temRejeitadas = (p.amortizacoesExtraRejeitadas || []).length > 0;\n  if (c.extrasAplicadasInfo.length > 0 || temRejeitadas) {\n    amortSect.style.display = '';\n    let amortHtml = '';\n    c.extrasAplicadasInfo.forEach((info, idx) => {\n      const truncated = info.applied < info.requested;\n      const note = truncated ? ` <span style=\"color:var(--negative);font-size:11px;\">(limitada ao saldo: ${eur(info.applied)} de ${eur(info.requested)} pretendidos)<\/span>` : '';\n      amortHtml += `<\/p>\n<tr>\n<td data-label=\"#\">${idx+1}<\/td>\n<td data-label=\"Ano\">${info.year}<\/td>\n<td data-label=\"Valor\">${eur2(info.applied)}${note}<\/td>\n<td data-label=\"Saldo antes\">${eur(info.saldoAntes)}<\/td>\n<td data-label=\"Saldo ap\u00f3s\">${eur(info.saldoApos)}<\/td>\n<\/tr>\n<p>`;\n    });\n    amortBody.innerHTML = amortHtml;<\/p>\n<p>    const totalApplied = c.extrasAplicadasInfo.reduce((s, x) => s + x.applied, 0);\n    let noteHtml = '';\n    if (totalApplied > 0) {\n      noteHtml += `Total amortiza\u00e7\u00f5es extraordin\u00e1rias aplicadas: <strong>${eur2(totalApplied)}<\/strong>. Estas sa\u00eddas adicionais de tesouraria est\u00e3o inclu\u00eddas no fluxo l\u00edquido do ano em que ocorrem e reduzem progressivamente os juros pagos nos anos seguintes.`;\n    }\n    if (temRejeitadas) {\n      const rej = p.amortizacoesExtraRejeitadas\n        .map(r => `ano ${r.year} (${eur2(r.value)})`).join(', ');\n      noteHtml += (noteHtml ? '<\/p>\n<p>' : '') +\n        `<span style=\"color:var(--negative);\"><strong>Aviso:<\/strong> ${p.amortizacoesExtraRejeitadas.length} amortiza\u00e7\u00e3o(\u00f5es) extraordin\u00e1ria(s) ignorada(s) por estarem em per\u00edodo de constru\u00e7\u00e3o\/car\u00eancia ou fora do prazo do empr\u00e9stimo: ${rej}. As amortiza\u00e7\u00f5es extraordin\u00e1rias s\u00f3 se aplicam a partir do ano ${firstAmortYear}.<\/span>`;\n    }\n    amortNote.innerHTML = noteHtml;\n  } else {\n    amortSect.style.display = 'none';\n  }<\/p>\n<p>  \/\/ Resumo tesouraria 10 anos\n  let topHeader = '<\/p>\n<th rowspan=\"2\">Per\u00edodo<\/th>\n<p>';\n  scenarioCols.forEach(s => topHeader += `<\/p>\n<th colspan=\"2\">${s}<\/th>\n<p>`);\n  document.getElementById('cashflowSummaryHeaderTop').innerHTML = topHeader;<\/p>\n<p>  let bottomHeader = '';\n  for (let i = 0; i < 4; i++) {\n    bottomHeader += '\n\n<th>Receita L\u00edq.<\/th>\n<th>Fluxo L\u00edq.<\/th>\n<p>';\n  }\n  document.getElementById('cashflowSummaryHeaderBottom').innerHTML = bottomHeader;<\/p>\n<p>  let cashflowBody = '';\n  for (let y = 0; y < 10; y++) {\n    cashflowBody += `\n\n<tr>\n<td>Ano ${c.years[y].year}<\/td>\n<p>`;\n    for (let i = 0; i < 4; i++) {\n      const sc = c.years[y].scenarios[i];\n      const flCls = sc.fluxoLiquido < 0 ? 'class=\"negative\"' : '';\n      cashflowBody += `\n\n<td data-label=\"C${i+1} Rec. L\u00edq.\">${eur(sc.receitaLiquida)}<\/td>\n<td ${flCls} data-label=\"C${i+1} Fluxo\">${eur(sc.fluxoLiquido)}<\/td>\n<p>`;\n    }\n    cashflowBody += '<\/tr>\n<p>';\n  }<\/p>\n<p>  \/\/ Subtotal anos 1-10\n  cashflowBody += `<\/p>\n<tr class=\"subtotal-row\">\n<td>Subtotal anos 1-10<\/td>\n<p>`;\n  for (let i = 0; i < 4; i++) {\n    cashflowBody += `\n\n<td data-label=\"C${i+1} Rec. L\u00edq.\">${eur(c.totais[i].receitaLiquida10)}<\/td>\n<td data-label=\"C${i+1} Fluxo\">${eur(c.totais[i].fluxoLiquido10)}<\/td>\n<p>`;\n  }\n  cashflowBody += '<\/tr>\n<p>';<\/p>\n<p>  \/\/ Anos 11-50\n  cashflowBody += `<\/p>\n<tr class=\"subtotal-row\">\n<td>Anos 11-50 (40 anos)<\/td>\n<p>`;\n  for (let i = 0; i < 4; i++) {\n    const rec40 = c.totais[i].receitaLiquida50 - c.totais[i].receitaLiquida10;\n    const flux40 = c.totais[i].fluxoLiquido50 - c.totais[i].fluxoLiquido10;\n    cashflowBody += `\n\n<td data-label=\"C${i+1} Rec. L\u00edq.\">${eur(rec40)}<\/td>\n<td data-label=\"C${i+1} Fluxo\">${eur(flux40)}<\/td>\n<p>`;\n  }\n  cashflowBody += '<\/tr>\n<p>';<\/p>\n<p>  \/\/ Total 50 anos\n  cashflowBody += `<\/p>\n<tr class=\"total-row\">\n<td>TOTAL 50 ANOS<\/td>\n<p>`;\n  for (let i = 0; i < 4; i++) {\n    cashflowBody += `\n\n<td data-label=\"C${i+1} Rec. L\u00edq.\">${eur(c.totais[i].receitaLiquida50)}<\/td>\n<td data-label=\"C${i+1} Fluxo\">${eur(c.totais[i].fluxoLiquido50)}<\/td>\n<p>`;\n  }\n  cashflowBody += '<\/tr>\n<p>';<\/p>\n<p>  document.getElementById('cashflowSummaryBody').innerHTML = cashflowBody;<\/p>\n<p>  \/\/ Detalhe 50 anos (full)\n  let detailHeader = `<\/p>\n<th>Ano<\/th>\n<th>Saldo Inicial<\/th>\n<th>Juros<\/th>\n<th>Amort. Cap.<\/th>\n<th>Amort. Extra<\/th>\n<th>Presta\u00e7\u00e3o<\/th>\n<th>OPEX<\/th>\n<th>Encargo<\/th>\n<th>Saldo Final<\/th>\n<p>`;\n  for (let i = 1; i <= 4; i++) {\n    detailHeader += `\n\n<th>C${i} Rec. L\u00edq.<\/th>\n<th>C${i} Fluxo L\u00edq.<\/th>\n<p>`;\n  }\n  document.getElementById('detailHeader').innerHTML = detailHeader;<\/p>\n<p>  let detailBody = '';\n  c.years.forEach(y => {\n    detailBody += `<\/p>\n<tr>\n<td>${y.year}<\/td>\n<p>`;\n    detailBody += `<\/p>\n<td>${eur(y.saldoInicial)}<\/td>\n<td>${eur(y.juros)}<\/td>\n<td>${eur(y.amortizacao)}<\/td>\n<p>`;\n    const extraCls = y.amortExtra > 0 ? 'class=\"positive\"' : '';\n    detailBody += `<\/p>\n<td ${extraCls}>${y.amortExtra > 0 ? eur(y.amortExtra) : '-'}<\/td>\n<p>`;\n    detailBody += `<\/p>\n<td>${eur(y.prestacao)}<\/td>\n<td>${eur(y.opex)}<\/td>\n<td>${eur(y.encargo)}<\/td>\n<td>${eur(y.saldoFinal)}<\/td>\n<p>`;\n    y.scenarios.forEach(sc => {\n      const flCls = sc.fluxoLiquido < 0 ? 'class=\"negative\"' : '';\n      detailBody += `\n\n<td>${eur(sc.receitaLiquida)}<\/td>\n<td ${flCls}>${eur(sc.fluxoLiquido)}<\/td>\n<p>`;\n    });\n    detailBody += '<\/tr>\n<p>';\n  });\n  document.getElementById('detailBody').innerHTML = detailBody;<\/p>\n<p>  \/\/ Cache for Excel generation\n  window._calc = c;\n  window._params = p;<\/p>\n<p>  \/\/ Render charts and flux panel (needs c)\n  renderFluxoPanel(c, p);\n  renderAllCharts(c);\n}<\/p>\n<p>function toggleDetail() {\n  const el = document.getElementById('detailContainer');\n  el.style.display = el.style.display === 'none' ? 'block' : 'none';\n}<\/p>\n<p>\/\/ ============================================================\n\/\/ EXCEL GENERATION (SheetJS)\n\/\/ ============================================================\nfunction downloadXlsx() {\n  const p = window._params;\n  const c = window._calc;\n  if (!p || !c) { alert('Recalcule primeiro alterando qualquer input.'); return; }<\/p>\n<p>  const jurosConstrucaoCarencia = c.years\n    .filter(y => y.year <= p.carencia)\n    .reduce((sum, y) => sum + y.juros, 0);<\/p>\n<p>  const wb = XLSX.utils.book_new();<\/p>\n<p>  \/\/ Estilos SFAI Portugal\n  const sfaiBanner = {\n    fill: { fgColor: { rgb: '4B384C' } },\n    font: { bold: true, color: { rgb: 'FFFFFF' }, sz: 16, name: 'Calibri' },\n    alignment: { horizontal: 'center', vertical: 'center' }\n  };\n  const sfaiSubtitle = {\n    fill: { fgColor: { rgb: 'D7C5CD' } },\n    font: { italic: true, color: { rgb: '4B384C' }, sz: 10 },\n    alignment: { horizontal: 'center' }\n  };\n  const sfaiSection = {\n    fill: { fgColor: { rgb: '4B384C' } },\n    font: { bold: true, color: { rgb: 'FFFFFF' }, sz: 11 },\n    alignment: { horizontal: 'left', vertical: 'center' }\n  };\n  const sfaiTableHeader = {\n    fill: { fgColor: { rgb: 'AF95A6' } },\n    font: { bold: true, color: { rgb: 'FFFFFF' }, sz: 10 },\n    alignment: { horizontal: 'center', vertical: 'center', wrapText: true }\n  };\n  const sfaiTotal = {\n    fill: { fgColor: { rgb: 'D7C5CD' } },\n    font: { bold: true, color: { rgb: '4B384C' } }\n  };\n  const sfaiInputCell = { fill: { fgColor: { rgb: 'FFFCDC' } } };\n  const sfaiBorder = { style: 'thin', color: { rgb: 'AF95A6' } };<\/p>\n<p>  function styleCell(sheet, addr, style) {\n    if (!sheet[addr]) sheet[addr] = { v: '', t: 's' };\n    sheet[addr].s = Object.assign({}, sheet[addr].s || {}, style);\n  }\n  function styleRange(sheet, range, style) {\n    const dec = XLSX.utils.decode_range(range);\n    for (let r = dec.s.r; r <= dec.e.r; r++) {\n      for (let c = dec.s.c; c <= dec.e.c; c++) {\n        const addr = XLSX.utils.encode_cell({ r, c });\n        styleCell(sheet, addr, style);\n      }\n    }\n  }\n\n\n  \/\/ ==== Sheet 1: Estudo Cen\u00e1rios ====\n  const s1 = {};\n  function set(addr, value, opts={}) {\n    s1[addr] = { v: value, t: opts.t || (typeof value === 'number' ? 'n' : 's'), ...(opts.f ? {f: opts.f} : {}), ...(opts.z ? {z: opts.z} : {}), ...(opts.s ? {s: opts.s} : {}) };\n  }\n\n  set('A1', 'SFAI PORTUGAL  \u00b7  SIMULADOR IHRU 1.\u00ba DIREITO', {s: sfaiBanner});\n  if (!s1['!merges']) s1['!merges'] = [];\n  s1['!merges'].push({ s: {r:0, c:0}, e: {r:0, c:10} });\n  if (!s1['!rows']) s1['!rows'] = [];\n  s1['!rows'][0] = { hpt: 28 };\n  set('B2', 'ESTUDO POR CEN\u00c1RIO: AN\u00c1LISE POR DESCONTO DE RENDAS', {s: sfaiSection});\n  set('B3', 'Investimento \u00fanico, financiamento IHRU livre, cen\u00e1rios definidos por descontos sobre rendas');\n\n  set('B5', '1. PRESSUPOSTOS');\n\n  \/\/ Tipologia\n  set('B7', 'Tipologia'); set('C7', 'Renda Mensal (\u20ac)'); set('D7', 'Quantidade Fogos');\n  ['T1','T2','T3','T4'].forEach((t, i) => {\n    const r = 8 + i;\n    set('B'+r, t);\n    set('C'+r, p.rendas[i], {z:'#,##0.00'});\n    set('D'+r, p.fogos[i], {z:'#,##0'});\n  });\n  set('B12', 'TOTAL');\n  set('D12', c.totalFogos, {f:'SUM(D8:D11)', z:'#,##0'});<\/p>\n<p>  \/\/ Par\u00e2metros\n  set('B14', 'Par\u00e2metros de Investimento e Financiamento');\n  set('B15', 'CAPEX Total (\u20ac)'); set('C15', p.capex, {z:'#,##0.00'});\n  set('B16', 'Comparticipa\u00e7\u00e3o IHRU (%)'); set('C16', p.ihruPct, {z:'0.0%'});\n  set('B17', 'Comparticipa\u00e7\u00e3o IHRU (\u20ac)'); set('C17', c.ihruEur, {f:'C15*C16', z:'#,##0.00'});\n  set('B18', 'Financiamento CM (%)'); set('C18', 1 - p.ihruPct, {f:'1-C16', z:'0.0%'});\n  set('B19', 'Financiamento CM (\u20ac)'); set('C19', c.finCM, {f:'C15-C17', z:'#,##0.00'});\n  set('B20', 'Prazo do empr\u00e9stimo (anos)'); set('C20', p.prazo);\n  set('B21', 'Per\u00edodo de car\u00eancia (anos)'); set('C21', p.carencia);\n  set('B22', 'Spread'); set('C22', p.spread, {z:'0.000%'});\n  set('B23', 'Euribor 12M (refer\u00eancia)'); set('C23', p.euribor, {z:'0.000%'});\n  set('B24', 'Taxa de juro total'); set('C24', c.taxaJuro, {f:'C22+C23', z:'0.000%'});\n  set('B25', 'Infla\u00e7\u00e3o'); set('C25', p.inflacao, {z:'0.0%'});\n  set('B26', 'Atualiza\u00e7\u00e3o rendas (anual)'); set('C26', p.atualizacao, {z:'0.000%'});\n  set('B27', 'OPEX anual (\u20ac)'); set('C27', p.opex, {z:'#,##0.00'});\n  set('B28', 'Anos de garantia da obra (OPEX reduzido)'); set('C28', p.anosGarantia);\n  set('B29', 'Taxa de incobr\u00e1veis (rendas n\u00e3o cobradas)'); set('C29', p.incobraveis, {z:'0.0%'});\n  set('B30', 'Ponderador OPEX durante garantia (% CM)'); set('C30', p.ponderador, {z:'0.0%'});\n  set('B31', 'M\u00e9todo de amortiza\u00e7\u00e3o (constante \/ linear)'); set('C31', p.metodoAmort);<\/p>\n<p>  set('B32', 'Prazos de Obra e In\u00edcio de Opera\u00e7\u00e3o');\n  set('B33', 'Prazo de obra (meses)'); set('C33', p.prazoObra);\n  set('B34', 'In\u00edcio cobran\u00e7a rendas (meses ap\u00f3s obra)'); set('C34', p.inicioCobranca);\n  set('B35', '1.\u00aa renda no ano (fra\u00e7\u00e3o)'); set('C35', (p.prazoObra + p.inicioCobranca)\/12, {f:'(C33+C34)\/12', z:'0.00'});<\/p>\n<p>  set('B37', 'Cen\u00e1rios de Desconto nas Rendas');\n  ['Cen\u00e1rio 1: 0% (renda integral)', 'Cen\u00e1rio 2: 10% de desconto', 'Cen\u00e1rio 3: 20% de desconto', 'Cen\u00e1rio 4: 30% de desconto'].forEach((label, i) => {\n    set('B'+(38+i), label);\n    set('C'+(38+i), p.descontos[i], {z:'0.0%'});\n  });<\/p>\n<p>  \/\/ Rendas Mensais\n  set('B43', 'Rendas Mensais por Tipologia (Portaria 53\/2024)');\n  set('B44', 'Tipologia'); set('C44', 'Renda Mensal (\u20ac)'); set('D44', 'Qtd Fogos');\n  set('E44', 'Receita Mensal Bruta (\u20ac)'); set('F44', 'Receita Anual Bruta (\u20ac)');\n  ['T1','T2','T3','T4'].forEach((t, i) => {\n    const r = 45 + i;\n    set('B'+r, t);\n    set('C'+r, p.rendas[i], {z:'#,##0.00'});\n    set('D'+r, p.fogos[i], {f:'D'+(8+i), z:'#,##0'});\n    set('E'+r, p.rendas[i] * p.fogos[i], {f:'C'+r+'*D'+r, z:'#,##0.00'});\n    set('F'+r, p.rendas[i] * p.fogos[i] * 12, {f:'E'+r+'*12', z:'#,##0.00'});\n  });\n  set('B49', 'TOTAL');\n  set('D49', c.totalFogos, {f:'SUM(D45:D48)', z:'#,##0'});\n  set('E49', c.receitaAnualBruta\/12, {f:'SUM(E45:E48)', z:'#,##0.00'});\n  set('F49', c.receitaAnualBruta, {f:'SUM(F45:F48)', z:'#,##0.00'});<\/p>\n<p>  \/\/ 2. An\u00e1lise por cen\u00e1rio\n  set('B51', '2. AN\u00c1LISE POR CEN\u00c1RIO');\n  ['Cen\u00e1rio 1','Cen\u00e1rio 2','Cen\u00e1rio 3','Cen\u00e1rio 4'].forEach((s, i) => {\n    set(String.fromCharCode(68+i)+'53', s);\n  });\n  for (let i = 0; i < 4; i++) {\n    const col = String.fromCharCode(68+i);\n    set('C54', 'CAPEX Total (\u20ac)');\n    set(col+'54', p.capex, {f:'$C$15', z:'#,##0.00'});\n    set('C55', 'Comparticipa\u00e7\u00e3o IHRU (\u20ac)');\n    set(col+'55', c.ihruEur, {f:'$C$17', z:'#,##0.00'});\n    set('C56', 'Financiamento CM (\u20ac)');\n    set(col+'56', c.finCM, {f:'$C$19', z:'#,##0.00'});\n    set('C57', 'OPEX Anual (\u20ac)');\n    set(col+'57', p.opex, {f:'$C$27', z:'#,##0.00'});\n    set('C58', 'Financiamento CM + OPEX anual nominal');\n    set(col+'58', c.finCM + p.opex, {f:'$C$19+$C$27', z:'#,##0.00'});\n    set('C59', '% Desconto nas Rendas');\n    set(col+'59', p.descontos[i], {f:'$C$'+(38+i), z:'0.0%'});\n    set('C60', 'Receita Anual Bruta c\/ Desconto (\u20ac)');\n    set(col+'60', c.totais[i].receitaAnualBaseDescontada, {f:'$F$49*(1-'+col+'59)', z:'#,##0.00'});\n  }\n\n  \/\/ 3. Servi\u00e7o da d\u00edvida\n  set('B62', '3. SERVI\u00c7O DE D\u00cdVIDA (Financiamento CM)');\n  set('B63', 'Id\u00eantico para todos os cen\u00e1rios (CAPEX e financiamento s\u00e3o os mesmos)');\n  set('C65', 'Capital em d\u00edvida (Financiamento CM)');\n  set('D65', c.finCM, {f:'$C$19', z:'#,##0.00'});\n  set('C66', 'Juros anuais (com capital totalmente libertado)');\n  set('D66', c.finCM * c.taxaJuro, {f:'D65*$C$24', z:'#,##0.00'});\n  set('C67', 'Total juros constru\u00e7\u00e3o + car\u00eancia (calc. ano a ano)');\n  set('D67', jurosConstrucaoCarencia, {f:\"SUMIFS('Evolu\u00e7\u00e3o Tesouraria'!$D$7:$D$56,'Evolu\u00e7\u00e3o Tesouraria'!$B$7:$B$56,\\\"<=\\\"&#038;$C$21)\", z:'#,##0.00'});\n  set('C68', p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o 1.\u00ba ano (linear, decresce)' : 'Presta\u00e7\u00e3o anual (ap\u00f3s car\u00eancia)');\n  set('D68', c.prestacao1Ano, {f:'IF($C$31=\"linear\",D65\/($C$20-$C$21)+D65*$C$24,-PMT($C$24,$C$20-$C$21,D65))', z:'#,##0.00'});\n  set('C69', '   componente capital (1.\u00aa presta\u00e7\u00e3o)');\n  set('D69', c.componenteCapital1, {f:'IF($C$31=\"linear\",D65\/($C$20-$C$21),-PPMT($C$24,1,$C$20-$C$21,D65))', z:'#,##0.00'});\n  set('C70', '   componente juros (1.\u00aa presta\u00e7\u00e3o)');\n  set('D70', c.componenteJuros1, {f:'-IPMT($C$24,1,$C$20-$C$21,D65)', z:'#,##0.00'});\n  set('C71', 'Total juros pagos (vida do empr\u00e9stimo)');\n  set('D71', c.totalJurosVida, {f:\"SUM('Evolu\u00e7\u00e3o Tesouraria'!$D$7:$D$56)\", z:'#,##0.00'});\n  set('C72', 'Custo total do financiamento (capital + juros)');\n  set('D72', c.custoTotalFin, {f:'D65+D71', z:'#,##0.00'});\n\n  \/\/ 4. Indicadores-chave\n  set('B74', '4. INDICADORES-CHAVE POR CEN\u00c1RIO');\n  ['Cen\u00e1rio 1','Cen\u00e1rio 2','Cen\u00e1rio 3','Cen\u00e1rio 4'].forEach((s, i) => {\n    set(String.fromCharCode(68+i)+'76', s);\n  });\n  const indicators_ = [\n    ['CAPEX por fogo (\u20ac)', '$C$15\/$D$12', () => p.capex\/c.totalFogos],\n    ['Financiamento CM por fogo (\u20ac)', '$C$19\/$D$12', () => c.finCM\/c.totalFogos],\n    ['Poupan\u00e7a CM por fogo (vs 100% CAPEX)', '$C$17\/$D$12', () => c.ihruEur\/c.totalFogos],\n    [(p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o mensal por fogo (1.\u00ba ano)' : 'Presta\u00e7\u00e3o mensal por fogo (\u20ac)'), '$D$68\/12\/$D$12', () => c.prestacao1Ano\/12\/c.totalFogos],\n    [(p.metodoAmort === 'linear' ? 'Presta\u00e7\u00e3o mensal total CM (1.\u00ba ano)' : 'Presta\u00e7\u00e3o mensal total CM (\u20ac)'), '$D$68\/12', () => c.prestacao1Ano\/12],\n    ['Encargo anual total CM (Presta\u00e7\u00e3o + OPEX)', '$D$68+$C$27', () => c.prestacao1Ano + p.opex],\n    ['Receita Anual Bruta c\/ Desconto (\u20ac)', null, (i) => c.totais[i].receitaAnualBaseDescontada],\n    ['Receita Anual L\u00edquida (ap\u00f3s incob.)', null, (i) => c.totais[i].receitaLiquidaAnual],\n    ['Margem Anual (Rec. L\u00edq. - Encargo)', null, (i) => c.totais[i].margemAnual]\n  ];\n  indicators_.forEach(([label, formula, fn], idx) => {\n    const r = 77 + idx;\n    set('C'+r, label);\n    for (let i = 0; i < 4; i++) {\n      const col = String.fromCharCode(68+i);\n      const val = fn(i);\n      if (formula) {\n        set(col+r, val, {f: formula, z:'#,##0.00'});\n      } else if (label.startsWith('Receita Anual Bruta')) {\n        set(col+r, val, {f: col+'60', z:'#,##0.00'});\n      } else if (label.startsWith('Receita Anual L\u00edquida')) {\n        set(col+r, val, {f: col+'60*(1-$C$29)', z:'#,##0.00'});\n      } else if (label.startsWith('Margem')) {\n        set(col+r, val, {f: col+(r-1)+'-($D$68+$C$27)', z:'#,##0.00'});\n      }\n    }\n  });\n\n  \/\/ 5. Compara\u00e7\u00e3o\n  set('B87', '5. COMPARA\u00c7\u00c3O ENTRE CEN\u00c1RIOS (refer\u00eancia: Cen\u00e1rio 1)');\n  ['Cen\u00e1rio 1','Cen\u00e1rio 2','Cen\u00e1rio 3','Cen\u00e1rio 4'].forEach((s, i) => {\n    set(String.fromCharCode(68+i)+'89', s);\n  });\n  const compRows_ = [\n    ['Receita Anual Bruta c\/ Desconto (\u20ac)', (col) => col+'60', (i) => c.totais[i].receitaAnualBaseDescontada, '#,##0.00'],\n    ['Diferen\u00e7a vs Cen\u00e1rio 1 (\u20ac\/ano)', (col) => col+'60-$D$60', (i) => c.totais[i].receitaAnualBaseDescontada - c.totais[0].receitaAnualBaseDescontada, '#,##0.00'],\n    ['Diferen\u00e7a % vs Cen\u00e1rio 1', (col) => 'IFERROR(('+col+'60-$D$60)\/$D$60,0)', (i) => c.totais[0].receitaAnualBaseDescontada !== 0 ? (c.totais[i].receitaAnualBaseDescontada - c.totais[0].receitaAnualBaseDescontada)\/c.totais[0].receitaAnualBaseDescontada : 0, '0.0%'],\n    ['Margem Anual (Rec. L\u00edq. - Encargo) (\u20ac)', (col) => col+'85', (i) => c.totais[i].margemAnual, '#,##0.00'],\n    ['Diferen\u00e7a Margem vs Cen\u00e1rio 1 (\u20ac\/ano)', (col) => col+'85-$D$85', (i) => c.totais[i].margemAnual - c.totais[0].margemAnual, '#,##0.00']\n  ];\n  compRows_.forEach(([label, fnFormula, fn, z], idx) => {\n    const r = 90 + idx;\n    set('C'+r, label);\n    for (let i = 0; i < 4; i++) {\n      const col = String.fromCharCode(68+i);\n      set(col+r, fn(i), {f: fnFormula(col), z});\n    }\n  });\n\n  \/\/ 6. Resumo tesouraria\n  set('B96', '6. RESUMO DE RESULTADOS: EVOLU\u00c7\u00c3O DA TESOURARIA');\n  ['CEN\u00c1RIO 1', 'CEN\u00c1RIO 2', 'CEN\u00c1RIO 3', 'CEN\u00c1RIO 4'].forEach((s, i) => {\n    set(['D','F','H','J'][i] + '98', s);\n  });\n  set('C99', 'Per\u00edodo');\n  for (let i = 0; i < 4; i++) {\n    set(['D','F','H','J'][i] + '99', 'Receita L\u00edquida');\n    set(['E','G','I','K'][i] + '99', 'Fluxo L\u00edquido');\n  }\n  for (let y = 0; y < 10; y++) {\n    const r = 100 + y;\n    set('C'+r, y+1);\n    for (let i = 0; i < 4; i++) {\n      const sc = c.years[y].scenarios[i];\n      const recCol = ['D','F','H','J'][i];\n      const fluxCol = ['E','G','I','K'][i];\n      const srcRow = 7 + y;  \/\/ Year 1 at row 7 in Evolu\u00e7\u00e3o Tesouraria\n      const recSrcCol = ['M','R','W','AB'][i];\n      const fluxSrcCol = ['N','S','X','AC'][i];\n      set(recCol+r, sc.receitaLiquida, {f: \"'Evolu\u00e7\u00e3o Tesouraria'!\"+recSrcCol+srcRow, z:'#,##0'});\n      set(fluxCol+r, sc.fluxoLiquido, {f: \"'Evolu\u00e7\u00e3o Tesouraria'!\"+fluxSrcCol+srcRow, z:'#,##0'});\n    }\n  }\n  \/\/ Subtotal anos 1-10\n  set('C110', 'Subtotal anos 1-10');\n  for (let i = 0; i < 4; i++) {\n    const recCol = ['D','F','H','J'][i];\n    const fluxCol = ['E','G','I','K'][i];\n    set(recCol+'110', c.totais[i].receitaLiquida10, {f:'SUM('+recCol+'100:'+recCol+'109)', z:'#,##0'});\n    set(fluxCol+'110', c.totais[i].fluxoLiquido10, {f:'SUM('+fluxCol+'100:'+fluxCol+'109)', z:'#,##0'});\n  }\n  \/\/ Anos 11-50\n  set('C111', 'Anos 11-50 (40 anos)');\n  for (let i = 0; i < 4; i++) {\n    const recCol = ['D','F','H','J'][i];\n    const fluxCol = ['E','G','I','K'][i];\n    const recSrcCol = ['M','R','W','AB'][i];\n    const fluxSrcCol = ['N','S','X','AC'][i];\n    set(recCol+'111', c.totais[i].receitaLiquida50 - c.totais[i].receitaLiquida10, {f:\"SUM('Evolu\u00e7\u00e3o Tesouraria'!\"+recSrcCol+\"17:\"+recSrcCol+\"56)\", z:'#,##0'});\n    set(fluxCol+'111', c.totais[i].fluxoLiquido50 - c.totais[i].fluxoLiquido10, {f:\"SUM('Evolu\u00e7\u00e3o Tesouraria'!\"+fluxSrcCol+\"17:\"+fluxSrcCol+\"56)\", z:'#,##0'});\n  }\n  \/\/ Total 50 anos\n  set('C112', 'TOTAL 50 ANOS');\n  for (let i = 0; i < 4; i++) {\n    const recCol = ['D','F','H','J'][i];\n    const fluxCol = ['E','G','I','K'][i];\n    set(recCol+'112', c.totais[i].receitaLiquida50, {f: recCol+'110+'+recCol+'111', z:'#,##0'});\n    set(fluxCol+'112', c.totais[i].fluxoLiquido50, {f: fluxCol+'110+'+fluxCol+'111', z:'#,##0'});\n  }\n\n  s1['!ref'] = 'A1:L115';\n  s1['!cols'] = [{wch:3},{wch:38},{wch:48},{wch:18},{wch:18},{wch:18},{wch:18},{wch:18},{wch:18},{wch:18},{wch:18},{wch:18}];\n  \/\/ Estilizar sec\u00e7\u00f5es e cabe\u00e7alhos em SFAI brand\n  ['B5','B14','B32','B37','B43','B51','B62','B74'].forEach(addr => styleCell(s1, addr, sfaiSection));\n  \/\/ Footer com identifica\u00e7\u00e3o SFAI\n  const lastRow = 90;\n  set('B' + lastRow, 'Documento gerado pelo simulador SFAI Portugal \u00b7 www.sfai-portugal.pt \u00b7 ruicoutinho@sfai.pt \u00b7 938 890 429', {s: sfaiSubtitle});\n  s1['!merges'].push({ s: {r: lastRow-1, c: 1}, e: {r: lastRow-1, c: 10} });\n  s1['!rows'][lastRow-1] = { hpt: 16 };\n  s1['!ref'] = 'A1:K' + lastRow;<\/p>\n<p>  XLSX.utils.book_append_sheet(wb, s1, 'Estudo Cen\u00e1rios');<\/p>\n<p>  \/\/ ==== Sheet 2: Amortiza\u00e7\u00f5es Extra ====\n  const s2 = {};\n  s2['B2'] = { v: 'AMORTIZA\u00c7\u00d5ES EXTRAORDIN\u00c1RIAS DO EMPR\u00c9STIMO', t: 's' };\n  s2['B3'] = { v: 'Aplicam-se igualmente aos 4 cen\u00e1rios (financiamento \u00fanico). Edite os valores aqui para recalcular.', t: 's' };\n  s2['B5'] = {v:'#',t:'s'}; s2['C5'] = {v:'Cen\u00e1rio Aplic\u00e1vel',t:'s'};\n  s2['D5'] = {v:'Ano',t:'s'}; s2['E5'] = {v:'Valor Amortiza\u00e7\u00e3o (\u20ac)',t:'s'};\n  for (let i = 0; i < 20; i++) {\n    s2['B'+(6+i)] = { v: i+1, t: 'n' };\n    if (i < p.amortizacoesExtra.length) {\n      const a = p.amortizacoesExtra[i];\n      s2['C'+(6+i)] = { v: 'Todos', t: 's' };\n      s2['D'+(6+i)] = { v: a.year, t: 'n', z: '0' };\n      s2['E'+(6+i)] = { v: a.value, t: 'n', z: '#,##0.00' };\n    }\n  }\n  s2['B27'] = {v:'RESUMO',t:'s'};\n  s2['C28'] = {v:'Total Amortiza\u00e7\u00f5es',t:'s'};\n  s2['D28'] = {v:'N.\u00ba',t:'s'};\n  s2['E28'] = {v:'Total (\u20ac)',t:'s'};\n  s2['D29'] = {v: p.amortizacoesExtra.length, t:'n', f:'COUNTA(E6:E25)', z:'#,##0'};\n  s2['E29'] = {v: p.amortizacoesExtra.reduce((s, a) => s + a.value, 0), t:'n', f:'SUM(E6:E25)', z:'#,##0.00'};\n  s2['!ref'] = 'A1:E30';\n  s2['!cols'] = [{wch:3},{wch:6},{wch:28},{wch:14},{wch:22}];\n  \/\/ SFAI styling Amortiza\u00e7\u00f5es Extra\n  s2['A1'] = { v: 'SFAI PORTUGAL  \u00b7  AMORTIZA\u00c7\u00d5ES EXTRAORDIN\u00c1RIAS', t: 's', s: sfaiBanner };\n  if (!s2['!merges']) s2['!merges'] = [];\n  s2['!merges'].push({ s: {r:0, c:0}, e: {r:0, c:6} });\n  if (!s2['!rows']) s2['!rows'] = [];\n  s2['!rows'][0] = { hpt: 26 };<\/p>\n<p>  XLSX.utils.book_append_sheet(wb, s2, 'Amortiza\u00e7\u00f5es Extra');<\/p>\n<p>  \/\/ ==== Sheet 3: Evolu\u00e7\u00e3o Tesouraria ====\n  const s3 = {};\n  s3['B2'] = { v: 'EVOLU\u00c7\u00c3O DA TESOURARIA E VALOR L\u00cdQUIDO: FINANCIAMENTO CM', t: 's' };\n  s3['B3'] = { v: 'Empr\u00e9stimo IHRU 1.\u00ba Direito: id\u00eantico para todos os cen\u00e1rios', t: 's' };<\/p>\n<p>  s3['C4'] = { v: 'SERVI\u00c7O DE D\u00cdVIDA + OPEX (id\u00eantico aos 4 cen\u00e1rios)', t: 's' };\n  ['CEN\u00c1RIO 1','CEN\u00c1RIO 2','CEN\u00c1RIO 3','CEN\u00c1RIO 4'].forEach((name, i) => {\n    const baseCol = XLSX.utils.encode_col(10 + i*5);  \/\/ K=10\n    s3[baseCol+'4'] = { v: name, t: 's' };\n  });<\/p>\n<p>  \/\/ Headers row 5\n  s3['B5'] = { v: 'Ano', t: 's' };\n  ['Saldo D\u00edvida Inicial','Juros','Amortiza\u00e7\u00e3o Capital','Presta\u00e7\u00e3o Total','OPEX Anual','Encargo Total Anual','Encargo Acumulado','Saldo D\u00edvida Final'].forEach((h, i) => {\n    s3[XLSX.utils.encode_col(2+i)+'5'] = { v: h, t: 's' };\n  });\n  for (let i = 0; i < 4; i++) {\n    const baseColIdx = 10 + i*5;\n    ['Receita Bruta Anual','Incobr\u00e1veis (1,5%)','Receita L\u00edquida Anual','Fluxo Caixa L\u00edquido','Fluxo Acumulado'].forEach((h, j) => {\n      s3[XLSX.utils.encode_col(baseColIdx+j)+'5'] = { v: h, t: 's' };\n    });\n  }<\/p>\n<p>  \/\/ Year 0 row\n  s3['B6'] = { v: 0, t: 'n' };\n  for (let i = 2; i < 9; i++) {\n    s3[XLSX.utils.encode_col(i)+'6'] = { v: 0, t: 'n', z: '#,##0.00' };\n  }\n  s3['J6'] = { v: c.finCM, t: 'n', f: \"'Estudo Cen\u00e1rios'!$C$19\", z: '#,##0.00' };\n  for (let i = 0; i < 4; i++) {\n    const baseColIdx = 10 + i*5;\n    for (let j = 0; j < 5; j++) {\n      s3[XLSX.utils.encode_col(baseColIdx+j)+'6'] = { v: 0, t: 'n', z: '#,##0.00' };\n    }\n  }\n\n  \/\/ Years 1-50 (rows 7-56)\n  c.years.forEach(y => {\n    const r = 6 + y.year;\n    s3['B'+r] = { v: y.year, t: 'n' };<\/p>\n<p>    \/\/ Loan service\n    s3['C'+r] = { v: y.saldoInicial, t: 'n', f: 'IF($B'+r+'=1,0,J'+(r-1)+')', z: '#,##0.00' };\n    s3['D'+r] = { v: y.juros, t: 'n', f: \"IF(($B\"+r+\"-1)*12<'Estudo Cen\u00e1rios'!$C$33,(C\"+r+\"+J\"+r+\")\/2*'Estudo Cen\u00e1rios'!$C$24,C\"+r+\"*'Estudo Cen\u00e1rios'!$C$24)\", z:'#,##0.00' };\n    \/\/ Amortiza\u00e7\u00e3o capped at saldoInicial (avoids phantom amortization after loan ends)\n    s3['E'+r] = { v: y.amortizacao, t: 'n', f: \"IF(OR($B\"+r+\"<='Estudo Cen\u00e1rios'!$C$21,($B\"+r+\"-1)*12<'Estudo Cen\u00e1rios'!$C$33),0,IF('Estudo Cen\u00e1rios'!$C$31=\\\"linear\\\",MIN(C\"+r+\",'Estudo Cen\u00e1rios'!$C$19\/('Estudo Cen\u00e1rios'!$C$20-'Estudo Cen\u00e1rios'!$C$21)),MIN(C\"+r+\",MAX(0,-PMT('Estudo Cen\u00e1rios'!$C$24,'Estudo Cen\u00e1rios'!$C$20-'Estudo Cen\u00e1rios'!$C$21,'Estudo Cen\u00e1rios'!$C$19)-D\"+r+\"))))\", z:'#,##0.00' };\n    s3['F'+r] = { v: y.prestacao, t: 'n', f: 'D'+r+'+E'+r, z:'#,##0.00' };\n    s3['G'+r] = { v: y.opex, t: 'n', f: \"'Estudo Cen\u00e1rios'!$C$27*(1+'Estudo Cen\u00e1rios'!$C$25)^($B\"+r+\"-1)*IF($B\"+r+\"<='Estudo Cen\u00e1rios'!$C$28,'Estudo Cen\u00e1rios'!$C$30,1)\", z:'#,##0.00' };\n    \/\/ Encargo Total includes ordinary service + OPEX + extra amortizations (capped at remaining balance)\n    s3['H'+r] = { v: y.encargo, t: 'n', f: \"F\"+r+\"+G\"+r+\"+MIN(MAX(0,C\"+r+\"-E\"+r+\"),SUMIFS('Amortiza\u00e7\u00f5es Extra'!$E$6:$E$25,'Amortiza\u00e7\u00f5es Extra'!$D$6:$D$25,$B\"+r+\"))\", z:'#,##0.00' };\n    s3['I'+r] = { v: y.encargoAcumulado, t: 'n', f: 'I'+(r-1)+'+H'+r, z:'#,##0.00' };\n    s3['J'+r] = { v: y.saldoFinal, t: 'n', f: \"IF(($B\"+r+\"-1)*12<'Estudo Cen\u00e1rios'!$C$33,MIN($J$6,$J$6*$B\"+r+\"*12\/'Estudo Cen\u00e1rios'!$C$33),MAX(0,C\"+r+\"-E\"+r+\"-MIN(MAX(0,C\"+r+\"-E\"+r+\"),SUMIFS('Amortiza\u00e7\u00f5es Extra'!$E$6:$E$25,'Amortiza\u00e7\u00f5es Extra'!$D$6:$D$25,$B\"+r+\"))))\", z:'#,##0.00' };\n\n    \/\/ Per scenario\n    for (let i = 0; i < 4; i++) {\n      const baseColIdx = 10 + i*5;\n      const K = XLSX.utils.encode_col(baseColIdx);\n      const L = XLSX.utils.encode_col(baseColIdx+1);\n      const M = XLSX.utils.encode_col(baseColIdx+2);\n      const N = XLSX.utils.encode_col(baseColIdx+3);\n      const O = XLSX.utils.encode_col(baseColIdx+4);\n      const sc = y.scenarios[i];\n      const discRef = \"'Estudo Cen\u00e1rios'!$C$\"+(38+i);\n\n      s3[K+r] = { v: sc.receitaBruta, t: 'n',\n        f: \"('Estudo Cen\u00e1rios'!$F$49*(1+'Estudo Cen\u00e1rios'!$C$26)^($B\"+r+\"-1))*MAX(0,MIN($B\"+r+\"*12,600)-MAX(($B\"+r+\"-1)*12,'Estudo Cen\u00e1rios'!$C$33+'Estudo Cen\u00e1rios'!$C$34))\/12*(1-\"+discRef+\")\",\n        z:'#,##0.00' };\n      s3[L+r] = { v: sc.incobraveis, t: 'n', f: K+r+\"*'Estudo Cen\u00e1rios'!$C$29\", z:'#,##0.00' };\n      s3[M+r] = { v: sc.receitaLiquida, t: 'n', f: K+r+'-'+L+r, z:'#,##0.00' };\n      s3[N+r] = { v: sc.fluxoLiquido, t: 'n', f: M+r+'-H'+r, z:'#,##0.00' };\n      s3[O+r] = { v: sc.fluxoAcumulado, t: 'n', f: O+(r-1)+'+'+N+r, z:'#,##0.00' };\n    }\n  });\n\n  \/\/ Total row 57\n  s3['B57'] = { v: 'TOTAL', t: 's' };\n  ['D','E','F','G','H'].forEach(col => {\n    let sum = 0;\n    for (let yr = 1; yr <= 50; yr++) {\n      sum += s3[col+(6+yr)].v;\n    }\n    s3[col+'57'] = { v: sum, t: 'n', f: 'SUM('+col+'7:'+col+'56)', z: '#,##0.00' };\n  });\n  for (let i = 0; i < 4; i++) {\n    const baseColIdx = 10 + i*5;\n    for (let j = 0; j < 4; j++) {\n      const col = XLSX.utils.encode_col(baseColIdx+j);\n      let sum = 0;\n      for (let yr = 1; yr <= 50; yr++) {\n        sum += s3[col+(6+yr)].v;\n      }\n      s3[col+'57'] = { v: sum, t: 'n', f: 'SUM('+col+'7:'+col+'56)', z: '#,##0.00' };\n    }\n    const oCol = XLSX.utils.encode_col(baseColIdx+4);\n    s3[oCol+'57'] = { v: c.totais[i].fluxoLiquido50, t: 'n', f: oCol+'56', z: '#,##0.00' };\n  }\n\n  s3['!ref'] = 'A1:AE57';\n  const cols3 = [{wch:12},{wch:6}];\n  for (let i = 0; i < 28; i++) cols3.push({wch:14});\n  s3['!cols'] = cols3;\n  \/\/ SFAI brand styling on Evolu\u00e7\u00e3o Tesouraria\n  s3['A1'] = { v: 'SFAI PORTUGAL  \u00b7  EVOLU\u00c7\u00c3O DETALHADA DA TESOURARIA', t: 's', s: sfaiBanner };\n  if (!s3['!merges']) s3['!merges'] = [];\n  s3['!merges'].push({ s: {r:0, c:0}, e: {r:0, c:30} });\n  if (!s3['!rows']) s3['!rows'] = [];\n  s3['!rows'][0] = { hpt: 26 };\n  \/\/ Style the row 5 headers (column titles)\n  styleRange(s3, 'B5:AE5', sfaiTableHeader);\n  \/\/ Style total row 57\n  styleRange(s3, 'B57:AE57', sfaiTotal);\n  \/\/ Style scenario header row 4\n  styleRange(s3, 'C4:AE4', sfaiSection);\n  s3['!ref'] = 'A1:AE57';\n\n  XLSX.utils.book_append_sheet(wb, s3, 'Evolu\u00e7\u00e3o Tesouraria');\n\n  \/\/ ==== Sheet 4: Nota Explicativa ====\n  const s4 = {};\n  const notaTexts = [\n    [2, 'NOTA EXPLICATIVA: L\u00f3gica do Simulador'],\n    [4, 'Este simulador analisa um \u00fanico investimento habitacional no qual os 4 cen\u00e1rios se distinguem apenas pelo desconto aplicado \u00e0s rendas (Portaria 53\/2024). Como o CAPEX, a percentagem de financiamento IHRU e os encargos operacionais s\u00e3o iguais nos 4 cen\u00e1rios, o servi\u00e7o da d\u00edvida \u00e9 id\u00eantico para todos. A diverg\u00eancia surge apenas do lado das receitas anuais.'],\n    [6, 'Inputs principais:'],\n    [7, '  CAPEX Total: valor total do investimento.'],\n    [8, '  Comparticipa\u00e7\u00e3o IHRU (%): livremente escolhida, sem teto m\u00e1ximo.'],\n    [9, '  Cen\u00e1rios de desconto: 4 percentagens sobre as rendas Portaria 53\/2024.'],\n    [10, '  Quantidade de fogos por tipologia (T1 a T4).'],\n    [11, '  Anos de garantia da obra: per\u00edodo em que o OPEX \u00e9 parcialmente coberto pela garantia da empreitada.'],\n    [12, '  Ponderador OPEX durante garantia: percentagem do OPEX suportada pela CM enquanto vigora a garantia.'],\n    [14, 'Fases do investimento:'],\n    [15, '  Constru\u00e7\u00e3o (anos 1 a 1,5): juros sobre saldo m\u00e9dio, capital libertado proporcionalmente, sem rendas.'],\n    [16, '  Car\u00eancia (anos 1 a 3): s\u00f3 juros, sem amortiza\u00e7\u00e3o, rendas iniciam ap\u00f3s obra.'],\n    [17, '  Garantia da obra: OPEX \u00d7 ponderador (CM 25% por defeito).'],\n    [18, '  P\u00f3s-garantia: amortiza\u00e7\u00e3o ' + (p.metodoAmort === 'linear' ? 'linear (capital constante por ano)' : 'pelo m\u00e9todo da presta\u00e7\u00e3o constante (anuidade, PMT)') + ', OPEX 100% com infla\u00e7\u00e3o, rendas integrais c\/desconto.']\n  ];\n  notaTexts.forEach(([r, txt]) => {\n    s4['B'+r] = { v: txt, t: 's' };\n  });\n  s4['!ref'] = 'A1:H20';\n  s4['!cols'] = [{wch:3},{wch:120}];\n  \/\/ SFAI styling Nota Explicativa\n  s4['A1'] = { v: 'SFAI PORTUGAL  \u00b7  NOTA EXPLICATIVA DA METODOLOGIA', t: 's', s: sfaiBanner };\n  if (!s4['!merges']) s4['!merges'] = [];\n  s4['!merges'].push({ s: {r:0, c:0}, e: {r:0, c:7} });\n  if (!s4['!rows']) s4['!rows'] = [];\n  s4['!rows'][0] = { hpt: 26 };\n  styleCell(s4, 'B4', sfaiSection);<\/p>\n<p>  XLSX.utils.book_append_sheet(wb, s4, 'Nota Explicativa');<\/p>\n<p>  \/\/ Write file\n  const today = new Date().toISOString().split('T')[0];\n  XLSX.writeFile(wb, 'Simulador_IHRU_'+today+'.xlsx');\n}<\/p>\n<p>\/\/ ============================================================\n\/\/ CHARTS (SVG nativo, sem depend\u00eancias)\n\/\/ ============================================================\nconst SCENARIO_COLORS = ['#4B384C', '#7B5E7C', '#AF95A6', '#C9A8B6'];\nconst SCENARIO_NAMES = ['Cen\u00e1rio 1 (0%)', 'Cen\u00e1rio 2 (10%)', 'Cen\u00e1rio 3 (20%)', 'Cen\u00e1rio 4 (30%)'];<\/p>\n<p>function fmtCompact(v) {\n  const abs = Math.abs(v);\n  if (abs >= 1e6) return (v \/ 1e6).toFixed(1).replace('.0', '') + 'M\u20ac';\n  if (abs >= 1e3) return Math.round(v \/ 1e3) + 'k\u20ac';\n  return Math.round(v) + '\u20ac';\n}<\/p>\n<p>function isMobileLayout() {\n  return window.matchMedia('(max-width: 600px)').matches;\n}<\/p>\n<p>\/\/ ---------- Chart 1: Fluxo anual primeiros anos (barras agrupadas) ----------\nfunction renderChartFluxoAnual(c) {\n  const container = document.getElementById('chartFluxoAnual');\n  const yearsToShow = 10;\n  const years = c.years.slice(0, yearsToShow);\n  const isMobile = isMobileLayout();\n  const W = isMobile ? 360 : 600;\n  const H = isMobile ? 280 : 320;\n  const margin = { top: 16, right: 14, bottom: 38, left: isMobile ? 44 : 56 };\n  const innerW = W - margin.left - margin.right;\n  const innerH = H - margin.top - margin.bottom;<\/p>\n<p>  \/\/ Get min\/max across all scenarios for these years\n  let minV = 0, maxV = 0;\n  years.forEach(y => y.scenarios.forEach(s => {\n    if (s.fluxoLiquido < minV) minV = s.fluxoLiquido;\n    if (s.fluxoLiquido > maxV) maxV = s.fluxoLiquido;\n  }));\n  if (maxV === 0 && minV === 0) maxV = 1;<\/p>\n<p>  \/\/ Add 10% padding\n  const range = maxV - minV;\n  minV -= range * 0.05;\n  maxV += range * 0.05;<\/p>\n<p>  const yScale = v => margin.top + innerH * (1 - (v - minV) \/ (maxV - minV));\n  const yZero = yScale(0);<\/p>\n<p>  const groupW = innerW \/ yearsToShow;\n  const barW = (groupW - 6) \/ 4;<\/p>\n<p>  \/\/ Y-axis ticks (4 grid lines)\n  const ticks = [];\n  const step = niceStep((maxV - minV) \/ 4);\n  let t = Math.ceil(minV \/ step) * step;\n  while (t <= maxV) { ticks.push(t); t += step; }\n\n  let svg = `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-label=\"Fluxo l\u00edquido anual primeiros 10 anos\">`;<\/p>\n<p>  \/\/ Grid + Y axis\n  ticks.forEach(tv => {\n    const y = yScale(tv);\n    svg += `<line class=\"grid-line\" x1=\"${margin.left}\" x2=\"${W - margin.right}\" y1=\"${y}\" y2=\"${y}\"\/>`;\n    svg += `<text class=\"axis-label\" x=\"${margin.left - 6}\" y=\"${y + 3}\" text-anchor=\"end\">${fmtCompact(tv)}<\/text>`;\n  });<\/p>\n<p>  \/\/ Zero line\n  svg += `<line class=\"axis-zero\" x1=\"${margin.left}\" x2=\"${W - margin.right}\" y1=\"${yZero}\" y2=\"${yZero}\"\/>`;<\/p>\n<p>  \/\/ Bars\n  years.forEach((yr, gi) => {\n    const groupX = margin.left + gi * groupW + 3;\n    yr.scenarios.forEach((sc, si) => {\n      const x = groupX + si * barW;\n      const v = sc.fluxoLiquido;\n      const bh = Math.abs(yScale(v) - yZero);\n      const by = v >= 0 ? yScale(v) : yZero;\n      svg += `<rect x=\"${x.toFixed(1)}\" y=\"${by.toFixed(1)}\" width=\"${(barW - 1).toFixed(1)}\" height=\"${bh.toFixed(1)}\" fill=\"${SCENARIO_COLORS[si]}\" opacity=\"0.9\">\n        <title>Ano ${yr.year}, ${SCENARIO_NAMES[si]}: ${eur(v)}<\/title>\n      <\/rect>`;\n    });<\/p>\n<p>    \/\/ X-axis label\n    const cx = groupX + 4 * barW \/ 2 - 1.5;\n    svg += `<text class=\"axis-label\" x=\"${cx}\" y=\"${H - margin.bottom + 14}\" text-anchor=\"middle\">${yr.year}<\/text>`;\n  });<\/p>\n<p>  \/\/ X axis title\n  svg += `<text class=\"axis-label\" x=\"${margin.left + innerW \/ 2}\" y=\"${H - 6}\" text-anchor=\"middle\" font-weight=\"bold\">Ano<\/text>`;<\/p>\n<p>  svg += `<\/svg>`;\n  container.innerHTML = svg;<\/p>\n<p>  \/\/ Legend\n  document.getElementById('legendFluxoAnual').innerHTML = SCENARIO_NAMES.map((n, i) =>\n    `<span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${SCENARIO_COLORS[i]}\"><\/span>${n}<\/span>`\n  ).join('');\n}<\/p>\n<p>function niceStep(raw) {\n  const exp = Math.floor(Math.log10(Math.abs(raw)));\n  const base = Math.pow(10, exp);\n  const norm = raw \/ base;\n  let nice;\n  if (norm < 1.5) nice = 1;\n  else if (norm < 3) nice = 2;\n  else if (norm < 7) nice = 5;\n  else nice = 10;\n  return nice * base;\n}\n\n\/\/ ---------- Chart 2: Fluxo acumulado 50 anos (linhas) ----------\nfunction renderChartFluxoAcumulado(c) {\n  const container = document.getElementById('chartFluxoAcumulado');\n  const isMobile = isMobileLayout();\n  const W = isMobile ? 360 : 600;\n  const H = isMobile ? 280 : 320;\n  const margin = { top: 16, right: 14, bottom: 38, left: isMobile ? 50 : 64 };\n  const innerW = W - margin.left - margin.right;\n  const innerH = H - margin.top - margin.bottom;\n\n  \/\/ Build series per scenario\n  const series = [0, 1, 2, 3].map(i => {\n    return [{ year: 0, value: 0 }, ...c.years.map(y => ({ year: y.year, value: y.scenarios[i].fluxoAcumulado }))];\n  });<\/p>\n<p>  let minV = 0, maxV = 0;\n  series.forEach(s => s.forEach(p => {\n    if (p.value < minV) minV = p.value;\n    if (p.value > maxV) maxV = p.value;\n  }));\n  const range = maxV - minV || 1;\n  minV -= range * 0.05;\n  maxV += range * 0.05;<\/p>\n<p>  const xScale = yr => margin.left + (yr \/ 50) * innerW;\n  const yScale = v => margin.top + innerH * (1 - (v - minV) \/ (maxV - minV));\n  const yZero = yScale(0);<\/p>\n<p>  const ticks = [];\n  const step = niceStep((maxV - minV) \/ 5);\n  let t = Math.ceil(minV \/ step) * step;\n  while (t <= maxV) { ticks.push(t); t += step; }\n\n  let svg = `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-label=\"Fluxo acumulado 50 anos\">`;<\/p>\n<p>  \/\/ Grid Y\n  ticks.forEach(tv => {\n    const y = yScale(tv);\n    svg += `<line class=\"grid-line\" x1=\"${margin.left}\" x2=\"${W - margin.right}\" y1=\"${y}\" y2=\"${y}\"\/>`;\n    svg += `<text class=\"axis-label\" x=\"${margin.left - 6}\" y=\"${y + 3}\" text-anchor=\"end\">${fmtCompact(tv)}<\/text>`;\n  });<\/p>\n<p>  \/\/ X grid (anos m\u00faltiplos de 10)\n  for (let yr = 0; yr <= 50; yr += 10) {\n    const x = xScale(yr);\n    svg += `<line class=\"grid-line\" x1=\"${x}\" x2=\"${x}\" y1=\"${margin.top}\" y2=\"${margin.top + innerH}\"\/>`;\n    svg += `<text class=\"axis-label\" x=\"${x}\" y=\"${H - margin.bottom + 14}\" text-anchor=\"middle\">${yr}<\/text>`;\n  }<\/p>\n<p>  \/\/ Zero line\n  svg += `<line class=\"axis-zero\" x1=\"${margin.left}\" x2=\"${W - margin.right}\" y1=\"${yZero}\" y2=\"${yZero}\"\/>`;<\/p>\n<p>  \/\/ Lines for each scenario\n  series.forEach((data, si) => {\n    const path = data.map((p, i) => {\n      const x = xScale(p.year).toFixed(1);\n      const y = yScale(p.value).toFixed(1);\n      return `${i === 0 ? 'M' : 'L'}${x},${y}`;\n    }).join(' ');\n    svg += `<path d=\"${path}\" fill=\"none\" stroke=\"${SCENARIO_COLORS[si]}\" stroke-width=\"${si === 0 ? 2.5 : 2}\" stroke-linejoin=\"round\" stroke-linecap=\"round\"\/>`;<\/p>\n<p>    \/\/ Final value label\n    const last = data[data.length - 1];\n    const lastX = xScale(last.year);\n    const lastY = yScale(last.value);\n    svg += `<circle cx=\"${lastX}\" cy=\"${lastY}\" r=\"3.5\" fill=\"${SCENARIO_COLORS[si]}\"\/>`;\n  });<\/p>\n<p>  \/\/ Final values text\n  series.forEach((data, si) => {\n    const last = data[data.length - 1];\n    const lastX = xScale(last.year);\n    const lastY = yScale(last.value);\n    svg += `<text x=\"${lastX - 4}\" y=\"${lastY + (si % 2 === 0 ? -6 : 12)}\" text-anchor=\"end\" font-weight=\"bold\" fill=\"${SCENARIO_COLORS[si]}\" font-size=\"9.5\">${fmtCompact(last.value)}<\/text>`;\n  });<\/p>\n<p>  svg += `<text class=\"axis-label\" x=\"${margin.left + innerW \/ 2}\" y=\"${H - 6}\" text-anchor=\"middle\" font-weight=\"bold\">Ano<\/text>`;<\/p>\n<p>  svg += `<\/svg>`;\n  container.innerHTML = svg;<\/p>\n<p>  \/\/ Legend with break-even info\n  const breakEven = series.map((data, si) => {\n    if (data[data.length - 1].value < 0) return null; \/\/ never breaks even\n    const idx = data.findIndex(p => p.value > 0);\n    return idx > 0 ? data[idx].year : null;\n  });<\/p>\n<p>  document.getElementById('legendFluxoAcumulado').innerHTML = SCENARIO_NAMES.map((n, i) => {\n    const be = breakEven[i] !== null ? ` (zero ano ${breakEven[i]})` : '';\n    return `<span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${SCENARIO_COLORS[i]}\"><\/span>${n}${be}<\/span>`;\n  }).join('');\n}<\/p>\n<p>\/\/ ---------- Chart 3: Composi\u00e7\u00e3o encargo vs receita (anos 1-15) ----------\nfunction renderChartComposicao(c) {\n  const container = document.getElementById('chartComposicao');\n  const yearsToShow = 15;\n  const years = c.years.slice(0, yearsToShow);\n  const isMobile = isMobileLayout();\n  const W = isMobile ? 360 : 800;\n  const H = isMobile ? 320 : 360;\n  const margin = { top: 16, right: 14, bottom: 38, left: isMobile ? 44 : 56 };\n  const innerW = W - margin.left - margin.right;\n  const innerH = H - margin.top - margin.bottom;<\/p>\n<p>  \/\/ For each year: encargo stack (juros, amort_normal, amort_extra, opex) vs receita_liquida (Cen\u00e1rio 1)\n  let maxV = 0;\n  years.forEach(y => {\n    const enc = y.juros + y.amortizacao + y.amortExtra + y.opex;\n    const rec = y.scenarios[0].receitaLiquida;\n    if (enc > maxV) maxV = enc;\n    if (rec > maxV) maxV = rec;\n  });\n  if (maxV === 0) maxV = 1;\n  maxV *= 1.05;<\/p>\n<p>  const yScale = v => margin.top + innerH * (1 - v \/ maxV);\n  const yZero = yScale(0);<\/p>\n<p>  const groupW = innerW \/ yearsToShow;\n  const barW = (groupW - 4) \/ 2;<\/p>\n<p>  const ticks = [];\n  const step = niceStep(maxV \/ 5);\n  for (let t = 0; t <= maxV; t += step) ticks.push(t);\n\n  let svg = `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-label=\"Composi\u00e7\u00e3o receita vs encargo\">`;<\/p>\n<p>  \/\/ Grid Y\n  ticks.forEach(tv => {\n    const y = yScale(tv);\n    svg += `<line class=\"grid-line\" x1=\"${margin.left}\" x2=\"${W - margin.right}\" y1=\"${y}\" y2=\"${y}\"\/>`;\n    svg += `<text class=\"axis-label\" x=\"${margin.left - 6}\" y=\"${y + 3}\" text-anchor=\"end\">${fmtCompact(tv)}<\/text>`;\n  });<\/p>\n<p>  \/\/ Stack colors\n  const COL_JUROS = '#7B5E7C';\n  const COL_AMORT = '#4B384C';\n  const COL_EXTRA = '#9B2226';\n  const COL_OPEX = '#AF95A6';\n  const COL_REC = '#2D6A4F';<\/p>\n<p>  years.forEach((yr, gi) => {\n    const groupX = margin.left + gi * groupW + 2;\n    \/\/ Encargo (stacked bar \u00e0 esquerda)\n    const xEnc = groupX;\n    let yCursor = yZero;<\/p>\n<p>    const stackParts = [\n      { val: yr.juros, color: COL_JUROS, label: 'Juros' },\n      { val: yr.amortizacao, color: COL_AMORT, label: 'Amort. capital' },\n      { val: yr.amortExtra, color: COL_EXTRA, label: 'Amort. extra' },\n      { val: yr.opex, color: COL_OPEX, label: 'OPEX' }\n    ];\n    stackParts.forEach(part => {\n      if (part.val <= 0) return;\n      const h = (yZero - yScale(part.val));\n      yCursor -= h;\n      svg += `<rect x=\"${xEnc.toFixed(1)}\" y=\"${yCursor.toFixed(1)}\" width=\"${barW.toFixed(1)}\" height=\"${h.toFixed(1)}\" fill=\"${part.color}\">\n        <title>Ano ${yr.year}, ${part.label}: ${eur(part.val)}<\/title>\n      <\/rect>`;\n    });<\/p>\n<p>    \/\/ Receita l\u00edquida (barra \u00e0 direita)\n    const xRec = groupX + barW + 1;\n    const rec = yr.scenarios[0].receitaLiquida;\n    if (rec > 0) {\n      const recH = yZero - yScale(rec);\n      svg += `<rect x=\"${xRec.toFixed(1)}\" y=\"${yScale(rec).toFixed(1)}\" width=\"${barW.toFixed(1)}\" height=\"${recH.toFixed(1)}\" fill=\"${COL_REC}\">\n        <title>Ano ${yr.year}, Receita l\u00edquida (C1): ${eur(rec)}<\/title>\n      <\/rect>`;\n    }<\/p>\n<p>    \/\/ X-label\n    svg += `<text class=\"axis-label\" x=\"${(groupX + barW + 1).toFixed(1)}\" y=\"${H - margin.bottom + 14}\" text-anchor=\"middle\">${yr.year}<\/text>`;\n  });<\/p>\n<p>  svg += `<text class=\"axis-label\" x=\"${margin.left + innerW \/ 2}\" y=\"${H - 6}\" text-anchor=\"middle\" font-weight=\"bold\">Ano<\/text>`;\n  svg += `<\/svg>`;\n  container.innerHTML = svg;<\/p>\n<p>  document.getElementById('legendComposicao').innerHTML = `\n    <span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${COL_JUROS}\"><\/span>Juros<\/span>\n    <span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${COL_AMORT}\"><\/span>Amort. capital<\/span>\n    <span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${COL_EXTRA}\"><\/span>Amort. extraordin\u00e1ria<\/span>\n    <span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${COL_OPEX}\"><\/span>OPEX<\/span>\n    <span class=\"legend-item\"><span class=\"legend-swatch\" style=\"background:${COL_REC}\"><\/span>Receita l\u00edquida (Cen. 1)<\/span>\n  `;\n}<\/p>\n<p>\/\/ ---------- Chart 4: Saldo d\u00edvida ao longo dos 50 anos ----------\nfunction renderChartSaldoDivida(c) {\n  const container = document.getElementById('chartSaldoDivida');\n  const isMobile = isMobileLayout();\n  const W = isMobile ? 360 : 800;\n  const H = isMobile ? 240 : 280;\n  const margin = { top: 16, right: 14, bottom: 38, left: isMobile ? 50 : 64 };\n  const innerW = W - margin.left - margin.right;\n  const innerH = H - margin.top - margin.bottom;<\/p>\n<p>  const data = [{ year: 0, value: c.finCM }, ...c.years.map(y => ({ year: y.year, value: y.saldoFinal }))];\n  const maxV = Math.max(c.finCM, ...data.map(d => d.value));<\/p>\n<p>  const xScale = yr => margin.left + (yr \/ 50) * innerW;\n  const yScale = v => margin.top + innerH * (1 - v \/ (maxV * 1.05));\n  const yZero = yScale(0);<\/p>\n<p>  const ticks = [];\n  const step = niceStep(maxV \/ 5);\n  for (let t = 0; t <= maxV * 1.05; t += step) ticks.push(t);\n\n  let svg = `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-label=\"Saldo em d\u00edvida\">`;<\/p>\n<p>  ticks.forEach(tv => {\n    const y = yScale(tv);\n    svg += `<line class=\"grid-line\" x1=\"${margin.left}\" x2=\"${W - margin.right}\" y1=\"${y}\" y2=\"${y}\"\/>`;\n    svg += `<text class=\"axis-label\" x=\"${margin.left - 6}\" y=\"${y + 3}\" text-anchor=\"end\">${fmtCompact(tv)}<\/text>`;\n  });<\/p>\n<p>  for (let yr = 0; yr <= 50; yr += 10) {\n    const x = xScale(yr);\n    svg += `<line class=\"grid-line\" x1=\"${x}\" x2=\"${x}\" y1=\"${margin.top}\" y2=\"${margin.top + innerH}\"\/>`;\n    svg += `<text class=\"axis-label\" x=\"${x}\" y=\"${H - margin.bottom + 14}\" text-anchor=\"middle\">${yr}<\/text>`;\n  }<\/p>\n<p>  \/\/ Filled area\n  let areaPath = `M${xScale(0)},${yZero}`;\n  data.forEach(p => { areaPath += ` L${xScale(p.year).toFixed(1)},${yScale(p.value).toFixed(1)}`; });\n  areaPath += ` L${xScale(50)},${yZero} Z`;\n  svg += `<path d=\"${areaPath}\" fill=\"${SCENARIO_COLORS[2]}\" opacity=\"0.25\"\/>`;<\/p>\n<p>  \/\/ Line\n  let linePath = data.map((p, i) => `${i === 0 ? 'M' : 'L'}${xScale(p.year).toFixed(1)},${yScale(p.value).toFixed(1)}`).join(' ');\n  svg += `<path d=\"${linePath}\" fill=\"none\" stroke=\"${SCENARIO_COLORS[0]}\" stroke-width=\"2\"\/>`;<\/p>\n<p>  \/\/ Mark extra amortizations\n  c.extrasAplicadasInfo.forEach(info => {\n    const x = xScale(info.year);\n    const y = yScale(info.saldoApos);\n    svg += `<circle cx=\"${x}\" cy=\"${y}\" r=\"4\" fill=\"#9B2226\" stroke=\"#fff\" stroke-width=\"1.5\">\n      <title>Ano ${info.year}: amortiza\u00e7\u00e3o extraordin\u00e1ria de ${eur(info.applied)}<\/title>\n    <\/circle>`;\n  });<\/p>\n<p>  svg += `<text class=\"axis-label\" x=\"${margin.left + innerW \/ 2}\" y=\"${H - 6}\" text-anchor=\"middle\" font-weight=\"bold\">Ano<\/text>`;\n  svg += `<\/svg>`;\n  container.innerHTML = svg;<\/p>\n<p>  \/\/ Stats\n  const yearPaidOff = c.years.find(y => y.saldoFinal < 0.01 &#038;&#038; y.year > c.p.carencia);\n  const yearPaidOffStr = yearPaidOff ? `Ano ${yearPaidOff.year}` : `Ano ${c.p.prazo} (prazo total)`;<\/p>\n<p>  document.getElementById('statsSaldoDivida').innerHTML = `<\/p>\n<div class=\"cs-stat\">\n<div class=\"cs-stat-label\">Capital inicial<\/div>\n<div class=\"cs-stat-value\">${fmtCompact(c.finCM)}<\/div>\n<\/p><\/div>\n<div class=\"cs-stat\">\n<div class=\"cs-stat-label\">Total juros pagos<\/div>\n<div class=\"cs-stat-value\">${fmtCompact(c.totalJurosVida)}<\/div>\n<\/p><\/div>\n<div class=\"cs-stat\">\n<div class=\"cs-stat-label\">Amortiza\u00e7\u00f5es extra<\/div>\n<div class=\"cs-stat-value\">${fmtCompact(c.totalAmortExtra)}<\/div>\n<\/p><\/div>\n<div class=\"cs-stat\">\n<div class=\"cs-stat-label\">Empr\u00e9stimo pago em<\/div>\n<div class=\"cs-stat-value\">${yearPaidOffStr}<\/div>\n<\/p><\/div>\n<p>  `;\n}<\/p>\n<p>function renderFluxoPanel(c, p) {\n  const heroEl = document.getElementById('fluxHero');\n  const tableEl = document.getElementById('fluxPeriodBody');\n  if (!heroEl || !tableEl) return;\n  const periods = [[1,1],[2,2],[3,3],[4,4],[5,5],[6,10],[11,20],[21,30],[31,40],[41,50]];\n  let heroHtml = '';\n  c.totais.forEach((tot, i) => {\n    const breakeven = c.years.find(y => y.scenarios[i].fluxoAcumulado >= 0);\n    const beStr = breakeven ? ('Ano ' + breakeven.year) : 'N\u00e3o atinge em 50 anos';\n    const beClass = breakeven ? 'flux-be-ok' : 'flux-be-no';\n    const avgAnnual = tot.fluxoLiquido50 \/ 50;\n    const totalClass = tot.fluxoLiquido50 >= 0 ? 'flux-positive' : 'flux-negative';\n    const desconto = (p.descontos[i] * 100).toFixed(0);\n    heroHtml +=\n      '<\/p>\n<div class=\"flux-hero-card\" data-scen=\"' + i + '\">' +\n        '<\/p>\n<div class=\"flux-scen-name\">Cen\u00e1rio ' + (i+1) + '<\/div>\n<p>' +\n        '<\/p>\n<div class=\"flux-scen-disc\">' + desconto + '% desconto sobre rendas<\/div>\n<p>' +\n        '<\/p>\n<div class=\"flux-total ' + totalClass + '\">' + eur(tot.fluxoLiquido50) + '<\/div>\n<p>' +\n        '<\/p>\n<div class=\"flux-total-label\">Fluxo acumulado 50 anos<\/div>\n<p>' +\n        '<\/p>\n<div class=\"flux-stats\">' +\n          '<\/p>\n<div><span class=\"flux-stat-label\">Break-even:<\/span> <span class=\"' + beClass + '\">' + beStr + '<\/span><\/div>\n<p>' +\n          '<\/p>\n<div><span class=\"flux-stat-label\">Fluxo m\u00e9dio\/ano:<\/span> ' + eur(avgAnnual) + '<\/div>\n<p>' +\n        '<\/p><\/div>\n<p>' +\n      '<\/p><\/div>\n<p>';\n  });\n  heroEl.innerHTML = heroHtml;<\/p>\n<p>  let tbHtml = '';\n  periods.forEach(function(per) {\n    const from = per[0], to = per[1];\n    const label = (from === to) ? ('Ano ' + from) : ('Anos ' + from + '-' + to);\n    const isSingle = (from === to);\n    let row = '<tr' + (isSingle ? ' class=\"flux-single-year\"' : '') + '>\n<td>' + label + '<\/td>\n<p>';\n    c.totais.forEach(function(_, i) {\n      const sum = c.years\n        .filter(function(y) { return y.year >= from && y.year <= to; })\n        .reduce(function(s, y) { return s + y.scenarios[i].fluxoLiquido; }, 0);\n      const cls = sum < 0 ? 'flux-negative' : '';\n      row += '\n\n<td class=\"' + cls + '\">' + eur(sum) + '<\/td>\n<p>';\n    });\n    row += '<\/tr>\n<p>';\n    tbHtml += row;\n  });\n  let totalRow = '<\/p>\n<tr class=\"flux-total-row\">\n<td>Total 50 anos<\/td>\n<p>';\n  c.totais.forEach(function(tot, i) {\n    const cls = tot.fluxoLiquido50 < 0 ? 'flux-negative' : '';\n    totalRow += '\n\n<td class=\"' + cls + '\"><strong>' + eur(tot.fluxoLiquido50) + '<\/strong><\/td>\n<p>';\n  });\n  totalRow += '<\/tr>\n<p>';\n  tbHtml += totalRow;\n  tableEl.innerHTML = tbHtml;\n}<\/p>\n<p>function renderAllCharts(c) {\n  try {\n    renderChartFluxoAnual(c);\n    renderChartFluxoAcumulado(c);\n    renderChartComposicao(c);\n    renderChartSaldoDivida(c);\n  } catch (e) {\n    console.error('Erro ao desenhar gr\u00e1ficos:', e);\n  }\n}<\/p>\n<p>\/\/ ============================================================\n\/\/ EVENT LISTENERS\n\/\/ ============================================================\ndocument.querySelectorAll('input').forEach(inp => {\n  inp.addEventListener('input', render);\n});<\/p>\n<p>\/\/ Re-render charts on window resize (for responsive sizing)\nlet resizeTimer;\nwindow.addEventListener('resize', () => {\n  clearTimeout(resizeTimer);\n  resizeTimer = setTimeout(() => { if (window._calc) renderAllCharts(window._calc); }, 200);\n});<\/p>\n<p>\/\/ Initial render\nrender();<\/p>\n<p>\/\/ ============================================================\n\/\/ MOBILE TABS\n\/\/ ============================================================\nfunction switchTab(tab) {\n  document.body.dataset.tab = tab;\n  document.querySelectorAll('.tab-btn').forEach(btn => {\n    btn.classList.toggle('active', btn.dataset.tabTarget === tab);\n  });\n  window.scrollTo({ top: 0, behavior: 'smooth' });\n}<\/p>\n<p>\/\/ Inicializa separador padr\u00e3o em ecr\u00e3s m\u00f3veis\nif (window.matchMedia('(max-width: 600px)').matches) {\n  document.body.dataset.tab = 'config';\n}<\/p>\n<p>\/\/ ============================================================\n\/\/ SCROLL TO TOP\n\/\/ ============================================================\nconst scrollBtn = document.getElementById('scrollTopBtn');\nfunction scrollToTop() {\n  window.scrollTo({ top: 0, behavior: 'smooth' });\n}\nwindow.addEventListener('scroll', () => {\n  if (window.scrollY > 300) {\n    scrollBtn.classList.add('visible');\n  } else {\n    scrollBtn.classList.remove('visible');\n  }\n});<\/p>\n<p>\/\/ ============================================================\n\/\/ PWA: registo do service worker\n\/\/ ============================================================\n\/\/ Service worker desativado em modo offline (file:\/\/)<\/p>\n<p>\/\/ ============================================================\n\/\/ PWA: prompt de instala\u00e7\u00e3o\n\/\/ ============================================================\nlet deferredPrompt = null;\nconst installBanner = document.getElementById('installBanner');<\/p>\n<p>window.addEventListener('beforeinstallprompt', (e) => {\n  e.preventDefault();\n  deferredPrompt = e;\n  \/\/ Mostra banner se o utilizador ainda n\u00e3o dispensou\n  if (!localStorage.getItem('install-dismissed')) {\n    installBanner.classList.add('show');\n  }\n});<\/p>\n<p>function installPWA() {\n  if (!deferredPrompt) return;\n  deferredPrompt.prompt();\n  deferredPrompt.userChoice.then(choice => {\n    if (choice.outcome === 'accepted') {\n      console.log('PWA instalada');\n    }\n    deferredPrompt = null;\n    installBanner.classList.remove('show');\n  });\n}<\/p>\n<p>function dismissInstall() {\n  installBanner.classList.remove('show');\n  try { localStorage.setItem('install-dismissed', '1'); } catch(e) {}\n}<\/p>\n<p>window.addEventListener('appinstalled', () => {\n  installBanner.classList.remove('show');\n  deferredPrompt = null;\n});\n<\/script><\/p>\n<p><\/body><br \/>\n<\/html><\/p>","protected":false},"excerpt":{"rendered":"<p>Simulador IHRU: Investimento Habitacional Instale a aplica\u00e7\u00e3o Funciona offline e tem \u00edcone pr\u00f3prio no telem\u00f3vel Instalar \u00d7 Simulador de Investimento Habitacional 1. Pressupostos edit\u00e1veis Tipologia e quantidade de fogos T1 (renda \u20ac\/m\u00eas) T2 (renda \u20ac\/m\u00eas) T3 (renda \u20ac\/m\u00eas) T4 (renda \u20ac\/m\u00eas) Quantidade T1 Quantidade T2 Quantidade T3 Quantidade T4 Investimento e financiamento CAPEX Total (\u20ac) [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-908","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/pages\/908","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/comments?post=908"}],"version-history":[{"count":1,"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/pages\/908\/revisions"}],"predecessor-version":[{"id":909,"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/pages\/908\/revisions\/909"}],"wp:attachment":[{"href":"https:\/\/sfai.pt\/en\/wp-json\/wp\/v2\/media?parent=908"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}