Spaces:
Sleeping
Sleeping
chore: deploy web_comercial from monorepo
Browse files- .dockerignore +3 -0
- Dockerfile +32 -0
- README.md +68 -6
- apps/web_comercial/.gitignore +41 -0
- apps/web_comercial/README.md +16 -0
- apps/web_comercial/eslint.config.mjs +18 -0
- apps/web_comercial/next-env.d.ts +6 -0
- apps/web_comercial/next.config.ts +12 -0
- apps/web_comercial/package-lock.json +0 -0
- apps/web_comercial/package.json +27 -0
- apps/web_comercial/postcss.config.mjs +7 -0
- apps/web_comercial/public/file.svg +1 -0
- apps/web_comercial/public/globe.svg +1 -0
- apps/web_comercial/public/next.svg +1 -0
- apps/web_comercial/public/vercel.svg +1 -0
- apps/web_comercial/public/window.svg +1 -0
- apps/web_comercial/src/app/comparador/page.tsx +291 -0
- apps/web_comercial/src/app/configuracoes/page.tsx +125 -0
- apps/web_comercial/src/app/favicon.ico +0 -0
- apps/web_comercial/src/app/globals.css +259 -0
- apps/web_comercial/src/app/layout.tsx +30 -0
- apps/web_comercial/src/app/mercado-secundario/page.tsx +534 -0
- apps/web_comercial/src/app/page.tsx +57 -0
- apps/web_comercial/src/app/premissas/page.tsx +88 -0
- apps/web_comercial/src/app/proposta/page.tsx +235 -0
- apps/web_comercial/src/components/layout/AppLayout.tsx +19 -0
- apps/web_comercial/src/components/layout/Sidebar.tsx +72 -0
- apps/web_comercial/src/components/layout/Topbar.tsx +68 -0
- apps/web_comercial/src/components/layout/layout.css +350 -0
- apps/web_comercial/src/components/simulator/SimulatorForm.tsx +354 -0
- apps/web_comercial/src/components/ui/Card.tsx +53 -0
- apps/web_comercial/src/components/ui/FormControls.tsx +76 -0
- apps/web_comercial/src/components/ui/Table.tsx +59 -0
- apps/web_comercial/src/components/ui/Tabs.tsx +29 -0
- apps/web_comercial/src/components/ui/Tooltip.tsx +15 -0
- apps/web_comercial/src/components/ui/ui.css +318 -0
- apps/web_comercial/tsconfig.json +42 -0
- packages/simulation-engine/package.json +14 -0
- packages/simulation-engine/src/generated/rodobensFieldMatrix.v1.json +838 -0
- packages/simulation-engine/src/index.d.ts +196 -0
- packages/simulation-engine/src/index.js +823 -0
- packages/simulation-engine/src/scpDefaults.js +658 -0
- packages/simulation-engine/tests/simulation-engine.test.js +274 -0
.dockerignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
**/.next
|
| 2 |
+
**/node_modules
|
| 3 |
+
**/.DS_Store
|
Dockerfile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:22-bookworm-slim AS builder
|
| 2 |
+
WORKDIR /workspace
|
| 3 |
+
|
| 4 |
+
COPY apps/web_comercial ./apps/web_comercial
|
| 5 |
+
COPY packages/simulation-engine ./packages/simulation-engine
|
| 6 |
+
|
| 7 |
+
RUN npm ci --prefix apps/web_comercial
|
| 8 |
+
RUN npm --prefix apps/web_comercial run build
|
| 9 |
+
|
| 10 |
+
FROM node:22-bookworm-slim AS runner
|
| 11 |
+
WORKDIR /workspace
|
| 12 |
+
ENV NODE_ENV=production
|
| 13 |
+
ENV PORT=7860
|
| 14 |
+
ENV HOSTNAME=0.0.0.0
|
| 15 |
+
|
| 16 |
+
# Install Dev Mode requirements
|
| 17 |
+
RUN apt-get update && \
|
| 18 |
+
apt-get install -y \
|
| 19 |
+
bash \
|
| 20 |
+
git git-lfs \
|
| 21 |
+
wget curl procps \
|
| 22 |
+
htop vim nano openssh-client && \
|
| 23 |
+
rm -rf /var/lib/apt/lists/*
|
| 24 |
+
|
| 25 |
+
COPY --from=builder /workspace/apps/web_comercial ./apps/web_comercial
|
| 26 |
+
COPY --from=builder /workspace/packages/simulation-engine ./packages/simulation-engine
|
| 27 |
+
|
| 28 |
+
RUN chown -R 1000:1000 /workspace
|
| 29 |
+
USER 1000
|
| 30 |
+
|
| 31 |
+
EXPOSE 7860
|
| 32 |
+
CMD ["sh", "-c", "cd apps/web_comercial && npm run start -- -H 0.0.0.0 -p 7860"]
|
README.md
CHANGED
|
@@ -1,10 +1,72 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
-
pinned:
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Plataforma MAIA - Simulador Consórcio
|
| 3 |
+
emoji: 📈
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
+
pinned: true
|
| 8 |
+
hardware: zero-a10g
|
| 9 |
---
|
| 10 |
|
| 11 |
+
## MAIA v2 - Simulador de Consórcios Corporativo
|
| 12 |
+
|
| 13 |
+
Este é o ambiente de implantação otimizado do **Simulador de Consórcio**, parte da arquitetura **MAIA v2**. O contêiner hospeda nativamente o frontend (`apps/web_comercial` base Next.js) e o motor de simulação por trás (`packages/simulation-engine`), provendo visualizações de fluxos de caixa e regras de negócio para análise de crédito de alto desempenho.
|
| 14 |
+
|
| 15 |
+
### 🏗 Arquitetura do Contêiner
|
| 16 |
+
|
| 17 |
+
- **Interface:** Baseada em Next.js (Node 22), rodando em modo stand-alone (`apps/web_comercial`).
|
| 18 |
+
- **Motor (Backend):** Pacotes internos de agentes (`packages/agents` e `packages/simulation-engine`) que interagem dinamicamente para aplicar regras complexas de extração e validação do mercado secundário.
|
| 19 |
+
- **Portabiblidade:** Agnóstico à plataforma. Rodamos a mesma imagem Docker em desenvolvimento local e implantação cloud.
|
| 20 |
+
|
| 21 |
+
### 🚀 Capacidades Premium (Hugging Face PRO)
|
| 22 |
+
|
| 23 |
+
Este Space foi arquitetado sob governança estrita para uso máximo de **Assinatura Hugging Face PRO**:
|
| 24 |
+
|
| 25 |
+
1. **ZeroGPU e Agnosticismo de Hardware:**
|
| 26 |
+
O sistema solicitará _hardware_ de GPU sob-demanda (`zero-a10g`) durante inferências robustas dos Agentes IA, entregando extrema performance sem consumo residual.
|
| 27 |
+
O utilitário interno Python (`DeviceManager`) é responsável pela segregação: ele ativa CUDA neste ambiente, mas rebaixa graciosamente para _MPS_ (Apple Silicon, M1/M2/M3) ou CPU (Ubuntu VPS/Coolify) em instâncias locais. **Nunca** altere bibliotecas para requerer CUDA como exclusividade.
|
| 28 |
+
|
| 29 |
+
2. **Depuração Viva (Dev Mode):**
|
| 30 |
+
Como assinante PRO, você pode realizar _hot-fixes_ e testes de inferência na nuvem ativando o **Dev Mode** em `Settings > Dev Mode` do Space. Pode se conectar através de terminal SSH ou usar o **VS Code Web** injetado nativamente pela Hugging Face. As edições realizadas nesse ambiente **SÃO TEMPORÁRIAS E DEVEM SER COMITADAS**.
|
| 31 |
+
O nosso fluxo padrão determina:
|
| 32 |
+
- **`develop` -> Space DEV:** Use o Dev Mode para testes paralelos sem afetar instâncias públicas. Altere o código pelo VS Code do Space e faça `git add`, `git commit` e `git push origin develop`.
|
| 33 |
+
- **`main` -> Space PROD:** Implantações em produção ocorrem automaticamente através de CI/CD limpo sem Dev Mode ativado.
|
| 34 |
+
|
| 35 |
+
3. **Governança de Dados (Data Studio):**
|
| 36 |
+
Artefatos massivos e pesados (extrações `.xlsm`, logs e tabelas relacionais do simulador) nunca são commitados no repositório de código fonte do GitHub. A análise desses eventos confidenciais corporativos utiliza o portal visual **Data Studio** do ecossistema de dados privados da Hugging Face associado a esta organização.
|
| 37 |
+
|
| 38 |
+
### ⚙ Variáveis de Ambiente Necessárias (Secrets)
|
| 39 |
+
|
| 40 |
+
Para que os motores e agentes operem de portas abertas com provedores roteados, defina no menu `Settings > Variables and Secrets`:
|
| 41 |
+
|
| 42 |
+
- `HF_TOKEN`: O token da sua conta PRO. Necessário para Inference Providers e APIs avançadas.
|
| 43 |
+
- Qualquer chave adicional de LLMs (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`) deve ser adicionada neste mesmo painel da nuvem, de acordo com o padrão encontrado no `.env` do monorepo original.
|
| 44 |
+
|
| 45 |
+
### 🔍 Acesso aos Logs (via API)
|
| 46 |
+
|
| 47 |
+
Para depuração avançada sem depender do painel web, a Hugging Face fornece endpoints SSE (_Server-Sent Events_) para observabilidade do Space.
|
| 48 |
+
Você pode usar o script local que configuramos:
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
# Exportar o token antes
|
| 52 |
+
export HF_TOKEN="seu_token_aqui"
|
| 53 |
+
|
| 54 |
+
# Obter logs de execução em tempo real
|
| 55 |
+
./scripts/deploy/hf_logs.sh sua-org/nome-do-space run
|
| 56 |
+
|
| 57 |
+
# Obter logs de build em tempo real
|
| 58 |
+
./scripts/deploy/hf_logs.sh sua-org/nome-do-space build
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
_Ou através de chamadas brutas (cURL)_:
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
curl -N -H "Authorization: Bearer $HF_TOKEN" "https://huggingface.co/api/spaces/sua-org/nome-do-space/logs/run"
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### 🤖 CI/CD do GitHub
|
| 68 |
+
|
| 69 |
+
_Para automação, este repositório é gerenciado upstream pelo GitHub, através do workflow `.github/workflows/simulador-consorcio-deploy-hf.yml`. Ele requer que os seguintes **Repository Secrets** do Github sejam provisionados:_
|
| 70 |
+
|
| 71 |
+
- `HF_SPACE_DEV`: Caminho do Space de desenvolvimento (Ex: `org/simulador-dev`). Disparado em push na `develop`.
|
| 72 |
+
- `HF_SPACE_PROD`: Caminho do Space de produção (Ex: `org/simulador-prod`). Disparado em push na `main`.
|
apps/web_comercial/.gitignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
/node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.*
|
| 7 |
+
.yarn/*
|
| 8 |
+
!.yarn/patches
|
| 9 |
+
!.yarn/plugins
|
| 10 |
+
!.yarn/releases
|
| 11 |
+
!.yarn/versions
|
| 12 |
+
|
| 13 |
+
# testing
|
| 14 |
+
/coverage
|
| 15 |
+
|
| 16 |
+
# next.js
|
| 17 |
+
/.next/
|
| 18 |
+
/out/
|
| 19 |
+
|
| 20 |
+
# production
|
| 21 |
+
/build
|
| 22 |
+
|
| 23 |
+
# misc
|
| 24 |
+
.DS_Store
|
| 25 |
+
*.pem
|
| 26 |
+
|
| 27 |
+
# debug
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
.pnpm-debug.log*
|
| 32 |
+
|
| 33 |
+
# env files (can opt-in for committing if needed)
|
| 34 |
+
.env*
|
| 35 |
+
|
| 36 |
+
# vercel
|
| 37 |
+
.vercel
|
| 38 |
+
|
| 39 |
+
# typescript
|
| 40 |
+
*.tsbuildinfo
|
| 41 |
+
next-env.d.ts
|
apps/web_comercial/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Web App - Simulador Consorcio
|
| 2 |
+
|
| 3 |
+
Aplicacao Next.js que consome o motor deterministico interno `@simulador-consorcio/simulation-engine`.
|
| 4 |
+
|
| 5 |
+
## Desenvolvimento
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm run dev
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
## Qualidade
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
npm run lint
|
| 15 |
+
npm run build
|
| 16 |
+
```
|
apps/web_comercial/eslint.config.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig, globalIgnores } from "eslint/config";
|
| 2 |
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
| 3 |
+
import nextTs from "eslint-config-next/typescript";
|
| 4 |
+
|
| 5 |
+
const eslintConfig = defineConfig([
|
| 6 |
+
...nextVitals,
|
| 7 |
+
...nextTs,
|
| 8 |
+
// Override default ignores of eslint-config-next.
|
| 9 |
+
globalIgnores([
|
| 10 |
+
// Default ignores of eslint-config-next:
|
| 11 |
+
".next/**",
|
| 12 |
+
"out/**",
|
| 13 |
+
"build/**",
|
| 14 |
+
"next-env.d.ts",
|
| 15 |
+
]),
|
| 16 |
+
]);
|
| 17 |
+
|
| 18 |
+
export default eslintConfig;
|
apps/web_comercial/next-env.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// <reference types="next" />
|
| 2 |
+
/// <reference types="next/image-types/global" />
|
| 3 |
+
import "./.next/types/routes.d.ts";
|
| 4 |
+
|
| 5 |
+
// NOTE: This file should not be edited
|
| 6 |
+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
apps/web_comercial/next.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { NextConfig } from "next";
|
| 2 |
+
import path from "path";
|
| 3 |
+
|
| 4 |
+
const nextConfig: NextConfig = {
|
| 5 |
+
transpilePackages: ["@simulador-consorcio/simulation-engine"],
|
| 6 |
+
outputFileTracingRoot: path.join(__dirname, "../.."),
|
| 7 |
+
experimental: {
|
| 8 |
+
externalDir: true,
|
| 9 |
+
},
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
export default nextConfig;
|
apps/web_comercial/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
apps/web_comercial/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "consorcio-simulator-app",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev",
|
| 7 |
+
"build": "next build --webpack",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "eslint"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"next": "16.1.6",
|
| 13 |
+
"react": "19.2.3",
|
| 14 |
+
"react-dom": "19.2.3",
|
| 15 |
+
"@simulador-consorcio/simulation-engine": "file:../../packages/simulation-engine"
|
| 16 |
+
},
|
| 17 |
+
"devDependencies": {
|
| 18 |
+
"@tailwindcss/postcss": "^4",
|
| 19 |
+
"@types/node": "^20",
|
| 20 |
+
"@types/react": "^19",
|
| 21 |
+
"@types/react-dom": "^19",
|
| 22 |
+
"eslint": "^9",
|
| 23 |
+
"eslint-config-next": "16.1.6",
|
| 24 |
+
"tailwindcss": "^4",
|
| 25 |
+
"typescript": "^5"
|
| 26 |
+
}
|
| 27 |
+
}
|
apps/web_comercial/postcss.config.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const config = {
|
| 2 |
+
plugins: {
|
| 3 |
+
"@tailwindcss/postcss": {},
|
| 4 |
+
},
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
export default config;
|
apps/web_comercial/public/file.svg
ADDED
|
|
apps/web_comercial/public/globe.svg
ADDED
|
|
apps/web_comercial/public/next.svg
ADDED
|
|
apps/web_comercial/public/vercel.svg
ADDED
|
|
apps/web_comercial/public/window.svg
ADDED
|
|
apps/web_comercial/src/app/comparador/page.tsx
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { Card, DataCard } from "@/components/ui/Card";
|
| 4 |
+
import { Input, Select } from "@/components/ui/FormControls";
|
| 5 |
+
import { calcularSimulacao } from "@simulador-consorcio/simulation-engine";
|
| 6 |
+
import { useMemo, useState } from "react";
|
| 7 |
+
|
| 8 |
+
function formatCurrency(value: number) {
|
| 9 |
+
return new Intl.NumberFormat("pt-BR", {
|
| 10 |
+
style: "currency",
|
| 11 |
+
currency: "BRL",
|
| 12 |
+
}).format(value);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export default function ComparadorPage() {
|
| 16 |
+
const [creditoA, setCreditoA] = useState(500000);
|
| 17 |
+
const [prazoA, setPrazoA] = useState(180);
|
| 18 |
+
const [temLanceA, setTemLanceA] = useState(true);
|
| 19 |
+
const [lancePctA, setLancePctA] = useState(30);
|
| 20 |
+
|
| 21 |
+
const [creditoB, setCreditoB] = useState(500000);
|
| 22 |
+
const [prazoB, setPrazoB] = useState(216);
|
| 23 |
+
const [temLanceB, setTemLanceB] = useState(true);
|
| 24 |
+
const [lancePctB, setLancePctB] = useState(20);
|
| 25 |
+
|
| 26 |
+
const cenarioA = useMemo(() => {
|
| 27 |
+
return calcularSimulacao({
|
| 28 |
+
credito_desejado: creditoA,
|
| 29 |
+
parametros: {
|
| 30 |
+
id_plano: "PADRAO",
|
| 31 |
+
nome_plano: "Plano Padrão A",
|
| 32 |
+
prazo_meses: prazoA,
|
| 33 |
+
taxa_administracao_total: 0.15,
|
| 34 |
+
fundo_reserva_mensal: 0.0005,
|
| 35 |
+
seguro_prestamista: 0.035,
|
| 36 |
+
},
|
| 37 |
+
tem_lance: temLanceA,
|
| 38 |
+
lance_percentual: temLanceA ? lancePctA / 100 : undefined,
|
| 39 |
+
});
|
| 40 |
+
}, [creditoA, prazoA, temLanceA, lancePctA]);
|
| 41 |
+
|
| 42 |
+
const cenarioB = useMemo(() => {
|
| 43 |
+
return calcularSimulacao({
|
| 44 |
+
credito_desejado: creditoB,
|
| 45 |
+
parametros: {
|
| 46 |
+
id_plano: "PADRAO",
|
| 47 |
+
nome_plano: "Plano Padrão B",
|
| 48 |
+
prazo_meses: prazoB,
|
| 49 |
+
taxa_administracao_total: 0.15,
|
| 50 |
+
fundo_reserva_mensal: 0.0005,
|
| 51 |
+
seguro_prestamista: 0.035,
|
| 52 |
+
},
|
| 53 |
+
tem_lance: temLanceB,
|
| 54 |
+
lance_percentual: temLanceB ? lancePctB / 100 : undefined,
|
| 55 |
+
});
|
| 56 |
+
}, [creditoB, prazoB, temLanceB, lancePctB]);
|
| 57 |
+
|
| 58 |
+
return (
|
| 59 |
+
<div className="module-container">
|
| 60 |
+
<div className="module-header">
|
| 61 |
+
<h1 className="module-title">Comparador de Cenários</h1>
|
| 62 |
+
<p className="module-subtitle">
|
| 63 |
+
Análise lado a lado com foco em parcela inicial, prazo, lance e saldo
|
| 64 |
+
operacional da proposta.
|
| 65 |
+
</p>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<div className="page-kpi-strip">
|
| 69 |
+
<DataCard label="Eixo de decisão" value="Fluxo inicial" subvalue="Peso maior na parcela e no lance" />
|
| 70 |
+
<DataCard label="Base comparada" value="Plano A vs B" subvalue="Prazo, crédito e contemplação" highlight />
|
| 71 |
+
<DataCard label="Leitura" value="Executiva" subvalue="Útil para reunião comercial" />
|
| 72 |
+
</div>
|
| 73 |
+
|
| 74 |
+
<div className="executive-banner">
|
| 75 |
+
<div>
|
| 76 |
+
<strong>Comparação orientada a decisão.</strong> O objetivo aqui é
|
| 77 |
+
evidenciar qual cenário preserva melhor o caixa sem degradar o
|
| 78 |
+
crédito disponível.
|
| 79 |
+
</div>
|
| 80 |
+
<div className="executive-banner__meta">
|
| 81 |
+
<span className="metric-chip">Parcela inicial</span>
|
| 82 |
+
<span className="metric-chip">Prazo total</span>
|
| 83 |
+
<span className="metric-chip">Lance embutido</span>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
|
| 87 |
+
<div className="module-content" style={{ display: "flex", gap: "24px" }}>
|
| 88 |
+
{/* Cenário A */}
|
| 89 |
+
<div
|
| 90 |
+
style={{
|
| 91 |
+
flex: 1,
|
| 92 |
+
display: "flex",
|
| 93 |
+
flexDirection: "column",
|
| 94 |
+
gap: "16px",
|
| 95 |
+
}}
|
| 96 |
+
>
|
| 97 |
+
<div
|
| 98 |
+
style={{
|
| 99 |
+
padding: "12px 16px",
|
| 100 |
+
backgroundColor: "var(--color-brand-dark)",
|
| 101 |
+
color: "white",
|
| 102 |
+
borderRadius: "4px",
|
| 103 |
+
fontWeight: 600,
|
| 104 |
+
textAlign: "center",
|
| 105 |
+
}}
|
| 106 |
+
>
|
| 107 |
+
Cenário A
|
| 108 |
+
</div>
|
| 109 |
+
<Card title="Parâmetros">
|
| 110 |
+
<div
|
| 111 |
+
style={{ display: "flex", flexDirection: "column", gap: "12px" }}
|
| 112 |
+
>
|
| 113 |
+
<Input
|
| 114 |
+
label="Crédito (R$)"
|
| 115 |
+
type="number"
|
| 116 |
+
value={creditoA}
|
| 117 |
+
onChange={(e) => setCreditoA(Number(e.target.value))}
|
| 118 |
+
/>
|
| 119 |
+
<Select
|
| 120 |
+
label="Prazo (meses)"
|
| 121 |
+
value={prazoA}
|
| 122 |
+
onChange={(e) => setPrazoA(Number(e.target.value))}
|
| 123 |
+
options={[
|
| 124 |
+
{ label: "120", value: 120 },
|
| 125 |
+
{ label: "150", value: 150 },
|
| 126 |
+
{ label: "180", value: 180 },
|
| 127 |
+
{ label: "216", value: 216 },
|
| 128 |
+
]}
|
| 129 |
+
/>
|
| 130 |
+
<div className="ui-form-group">
|
| 131 |
+
<label
|
| 132 |
+
className="ui-form-label"
|
| 133 |
+
style={{ display: "flex", alignItems: "center", gap: "8px" }}
|
| 134 |
+
>
|
| 135 |
+
<input
|
| 136 |
+
type="checkbox"
|
| 137 |
+
checked={temLanceA}
|
| 138 |
+
onChange={(e) => setTemLanceA(e.target.checked)}
|
| 139 |
+
/>{" "}
|
| 140 |
+
Com Lance?
|
| 141 |
+
</label>
|
| 142 |
+
</div>
|
| 143 |
+
{temLanceA && (
|
| 144 |
+
<Input
|
| 145 |
+
label="Lance (%)"
|
| 146 |
+
type="number"
|
| 147 |
+
value={lancePctA}
|
| 148 |
+
onChange={(e) => setLancePctA(Number(e.target.value))}
|
| 149 |
+
/>
|
| 150 |
+
)}
|
| 151 |
+
</div>
|
| 152 |
+
</Card>
|
| 153 |
+
<Card title="Sumário" highlight>
|
| 154 |
+
<div
|
| 155 |
+
style={{
|
| 156 |
+
padding: "16px",
|
| 157 |
+
display: "flex",
|
| 158 |
+
flexDirection: "column",
|
| 159 |
+
gap: "12px",
|
| 160 |
+
}}
|
| 161 |
+
>
|
| 162 |
+
<DataCard
|
| 163 |
+
label="Crédito"
|
| 164 |
+
value={formatCurrency(cenarioA.credito)}
|
| 165 |
+
/>
|
| 166 |
+
<DataCard
|
| 167 |
+
label="Parcela Inicial Integral"
|
| 168 |
+
value={formatCurrency(cenarioA.parcela_integral.total)}
|
| 169 |
+
highlight
|
| 170 |
+
/>
|
| 171 |
+
<DataCard label="Prazo Total" value={`${cenarioA.prazo} meses`} />
|
| 172 |
+
{temLanceA && cenarioA.valor_lance && (
|
| 173 |
+
<DataCard
|
| 174 |
+
label="Lance Embutido Estimado"
|
| 175 |
+
value={`${formatCurrency(cenarioA.valor_lance)} (${lancePctA}%)`}
|
| 176 |
+
/>
|
| 177 |
+
)}
|
| 178 |
+
</div>
|
| 179 |
+
</Card>
|
| 180 |
+
</div>
|
| 181 |
+
|
| 182 |
+
{/* VS Divider */}
|
| 183 |
+
<div
|
| 184 |
+
style={{
|
| 185 |
+
display: "flex",
|
| 186 |
+
alignItems: "center",
|
| 187 |
+
justifyContent: "center",
|
| 188 |
+
fontWeight: "bold",
|
| 189 |
+
color: "var(--text-muted)",
|
| 190 |
+
}}
|
| 191 |
+
>
|
| 192 |
+
VS
|
| 193 |
+
</div>
|
| 194 |
+
|
| 195 |
+
{/* Cenário B */}
|
| 196 |
+
<div
|
| 197 |
+
style={{
|
| 198 |
+
flex: 1,
|
| 199 |
+
display: "flex",
|
| 200 |
+
flexDirection: "column",
|
| 201 |
+
gap: "16px",
|
| 202 |
+
}}
|
| 203 |
+
>
|
| 204 |
+
<div
|
| 205 |
+
style={{
|
| 206 |
+
padding: "12px 16px",
|
| 207 |
+
backgroundColor: "var(--color-brand-accent)",
|
| 208 |
+
color: "white",
|
| 209 |
+
borderRadius: "4px",
|
| 210 |
+
fontWeight: 600,
|
| 211 |
+
textAlign: "center",
|
| 212 |
+
}}
|
| 213 |
+
>
|
| 214 |
+
Cenário B
|
| 215 |
+
</div>
|
| 216 |
+
<Card title="Parâmetros">
|
| 217 |
+
<div
|
| 218 |
+
style={{ display: "flex", flexDirection: "column", gap: "12px" }}
|
| 219 |
+
>
|
| 220 |
+
<Input
|
| 221 |
+
label="Crédito (R$)"
|
| 222 |
+
type="number"
|
| 223 |
+
value={creditoB}
|
| 224 |
+
onChange={(e) => setCreditoB(Number(e.target.value))}
|
| 225 |
+
/>
|
| 226 |
+
<Select
|
| 227 |
+
label="Prazo (meses)"
|
| 228 |
+
value={prazoB}
|
| 229 |
+
onChange={(e) => setPrazoB(Number(e.target.value))}
|
| 230 |
+
options={[
|
| 231 |
+
{ label: "120", value: 120 },
|
| 232 |
+
{ label: "150", value: 150 },
|
| 233 |
+
{ label: "180", value: 180 },
|
| 234 |
+
{ label: "216", value: 216 },
|
| 235 |
+
]}
|
| 236 |
+
/>
|
| 237 |
+
<div className="ui-form-group">
|
| 238 |
+
<label
|
| 239 |
+
className="ui-form-label"
|
| 240 |
+
style={{ display: "flex", alignItems: "center", gap: "8px" }}
|
| 241 |
+
>
|
| 242 |
+
<input
|
| 243 |
+
type="checkbox"
|
| 244 |
+
checked={temLanceB}
|
| 245 |
+
onChange={(e) => setTemLanceB(e.target.checked)}
|
| 246 |
+
/>{" "}
|
| 247 |
+
Com Lance?
|
| 248 |
+
</label>
|
| 249 |
+
</div>
|
| 250 |
+
{temLanceB && (
|
| 251 |
+
<Input
|
| 252 |
+
label="Lance (%)"
|
| 253 |
+
type="number"
|
| 254 |
+
value={lancePctB}
|
| 255 |
+
onChange={(e) => setLancePctB(Number(e.target.value))}
|
| 256 |
+
/>
|
| 257 |
+
)}
|
| 258 |
+
</div>
|
| 259 |
+
</Card>
|
| 260 |
+
<Card title="Sumário" highlight>
|
| 261 |
+
<div
|
| 262 |
+
style={{
|
| 263 |
+
padding: "16px",
|
| 264 |
+
display: "flex",
|
| 265 |
+
flexDirection: "column",
|
| 266 |
+
gap: "12px",
|
| 267 |
+
}}
|
| 268 |
+
>
|
| 269 |
+
<DataCard
|
| 270 |
+
label="Crédito"
|
| 271 |
+
value={formatCurrency(cenarioB.credito)}
|
| 272 |
+
/>
|
| 273 |
+
<DataCard
|
| 274 |
+
label="Parcela Inicial Integral"
|
| 275 |
+
value={formatCurrency(cenarioB.parcela_integral.total)}
|
| 276 |
+
highlight
|
| 277 |
+
/>
|
| 278 |
+
<DataCard label="Prazo Total" value={`${cenarioB.prazo} meses`} />
|
| 279 |
+
{temLanceB && cenarioB.valor_lance && (
|
| 280 |
+
<DataCard
|
| 281 |
+
label="Lance Embutido Estimado"
|
| 282 |
+
value={`${formatCurrency(cenarioB.valor_lance)} (${lancePctB}%)`}
|
| 283 |
+
/>
|
| 284 |
+
)}
|
| 285 |
+
</div>
|
| 286 |
+
</Card>
|
| 287 |
+
</div>
|
| 288 |
+
</div>
|
| 289 |
+
</div>
|
| 290 |
+
);
|
| 291 |
+
}
|
apps/web_comercial/src/app/configuracoes/page.tsx
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Card, DataCard } from "@/components/ui/Card";
|
| 2 |
+
import { Select } from "@/components/ui/FormControls";
|
| 3 |
+
|
| 4 |
+
export default function ConfiguracoesPage() {
|
| 5 |
+
return (
|
| 6 |
+
<div className="module-container">
|
| 7 |
+
<div className="module-header">
|
| 8 |
+
<h1 className="module-title">Configurações de Visualização</h1>
|
| 9 |
+
<p className="module-subtitle">
|
| 10 |
+
Ajuste densidade, modo de leitura e profundidade de memória de cálculo.
|
| 11 |
+
</p>
|
| 12 |
+
</div>
|
| 13 |
+
|
| 14 |
+
<div className="page-kpi-strip">
|
| 15 |
+
<DataCard label="Perfil padrão" value="Operacional" subvalue="Leitura detalhada de backoffice" />
|
| 16 |
+
<DataCard label="Saída cliente" value="Executivo" subvalue="Condensa ruído e prioriza proposta" highlight />
|
| 17 |
+
<DataCard label="Granularidade" value="Configuração fina" subvalue="Composição, cronograma e visão consolidada" />
|
| 18 |
+
</div>
|
| 19 |
+
|
| 20 |
+
<div
|
| 21 |
+
className="module-content"
|
| 22 |
+
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
|
| 23 |
+
>
|
| 24 |
+
<Card title="Modo de Operação">
|
| 25 |
+
<div
|
| 26 |
+
style={{
|
| 27 |
+
display: "grid",
|
| 28 |
+
gridTemplateColumns: "1fr 1fr",
|
| 29 |
+
gap: "16px",
|
| 30 |
+
}}
|
| 31 |
+
>
|
| 32 |
+
<Select
|
| 33 |
+
label="Modo"
|
| 34 |
+
options={[
|
| 35 |
+
{ label: "Operacional (Detalhado)", value: "operacional" },
|
| 36 |
+
{ label: "Executivo (Sumarizado)", value: "executivo" },
|
| 37 |
+
{ label: "Cliente (Simplificado)", value: "cliente" },
|
| 38 |
+
]}
|
| 39 |
+
defaultValue="operacional"
|
| 40 |
+
/>
|
| 41 |
+
<Select
|
| 42 |
+
label="Densidade da Interface"
|
| 43 |
+
options={[
|
| 44 |
+
{ label: "Compacta (Mais dados)", value: "compacta" },
|
| 45 |
+
{ label: "Confortável (Melhor leitura)", value: "confortavel" },
|
| 46 |
+
]}
|
| 47 |
+
defaultValue="compacta"
|
| 48 |
+
/>
|
| 49 |
+
</div>
|
| 50 |
+
</Card>
|
| 51 |
+
|
| 52 |
+
<Card title="Preferências do Simulador">
|
| 53 |
+
<div
|
| 54 |
+
style={{ display: "grid", gridTemplateColumns: "1fr", gap: "12px" }}
|
| 55 |
+
>
|
| 56 |
+
<label
|
| 57 |
+
style={{
|
| 58 |
+
display: "flex",
|
| 59 |
+
alignItems: "center",
|
| 60 |
+
gap: "8px",
|
| 61 |
+
fontSize: "0.9rem",
|
| 62 |
+
}}
|
| 63 |
+
>
|
| 64 |
+
<input type="checkbox" defaultChecked /> Mostrar composição da
|
| 65 |
+
parcela
|
| 66 |
+
</label>
|
| 67 |
+
<label
|
| 68 |
+
style={{
|
| 69 |
+
display: "flex",
|
| 70 |
+
alignItems: "center",
|
| 71 |
+
gap: "8px",
|
| 72 |
+
fontSize: "0.9rem",
|
| 73 |
+
}}
|
| 74 |
+
>
|
| 75 |
+
<input type="checkbox" defaultChecked /> Mostrar memória de
|
| 76 |
+
cálculo expandida
|
| 77 |
+
</label>
|
| 78 |
+
<label
|
| 79 |
+
style={{
|
| 80 |
+
display: "flex",
|
| 81 |
+
alignItems: "center",
|
| 82 |
+
gap: "8px",
|
| 83 |
+
fontSize: "0.9rem",
|
| 84 |
+
}}
|
| 85 |
+
>
|
| 86 |
+
<input type="checkbox" /> Mostrar cronograma mensal completo
|
| 87 |
+
</label>
|
| 88 |
+
<label
|
| 89 |
+
style={{
|
| 90 |
+
display: "flex",
|
| 91 |
+
alignItems: "center",
|
| 92 |
+
gap: "8px",
|
| 93 |
+
fontSize: "0.9rem",
|
| 94 |
+
}}
|
| 95 |
+
>
|
| 96 |
+
<input type="checkbox" defaultChecked /> Destacar números
|
| 97 |
+
absolutos vs percentuais
|
| 98 |
+
</label>
|
| 99 |
+
<label
|
| 100 |
+
style={{
|
| 101 |
+
display: "flex",
|
| 102 |
+
alignItems: "center",
|
| 103 |
+
gap: "8px",
|
| 104 |
+
fontSize: "0.9rem",
|
| 105 |
+
}}
|
| 106 |
+
>
|
| 107 |
+
<input type="checkbox" /> Visão consolidada por múltiplas cotas
|
| 108 |
+
</label>
|
| 109 |
+
</div>
|
| 110 |
+
</Card>
|
| 111 |
+
|
| 112 |
+
<div
|
| 113 |
+
style={{
|
| 114 |
+
display: "flex",
|
| 115 |
+
justifyContent: "flex-end",
|
| 116 |
+
gap: "12px",
|
| 117 |
+
marginTop: "16px",
|
| 118 |
+
}}
|
| 119 |
+
>
|
| 120 |
+
<button className="btn btn-primary">Aplicar Configurações</button>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
</div>
|
| 124 |
+
);
|
| 125 |
+
}
|
apps/web_comercial/src/app/favicon.ico
ADDED
|
|
apps/web_comercial/src/app/globals.css
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import "tailwindcss";
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
--background: #f3f0eb;
|
| 5 |
+
--foreground: #1c2733;
|
| 6 |
+
--bg-main: #ece7e0;
|
| 7 |
+
--bg-panel: #ffffff;
|
| 8 |
+
--bg-panel-soft: #f8f5f1;
|
| 9 |
+
--bg-panel-accent: #edf4fb;
|
| 10 |
+
--bg-panel-dark: #0f2743;
|
| 11 |
+
--bg-sidebar: #09213d;
|
| 12 |
+
--border-color: #d7d2c8;
|
| 13 |
+
--border-strong: #c5bcae;
|
| 14 |
+
--text-main: #1c2733;
|
| 15 |
+
--text-muted: #667285;
|
| 16 |
+
--text-soft: #8f9aac;
|
| 17 |
+
--color-brand-dark: #0d2d52;
|
| 18 |
+
--color-brand-accent: #ec5b13;
|
| 19 |
+
--color-brand-accent-strong: #cb4610;
|
| 20 |
+
--color-brand-light: #dcebf7;
|
| 21 |
+
--status-positive: #1c7c54;
|
| 22 |
+
--status-warning: #9a6700;
|
| 23 |
+
--status-negative: #c2410c;
|
| 24 |
+
--status-info: #2563eb;
|
| 25 |
+
--shadow-panel: 0 10px 24px rgba(11, 27, 49, 0.06);
|
| 26 |
+
--shadow-soft: 0 2px 6px rgba(15, 39, 67, 0.08);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
@theme inline {
|
| 30 |
+
--color-background: var(--background);
|
| 31 |
+
--color-foreground: var(--foreground);
|
| 32 |
+
--font-sans: "Public Sans", "IBM Plex Sans", "Aptos", "Segoe UI", sans-serif;
|
| 33 |
+
--font-mono: "SFMono-Regular", "Menlo", "Monaco", "Consolas", monospace;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
* {
|
| 37 |
+
box-sizing: border-box;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
html,
|
| 41 |
+
body {
|
| 42 |
+
min-height: 100%;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
body {
|
| 46 |
+
margin: 0;
|
| 47 |
+
background:
|
| 48 |
+
linear-gradient(180deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0))
|
| 49 |
+
0 0 / 100% 180px no-repeat,
|
| 50 |
+
var(--background);
|
| 51 |
+
color: var(--text-main);
|
| 52 |
+
font-family: var(--font-sans);
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
a {
|
| 56 |
+
color: inherit;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
button,
|
| 60 |
+
input,
|
| 61 |
+
select,
|
| 62 |
+
textarea {
|
| 63 |
+
font: inherit;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.page-kpi-strip {
|
| 67 |
+
display: grid;
|
| 68 |
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
| 69 |
+
gap: 12px;
|
| 70 |
+
margin-bottom: 20px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.executive-banner {
|
| 74 |
+
display: flex;
|
| 75 |
+
justify-content: space-between;
|
| 76 |
+
gap: 16px;
|
| 77 |
+
align-items: center;
|
| 78 |
+
padding: 14px 18px;
|
| 79 |
+
border: 1px solid var(--border-color);
|
| 80 |
+
border-left: 4px solid var(--color-brand-accent);
|
| 81 |
+
border-radius: 8px;
|
| 82 |
+
background: linear-gradient(90deg, rgba(236, 91, 19, 0.08), rgba(13, 45, 82, 0.04));
|
| 83 |
+
color: var(--text-main);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.executive-banner strong {
|
| 87 |
+
color: var(--color-brand-dark);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.executive-banner__meta {
|
| 91 |
+
display: flex;
|
| 92 |
+
flex-wrap: wrap;
|
| 93 |
+
gap: 8px;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.metric-chip,
|
| 97 |
+
.status-chip {
|
| 98 |
+
display: inline-flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 6px;
|
| 101 |
+
min-height: 30px;
|
| 102 |
+
padding: 0 10px;
|
| 103 |
+
border-radius: 999px;
|
| 104 |
+
font-size: 0.73rem;
|
| 105 |
+
font-weight: 700;
|
| 106 |
+
letter-spacing: 0.04em;
|
| 107 |
+
text-transform: uppercase;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.metric-chip {
|
| 111 |
+
background: rgba(13, 45, 82, 0.08);
|
| 112 |
+
color: var(--color-brand-dark);
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.status-chip {
|
| 116 |
+
border: 1px solid transparent;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.status-chip.status-live {
|
| 120 |
+
background: rgba(28, 124, 84, 0.1);
|
| 121 |
+
color: var(--status-positive);
|
| 122 |
+
border-color: rgba(28, 124, 84, 0.18);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.status-chip.status-warning {
|
| 126 |
+
background: rgba(154, 103, 0, 0.1);
|
| 127 |
+
color: var(--status-warning);
|
| 128 |
+
border-color: rgba(154, 103, 0, 0.2);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.status-chip.status-info {
|
| 132 |
+
background: rgba(37, 99, 235, 0.08);
|
| 133 |
+
color: var(--status-info);
|
| 134 |
+
border-color: rgba(37, 99, 235, 0.18);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
.section-grid {
|
| 138 |
+
display: grid;
|
| 139 |
+
gap: 20px;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.section-grid--2 {
|
| 143 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.section-grid--3 {
|
| 147 |
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.section-grid--simulator {
|
| 151 |
+
grid-template-columns: minmax(0, 1.75fr) minmax(320px, 0.95fr);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
.section-stack {
|
| 155 |
+
display: flex;
|
| 156 |
+
flex-direction: column;
|
| 157 |
+
gap: 20px;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.field-grid {
|
| 161 |
+
display: grid;
|
| 162 |
+
gap: 16px;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.field-grid--2 {
|
| 166 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.field-grid--3 {
|
| 170 |
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.field-grid--4 {
|
| 174 |
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.summary-rail {
|
| 178 |
+
display: flex;
|
| 179 |
+
flex-direction: column;
|
| 180 |
+
gap: 20px;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.plain-note {
|
| 184 |
+
margin: 0;
|
| 185 |
+
font-size: 0.84rem;
|
| 186 |
+
color: var(--text-muted);
|
| 187 |
+
line-height: 1.55;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.inline-toggle {
|
| 191 |
+
display: flex;
|
| 192 |
+
align-items: center;
|
| 193 |
+
gap: 10px;
|
| 194 |
+
font-size: 0.84rem;
|
| 195 |
+
color: var(--text-main);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
.table-note {
|
| 199 |
+
display: flex;
|
| 200 |
+
justify-content: space-between;
|
| 201 |
+
gap: 12px;
|
| 202 |
+
padding: 12px 16px;
|
| 203 |
+
border-top: 1px solid var(--border-color);
|
| 204 |
+
background: var(--bg-panel-soft);
|
| 205 |
+
color: var(--text-muted);
|
| 206 |
+
font-size: 0.78rem;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.sheet-frame {
|
| 210 |
+
background: var(--bg-panel);
|
| 211 |
+
border: 1px solid var(--border-strong);
|
| 212 |
+
border-radius: 10px;
|
| 213 |
+
box-shadow: var(--shadow-panel);
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.sheet-section-title {
|
| 217 |
+
display: flex;
|
| 218 |
+
align-items: center;
|
| 219 |
+
justify-content: space-between;
|
| 220 |
+
gap: 12px;
|
| 221 |
+
margin-bottom: 14px;
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
.sheet-section-title h2,
|
| 225 |
+
.sheet-section-title h3 {
|
| 226 |
+
margin: 0;
|
| 227 |
+
font-size: 1rem;
|
| 228 |
+
font-weight: 700;
|
| 229 |
+
color: var(--color-brand-dark);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.muted-divider {
|
| 233 |
+
height: 1px;
|
| 234 |
+
background: var(--border-color);
|
| 235 |
+
margin: 8px 0 0;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
@media (max-width: 1180px) {
|
| 239 |
+
.section-grid--simulator {
|
| 240 |
+
grid-template-columns: 1fr;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.field-grid--4 {
|
| 244 |
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
| 245 |
+
}
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
@media (max-width: 768px) {
|
| 249 |
+
.executive-banner {
|
| 250 |
+
flex-direction: column;
|
| 251 |
+
align-items: flex-start;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.field-grid--2,
|
| 255 |
+
.field-grid--3,
|
| 256 |
+
.field-grid--4 {
|
| 257 |
+
grid-template-columns: 1fr;
|
| 258 |
+
}
|
| 259 |
+
}
|
apps/web_comercial/src/app/layout.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Metadata } from "next";
|
| 2 |
+
import { Public_Sans } from "next/font/google";
|
| 3 |
+
import { AppLayout } from "@/components/layout/AppLayout";
|
| 4 |
+
import "./globals.css";
|
| 5 |
+
import "@/components/layout/layout.css";
|
| 6 |
+
import "@/components/ui/ui.css";
|
| 7 |
+
|
| 8 |
+
const publicSans = Public_Sans({
|
| 9 |
+
subsets: ["latin"],
|
| 10 |
+
display: "swap",
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
export const metadata: Metadata = {
|
| 14 |
+
title: "Apex Consorcios | Simulador",
|
| 15 |
+
description: "Simulador deterministico de consorcio imobiliario",
|
| 16 |
+
};
|
| 17 |
+
|
| 18 |
+
export default function RootLayout({
|
| 19 |
+
children,
|
| 20 |
+
}: Readonly<{
|
| 21 |
+
children: React.ReactNode;
|
| 22 |
+
}>) {
|
| 23 |
+
return (
|
| 24 |
+
<html lang="pt-BR">
|
| 25 |
+
<body className={`${publicSans.className} antialiased`}>
|
| 26 |
+
<AppLayout>{children}</AppLayout>
|
| 27 |
+
</body>
|
| 28 |
+
</html>
|
| 29 |
+
);
|
| 30 |
+
}
|
apps/web_comercial/src/app/mercado-secundario/page.tsx
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useMemo, useState } from "react";
|
| 4 |
+
import {
|
| 5 |
+
listarPerfisFluxoScpDefault,
|
| 6 |
+
simularModoConversaoScpConsorcio,
|
| 7 |
+
simularSecundarioComPerfilScp,
|
| 8 |
+
type ResultadoSimulacaoConversaoScpConsorcio,
|
| 9 |
+
type ResultadoSimulacaoSecundarioComPerfil,
|
| 10 |
+
} from "@simulador-consorcio/simulation-engine";
|
| 11 |
+
import { Card, DataCard } from "@/components/ui/Card";
|
| 12 |
+
import { Input, Select } from "@/components/ui/FormControls";
|
| 13 |
+
import {
|
| 14 |
+
Table,
|
| 15 |
+
TableBody,
|
| 16 |
+
TableCell,
|
| 17 |
+
TableHead,
|
| 18 |
+
TableHeader,
|
| 19 |
+
TableRow,
|
| 20 |
+
} from "@/components/ui/Table";
|
| 21 |
+
|
| 22 |
+
const catalogoDefault = listarPerfisFluxoScpDefault();
|
| 23 |
+
type ModoSimulacao = "secundario_padrao" | "conversao_scp_imovel_consorcio";
|
| 24 |
+
type ResultadoTela =
|
| 25 |
+
| ResultadoSimulacaoSecundarioComPerfil
|
| 26 |
+
| ResultadoSimulacaoConversaoScpConsorcio;
|
| 27 |
+
|
| 28 |
+
function formatCurrency(value: number) {
|
| 29 |
+
return new Intl.NumberFormat("pt-BR", {
|
| 30 |
+
style: "currency",
|
| 31 |
+
currency: "BRL",
|
| 32 |
+
}).format(value);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
function formatPercent(value: number | null | undefined) {
|
| 36 |
+
if (value === null || value === undefined) {
|
| 37 |
+
return "--";
|
| 38 |
+
}
|
| 39 |
+
return `${(value * 100).toFixed(2)}%`;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export default function MercadoSecundarioPage() {
|
| 43 |
+
const activeClient =
|
| 44 |
+
catalogoDefault.clients.find((client) => client.status === "ativo") ||
|
| 45 |
+
catalogoDefault.clients[0];
|
| 46 |
+
|
| 47 |
+
const [modoSimulacao, setModoSimulacao] =
|
| 48 |
+
useState<ModoSimulacao>("secundario_padrao");
|
| 49 |
+
const [clienteId, setClienteId] = useState(activeClient.client_id);
|
| 50 |
+
const [patternId, setPatternId] = useState(activeClient.default_pattern_id);
|
| 51 |
+
const [novoClienteId, setNovoClienteId] = useState("");
|
| 52 |
+
const [novoClienteNome, setNovoClienteNome] = useState("");
|
| 53 |
+
const [escalaFluxo, setEscalaFluxo] = useState(1);
|
| 54 |
+
const [deslocamentoMeses, setDeslocamentoMeses] = useState(0);
|
| 55 |
+
const [portabilidadeContempladaPct, setPortabilidadeContempladaPct] =
|
| 56 |
+
useState(1);
|
| 57 |
+
const [portabilidadeNaoContempladaPct, setPortabilidadeNaoContempladaPct] =
|
| 58 |
+
useState(0.5);
|
| 59 |
+
const [feePct, setFeePct] = useState(2);
|
| 60 |
+
const [volumeContratado, setVolumeContratado] = useState(300000);
|
| 61 |
+
const [percentualCartasReduzidaPct, setPercentualCartasReduzidaPct] =
|
| 62 |
+
useState(100);
|
| 63 |
+
const [mesesPosContemplacao, setMesesPosContemplacao] = useState(0);
|
| 64 |
+
const [valorImovelBase, setValorImovelBase] = useState(600000);
|
| 65 |
+
const [descontoImovelPct, setDescontoImovelPct] = useState(15);
|
| 66 |
+
const [percentualEntradaImovelPct, setPercentualEntradaImovelPct] =
|
| 67 |
+
useState(25);
|
| 68 |
+
const [agioConsorcioPct, setAgioConsorcioPct] = useState(10);
|
| 69 |
+
const [taxaCustosTransacaoPct, setTaxaCustosTransacaoPct] = useState(2);
|
| 70 |
+
const [resultado, setResultado] = useState<ResultadoTela | null>(null);
|
| 71 |
+
const [erro, setErro] = useState<string | null>(null);
|
| 72 |
+
|
| 73 |
+
const clienteSelecionado = useMemo(
|
| 74 |
+
() => catalogoDefault.clients.find((client) => client.client_id === clienteId),
|
| 75 |
+
[clienteId]
|
| 76 |
+
);
|
| 77 |
+
|
| 78 |
+
const resumoFluxo = useMemo(() => {
|
| 79 |
+
if (!resultado) {
|
| 80 |
+
return null;
|
| 81 |
+
}
|
| 82 |
+
return resultado.fluxo_cliente.pontos.slice(0, 12);
|
| 83 |
+
}, [resultado]);
|
| 84 |
+
|
| 85 |
+
const handleClienteChange = (nextClientId: string) => {
|
| 86 |
+
setClienteId(nextClientId);
|
| 87 |
+
const client = catalogoDefault.clients.find((item) => item.client_id === nextClientId);
|
| 88 |
+
if (client) {
|
| 89 |
+
setPatternId(client.default_pattern_id);
|
| 90 |
+
}
|
| 91 |
+
};
|
| 92 |
+
|
| 93 |
+
const handleSimular = () => {
|
| 94 |
+
try {
|
| 95 |
+
setErro(null);
|
| 96 |
+
const baseInput = {
|
| 97 |
+
client_id: clienteId,
|
| 98 |
+
pattern_id: patternId,
|
| 99 |
+
novo_client_id: novoClienteId || undefined,
|
| 100 |
+
novo_client_name: novoClienteNome || undefined,
|
| 101 |
+
escala_fluxo: escalaFluxo,
|
| 102 |
+
deslocamento_meses: deslocamentoMeses,
|
| 103 |
+
portabilidade_percentual_contemplada: portabilidadeContempladaPct / 100,
|
| 104 |
+
portabilidade_percentual_nao_contemplada:
|
| 105 |
+
portabilidadeNaoContempladaPct / 100,
|
| 106 |
+
fee_percentual: feePct / 100,
|
| 107 |
+
volume_contratado: volumeContratado,
|
| 108 |
+
percentual_cartas_parcela_reduzida: percentualCartasReduzidaPct / 100,
|
| 109 |
+
meses_pos_ultima_contemplacao: mesesPosContemplacao,
|
| 110 |
+
};
|
| 111 |
+
const simulacao =
|
| 112 |
+
modoSimulacao === "conversao_scp_imovel_consorcio"
|
| 113 |
+
? simularModoConversaoScpConsorcio({
|
| 114 |
+
...baseInput,
|
| 115 |
+
valor_imovel_base: valorImovelBase,
|
| 116 |
+
desconto_imovel_percentual: descontoImovelPct / 100,
|
| 117 |
+
percentual_entrada_imovel: percentualEntradaImovelPct / 100,
|
| 118 |
+
agio_consorcio_percentual: agioConsorcioPct / 100,
|
| 119 |
+
taxa_custos_transacao_percentual: taxaCustosTransacaoPct / 100,
|
| 120 |
+
})
|
| 121 |
+
: simularSecundarioComPerfilScp(baseInput);
|
| 122 |
+
setResultado(simulacao);
|
| 123 |
+
} catch (simulationError) {
|
| 124 |
+
const message =
|
| 125 |
+
simulationError instanceof Error
|
| 126 |
+
? simulationError.message
|
| 127 |
+
: "Falha ao simular cenário do mercado secundário.";
|
| 128 |
+
setErro(message);
|
| 129 |
+
setResultado(null);
|
| 130 |
+
}
|
| 131 |
+
};
|
| 132 |
+
|
| 133 |
+
return (
|
| 134 |
+
<div className="module-container">
|
| 135 |
+
<div
|
| 136 |
+
className="module-header"
|
| 137 |
+
style={{
|
| 138 |
+
display: "flex",
|
| 139 |
+
justifyContent: "space-between",
|
| 140 |
+
alignItems: "center",
|
| 141 |
+
}}
|
| 142 |
+
>
|
| 143 |
+
<div>
|
| 144 |
+
<h1 className="module-title">Mercado Secundário</h1>
|
| 145 |
+
<p className="module-subtitle">
|
| 146 |
+
Simulação parametrizada por cliente SCP com base nos padrões de fluxo
|
| 147 |
+
entregues.
|
| 148 |
+
</p>
|
| 149 |
+
</div>
|
| 150 |
+
<div style={{ display: "flex", gap: "12px" }}>
|
| 151 |
+
<button className="btn btn-secondary">Exportar Relatório</button>
|
| 152 |
+
<button className="btn btn-primary" onClick={handleSimular}>
|
| 153 |
+
Simular Cenário
|
| 154 |
+
</button>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<div className="page-kpi-strip">
|
| 159 |
+
<DataCard
|
| 160 |
+
label="Carteira"
|
| 161 |
+
value={clienteSelecionado?.client_name ?? "Cliente SCP"}
|
| 162 |
+
subvalue={`Padrão ${patternId}`}
|
| 163 |
+
/>
|
| 164 |
+
<DataCard
|
| 165 |
+
label="Modo"
|
| 166 |
+
value={
|
| 167 |
+
modoSimulacao === "conversao_scp_imovel_consorcio"
|
| 168 |
+
? "Conversão SCP"
|
| 169 |
+
: "Secundário padrão"
|
| 170 |
+
}
|
| 171 |
+
subvalue="Fluxo operacional por cliente"
|
| 172 |
+
highlight
|
| 173 |
+
/>
|
| 174 |
+
<DataCard
|
| 175 |
+
label="Volume contratado"
|
| 176 |
+
value={formatCurrency(volumeContratado)}
|
| 177 |
+
subvalue={`${percentualCartasReduzidaPct}% da carteira reduzida`}
|
| 178 |
+
/>
|
| 179 |
+
<DataCard
|
| 180 |
+
label="Fee"
|
| 181 |
+
value={`${feePct.toFixed(2)}%`}
|
| 182 |
+
subvalue={`Deslocamento ${deslocamentoMeses} meses`}
|
| 183 |
+
/>
|
| 184 |
+
</div>
|
| 185 |
+
|
| 186 |
+
<div className="executive-banner">
|
| 187 |
+
<div>
|
| 188 |
+
<strong>Mercado secundário com leitura operacional.</strong> A tela
|
| 189 |
+
combina fluxo SCP, portabilidade e conversão para compra de imóvel em
|
| 190 |
+
uma visão única de decisão.
|
| 191 |
+
</div>
|
| 192 |
+
<div className="executive-banner__meta">
|
| 193 |
+
<span className="metric-chip">Fluxo cliente</span>
|
| 194 |
+
<span className="metric-chip">Resultado APEX</span>
|
| 195 |
+
<span className="metric-chip">Retorno anual</span>
|
| 196 |
+
</div>
|
| 197 |
+
</div>
|
| 198 |
+
|
| 199 |
+
<div
|
| 200 |
+
className="module-content"
|
| 201 |
+
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
|
| 202 |
+
>
|
| 203 |
+
<Card title="Cliente e Padrão de Fluxo SCP">
|
| 204 |
+
<div
|
| 205 |
+
style={{
|
| 206 |
+
display: "grid",
|
| 207 |
+
gridTemplateColumns: "repeat(4, minmax(180px, 1fr))",
|
| 208 |
+
gap: "16px",
|
| 209 |
+
}}
|
| 210 |
+
>
|
| 211 |
+
<Select
|
| 212 |
+
label="Modo de Simulação"
|
| 213 |
+
value={modoSimulacao}
|
| 214 |
+
onChange={(event) =>
|
| 215 |
+
setModoSimulacao(event.target.value as ModoSimulacao)
|
| 216 |
+
}
|
| 217 |
+
options={[
|
| 218 |
+
{ value: "secundario_padrao", label: "Secundário padrão" },
|
| 219 |
+
{
|
| 220 |
+
value: "conversao_scp_imovel_consorcio",
|
| 221 |
+
label: "Conversão SCP -> Imóvel + Secundário",
|
| 222 |
+
},
|
| 223 |
+
]}
|
| 224 |
+
/>
|
| 225 |
+
<Select
|
| 226 |
+
label="Cliente Default"
|
| 227 |
+
value={clienteId}
|
| 228 |
+
onChange={(event) => handleClienteChange(event.target.value)}
|
| 229 |
+
options={catalogoDefault.clients.map((client) => ({
|
| 230 |
+
value: client.client_id,
|
| 231 |
+
label: `${client.client_name} (${client.status})`,
|
| 232 |
+
}))}
|
| 233 |
+
/>
|
| 234 |
+
<Select
|
| 235 |
+
label="Padrão de Fluxo"
|
| 236 |
+
value={patternId}
|
| 237 |
+
onChange={(event) => setPatternId(event.target.value)}
|
| 238 |
+
options={catalogoDefault.patterns.map((pattern) => ({
|
| 239 |
+
value: pattern.id,
|
| 240 |
+
label: `${pattern.nome} (${pattern.pontos} pontos)`,
|
| 241 |
+
}))}
|
| 242 |
+
/>
|
| 243 |
+
<Input
|
| 244 |
+
label="Novo Cliente ID (opcional)"
|
| 245 |
+
value={novoClienteId}
|
| 246 |
+
onChange={(event) => setNovoClienteId(event.target.value)}
|
| 247 |
+
placeholder="cliente_scp_001"
|
| 248 |
+
/>
|
| 249 |
+
<Input
|
| 250 |
+
label="Novo Cliente Nome (opcional)"
|
| 251 |
+
value={novoClienteNome}
|
| 252 |
+
onChange={(event) => setNovoClienteNome(event.target.value)}
|
| 253 |
+
placeholder="Cliente SCP 001"
|
| 254 |
+
/>
|
| 255 |
+
<Input
|
| 256 |
+
label="Escala de Fluxo"
|
| 257 |
+
type="number"
|
| 258 |
+
value={escalaFluxo}
|
| 259 |
+
onChange={(event) => setEscalaFluxo(Number(event.target.value || 0))}
|
| 260 |
+
step="0.1"
|
| 261 |
+
/>
|
| 262 |
+
<Input
|
| 263 |
+
label="Deslocamento (meses)"
|
| 264 |
+
type="number"
|
| 265 |
+
value={deslocamentoMeses}
|
| 266 |
+
onChange={(event) =>
|
| 267 |
+
setDeslocamentoMeses(Number(event.target.value || 0))
|
| 268 |
+
}
|
| 269 |
+
step="1"
|
| 270 |
+
/>
|
| 271 |
+
<Input
|
| 272 |
+
label="Portabilidade Contemplada"
|
| 273 |
+
type="number"
|
| 274 |
+
value={portabilidadeContempladaPct}
|
| 275 |
+
onChange={(event) =>
|
| 276 |
+
setPortabilidadeContempladaPct(Number(event.target.value || 0))
|
| 277 |
+
}
|
| 278 |
+
suffix="%"
|
| 279 |
+
/>
|
| 280 |
+
<Input
|
| 281 |
+
label="Portabilidade Não Contemplada"
|
| 282 |
+
type="number"
|
| 283 |
+
value={portabilidadeNaoContempladaPct}
|
| 284 |
+
onChange={(event) =>
|
| 285 |
+
setPortabilidadeNaoContempladaPct(Number(event.target.value || 0))
|
| 286 |
+
}
|
| 287 |
+
suffix="%"
|
| 288 |
+
/>
|
| 289 |
+
<Input
|
| 290 |
+
label="Fee"
|
| 291 |
+
type="number"
|
| 292 |
+
value={feePct}
|
| 293 |
+
onChange={(event) => setFeePct(Number(event.target.value || 0))}
|
| 294 |
+
suffix="%"
|
| 295 |
+
/>
|
| 296 |
+
<Input
|
| 297 |
+
label="Volume Contratado"
|
| 298 |
+
type="number"
|
| 299 |
+
value={volumeContratado}
|
| 300 |
+
onChange={(event) =>
|
| 301 |
+
setVolumeContratado(Number(event.target.value || 0))
|
| 302 |
+
}
|
| 303 |
+
prefix="R$"
|
| 304 |
+
/>
|
| 305 |
+
<Input
|
| 306 |
+
label="% Cartas com Parcela Reduzida"
|
| 307 |
+
type="number"
|
| 308 |
+
value={percentualCartasReduzidaPct}
|
| 309 |
+
onChange={(event) =>
|
| 310 |
+
setPercentualCartasReduzidaPct(Number(event.target.value || 0))
|
| 311 |
+
}
|
| 312 |
+
suffix="%"
|
| 313 |
+
/>
|
| 314 |
+
<Input
|
| 315 |
+
label="Meses Pós Última Contemplação"
|
| 316 |
+
type="number"
|
| 317 |
+
value={mesesPosContemplacao}
|
| 318 |
+
onChange={(event) =>
|
| 319 |
+
setMesesPosContemplacao(Number(event.target.value || 0))
|
| 320 |
+
}
|
| 321 |
+
step="1"
|
| 322 |
+
/>
|
| 323 |
+
{modoSimulacao === "conversao_scp_imovel_consorcio" && (
|
| 324 |
+
<>
|
| 325 |
+
<Input
|
| 326 |
+
label="Valor Base do Imóvel"
|
| 327 |
+
type="number"
|
| 328 |
+
value={valorImovelBase}
|
| 329 |
+
onChange={(event) =>
|
| 330 |
+
setValorImovelBase(Number(event.target.value || 0))
|
| 331 |
+
}
|
| 332 |
+
prefix="R$"
|
| 333 |
+
/>
|
| 334 |
+
<Input
|
| 335 |
+
label="Desconto Imóvel"
|
| 336 |
+
type="number"
|
| 337 |
+
value={descontoImovelPct}
|
| 338 |
+
onChange={(event) =>
|
| 339 |
+
setDescontoImovelPct(Number(event.target.value || 0))
|
| 340 |
+
}
|
| 341 |
+
suffix="%"
|
| 342 |
+
/>
|
| 343 |
+
<Input
|
| 344 |
+
label="Entrada da Compra"
|
| 345 |
+
type="number"
|
| 346 |
+
value={percentualEntradaImovelPct}
|
| 347 |
+
onChange={(event) =>
|
| 348 |
+
setPercentualEntradaImovelPct(Number(event.target.value || 0))
|
| 349 |
+
}
|
| 350 |
+
suffix="%"
|
| 351 |
+
/>
|
| 352 |
+
<Input
|
| 353 |
+
label="Ágio no Secundário"
|
| 354 |
+
type="number"
|
| 355 |
+
value={agioConsorcioPct}
|
| 356 |
+
onChange={(event) =>
|
| 357 |
+
setAgioConsorcioPct(Number(event.target.value || 0))
|
| 358 |
+
}
|
| 359 |
+
suffix="%"
|
| 360 |
+
/>
|
| 361 |
+
<Input
|
| 362 |
+
label="Custos de Transação"
|
| 363 |
+
type="number"
|
| 364 |
+
value={taxaCustosTransacaoPct}
|
| 365 |
+
onChange={(event) =>
|
| 366 |
+
setTaxaCustosTransacaoPct(Number(event.target.value || 0))
|
| 367 |
+
}
|
| 368 |
+
suffix="%"
|
| 369 |
+
/>
|
| 370 |
+
</>
|
| 371 |
+
)}
|
| 372 |
+
</div>
|
| 373 |
+
{clienteSelecionado && (
|
| 374 |
+
<p style={{ marginTop: "12px", color: "var(--text-muted)" }}>
|
| 375 |
+
Cliente selecionado: <strong>{clienteSelecionado.client_name}</strong>.
|
| 376 |
+
Padrão default: <code>{clienteSelecionado.default_pattern_id}</code>.
|
| 377 |
+
</p>
|
| 378 |
+
)}
|
| 379 |
+
{modoSimulacao === "conversao_scp_imovel_consorcio" && (
|
| 380 |
+
<p style={{ marginTop: "6px", color: "var(--text-muted)" }}>
|
| 381 |
+
Modo conversão é um cenário fechado parametrizado para SCP -> compra
|
| 382 |
+
de imóvel com desconto + operação no secundário.
|
| 383 |
+
</p>
|
| 384 |
+
)}
|
| 385 |
+
</Card>
|
| 386 |
+
|
| 387 |
+
{erro && (
|
| 388 |
+
<Card title="Erro de Simulação">
|
| 389 |
+
<p style={{ color: "var(--status-negative)" }}>{erro}</p>
|
| 390 |
+
</Card>
|
| 391 |
+
)}
|
| 392 |
+
|
| 393 |
+
<Card title="Resultado do Cenário" noPadding>
|
| 394 |
+
{resultado ? (
|
| 395 |
+
<div style={{ padding: "20px", display: "flex", flexDirection: "column", gap: "20px" }}>
|
| 396 |
+
<div
|
| 397 |
+
style={{
|
| 398 |
+
display: "grid",
|
| 399 |
+
gridTemplateColumns: "repeat(4, minmax(170px, 1fr))",
|
| 400 |
+
gap: "12px",
|
| 401 |
+
}}
|
| 402 |
+
>
|
| 403 |
+
<DataCard
|
| 404 |
+
label="Payback"
|
| 405 |
+
value={
|
| 406 |
+
resultado.payback_meses === null
|
| 407 |
+
? "Sem retorno"
|
| 408 |
+
: `${resultado.payback_meses} meses`
|
| 409 |
+
}
|
| 410 |
+
/>
|
| 411 |
+
<DataCard
|
| 412 |
+
label="TIR Mensal"
|
| 413 |
+
value={formatPercent(resultado.tir_mensal)}
|
| 414 |
+
/>
|
| 415 |
+
<DataCard
|
| 416 |
+
label="TIR Anual"
|
| 417 |
+
value={formatPercent(resultado.tir_anual)}
|
| 418 |
+
/>
|
| 419 |
+
<DataCard
|
| 420 |
+
label="Caixa Máximo Negativo"
|
| 421 |
+
value={formatCurrency(resultado.caixa_maximo_negativo)}
|
| 422 |
+
/>
|
| 423 |
+
<DataCard
|
| 424 |
+
label="Custo Portabilidade"
|
| 425 |
+
value={formatCurrency(resultado.custo_portabilidade)}
|
| 426 |
+
/>
|
| 427 |
+
<DataCard
|
| 428 |
+
label="Custo Fee 70%"
|
| 429 |
+
value={formatCurrency(resultado.custo_fee_parcela_reduzida)}
|
| 430 |
+
/>
|
| 431 |
+
<DataCard
|
| 432 |
+
label="Deduções Totais"
|
| 433 |
+
value={formatCurrency(resultado.custo_total_deducoes)}
|
| 434 |
+
/>
|
| 435 |
+
<DataCard
|
| 436 |
+
label="Cliente Simulado"
|
| 437 |
+
value={resultado.fluxo_cliente.client_name}
|
| 438 |
+
subvalue={resultado.fluxo_cliente.client_id}
|
| 439 |
+
/>
|
| 440 |
+
</div>
|
| 441 |
+
|
| 442 |
+
{"modo_conversao" in resultado && (
|
| 443 |
+
<Card title="Métricas do Modo Conversão">
|
| 444 |
+
<div
|
| 445 |
+
style={{
|
| 446 |
+
display: "grid",
|
| 447 |
+
gridTemplateColumns: "repeat(4, minmax(160px, 1fr))",
|
| 448 |
+
gap: "12px",
|
| 449 |
+
}}
|
| 450 |
+
>
|
| 451 |
+
<DataCard
|
| 452 |
+
label="Valor Imóvel Base"
|
| 453 |
+
value={formatCurrency(resultado.modo_conversao.valor_imovel_base)}
|
| 454 |
+
/>
|
| 455 |
+
<DataCard
|
| 456 |
+
label="Valor com Desconto"
|
| 457 |
+
value={formatCurrency(
|
| 458 |
+
resultado.modo_conversao.valor_imovel_com_desconto
|
| 459 |
+
)}
|
| 460 |
+
/>
|
| 461 |
+
<DataCard
|
| 462 |
+
label="Entrada Imóvel"
|
| 463 |
+
value={formatCurrency(
|
| 464 |
+
resultado.modo_conversao.valor_entrada_imovel
|
| 465 |
+
)}
|
| 466 |
+
/>
|
| 467 |
+
<DataCard
|
| 468 |
+
label="Crédito Consórcio"
|
| 469 |
+
value={formatCurrency(
|
| 470 |
+
resultado.modo_conversao.valor_credito_consorcio
|
| 471 |
+
)}
|
| 472 |
+
/>
|
| 473 |
+
<DataCard
|
| 474 |
+
label="Custo Transação"
|
| 475 |
+
value={formatCurrency(
|
| 476 |
+
resultado.modo_conversao.valor_custos_transacao
|
| 477 |
+
)}
|
| 478 |
+
/>
|
| 479 |
+
<DataCard
|
| 480 |
+
label="Investimento Conversão"
|
| 481 |
+
value={formatCurrency(
|
| 482 |
+
resultado.modo_conversao.investimento_total_conversao
|
| 483 |
+
)}
|
| 484 |
+
/>
|
| 485 |
+
<DataCard
|
| 486 |
+
label="Saída no Secundário"
|
| 487 |
+
value={formatCurrency(
|
| 488 |
+
resultado.modo_conversao.valor_saida_consorcio_secundario
|
| 489 |
+
)}
|
| 490 |
+
/>
|
| 491 |
+
<DataCard
|
| 492 |
+
label="Lucro Projetado"
|
| 493 |
+
value={formatCurrency(
|
| 494 |
+
resultado.modo_conversao.lucro_projetado_conversao
|
| 495 |
+
)}
|
| 496 |
+
subvalue={`ROI ${formatPercent(
|
| 497 |
+
resultado.modo_conversao.roi_projetado_conversao
|
| 498 |
+
)}`}
|
| 499 |
+
/>
|
| 500 |
+
</div>
|
| 501 |
+
</Card>
|
| 502 |
+
)}
|
| 503 |
+
|
| 504 |
+
<Table>
|
| 505 |
+
<TableHead>
|
| 506 |
+
<TableRow>
|
| 507 |
+
<TableHeader>Período</TableHeader>
|
| 508 |
+
<TableHeader style={{ textAlign: "right" }}>
|
| 509 |
+
Fluxo (R$)
|
| 510 |
+
</TableHeader>
|
| 511 |
+
</TableRow>
|
| 512 |
+
</TableHead>
|
| 513 |
+
<TableBody>
|
| 514 |
+
{(resumoFluxo || []).map((point) => (
|
| 515 |
+
<TableRow key={`${point.periodo}-${point.fluxo}`}>
|
| 516 |
+
<TableCell>{point.periodo}</TableCell>
|
| 517 |
+
<TableCell style={{ textAlign: "right", fontFamily: "monospace" }}>
|
| 518 |
+
{formatCurrency(point.fluxo)}
|
| 519 |
+
</TableCell>
|
| 520 |
+
</TableRow>
|
| 521 |
+
))}
|
| 522 |
+
</TableBody>
|
| 523 |
+
</Table>
|
| 524 |
+
</div>
|
| 525 |
+
) : (
|
| 526 |
+
<div style={{ padding: "20px", color: "var(--text-muted)" }}>
|
| 527 |
+
Selecione um cliente/padrão e clique em <strong>Simular Cenário</strong>.
|
| 528 |
+
</div>
|
| 529 |
+
)}
|
| 530 |
+
</Card>
|
| 531 |
+
</div>
|
| 532 |
+
</div>
|
| 533 |
+
);
|
| 534 |
+
}
|
apps/web_comercial/src/app/page.tsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { SimulatorForm } from "@/components/simulator/SimulatorForm";
|
| 2 |
+
import { DataCard } from "@/components/ui/Card";
|
| 3 |
+
|
| 4 |
+
export default function Home() {
|
| 5 |
+
return (
|
| 6 |
+
<div className="module-container">
|
| 7 |
+
<div className="module-header">
|
| 8 |
+
<h1 className="module-title">Simulador de Consórcio Imobiliário</h1>
|
| 9 |
+
<p className="module-subtitle">
|
| 10 |
+
Estrutura operacional inspirada na planilha Rodobens/BTG, com leitura
|
| 11 |
+
tabular, resumo executivo e preparação de proposta cliente.
|
| 12 |
+
</p>
|
| 13 |
+
</div>
|
| 14 |
+
|
| 15 |
+
<div className="page-kpi-strip">
|
| 16 |
+
<DataCard
|
| 17 |
+
label="Escopo"
|
| 18 |
+
value="Somente imóvel"
|
| 19 |
+
subvalue="Produto bloqueado para carteira imobiliária"
|
| 20 |
+
/>
|
| 21 |
+
<DataCard
|
| 22 |
+
label="Base operacional"
|
| 23 |
+
value="Workbook Rodobens"
|
| 24 |
+
subvalue="Degrau, Linear, Pontual e variantes reduzidas"
|
| 25 |
+
highlight
|
| 26 |
+
/>
|
| 27 |
+
<DataCard
|
| 28 |
+
label="Modo"
|
| 29 |
+
value="Operacional"
|
| 30 |
+
subvalue="Resumo geral, CET e memória de cálculo"
|
| 31 |
+
/>
|
| 32 |
+
<DataCard
|
| 33 |
+
label="Status"
|
| 34 |
+
value="Pronto para validação"
|
| 35 |
+
subvalue="Comparador, proposta, premissas e configurações ativos"
|
| 36 |
+
/>
|
| 37 |
+
</div>
|
| 38 |
+
|
| 39 |
+
<div className="executive-banner">
|
| 40 |
+
<div>
|
| 41 |
+
<strong>Leitura de planilha convertida em software.</strong> O foco
|
| 42 |
+
aqui não é marketing; é velocidade operacional, legibilidade numérica
|
| 43 |
+
e confiança para originar proposta.
|
| 44 |
+
</div>
|
| 45 |
+
<div className="executive-banner__meta">
|
| 46 |
+
<span className="status-chip status-live">Motor determinístico</span>
|
| 47 |
+
<span className="metric-chip">Resumo geral ativo</span>
|
| 48 |
+
<span className="metric-chip">CET e ao mês</span>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<div className="module-content">
|
| 53 |
+
<SimulatorForm />
|
| 54 |
+
</div>
|
| 55 |
+
</div>
|
| 56 |
+
);
|
| 57 |
+
}
|
apps/web_comercial/src/app/premissas/page.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Card, DataCard } from "@/components/ui/Card";
|
| 2 |
+
import { Input, Select } from "@/components/ui/FormControls";
|
| 3 |
+
|
| 4 |
+
export default function PremissasPage() {
|
| 5 |
+
return (
|
| 6 |
+
<div className="module-container">
|
| 7 |
+
<div className="module-header">
|
| 8 |
+
<h1 className="module-title">Premissas Operacionais</h1>
|
| 9 |
+
<p className="module-subtitle">
|
| 10 |
+
Parametrização central de regras, metas e datas que alimentam o motor
|
| 11 |
+
determinístico.
|
| 12 |
+
</p>
|
| 13 |
+
</div>
|
| 14 |
+
|
| 15 |
+
<div className="page-kpi-strip">
|
| 16 |
+
<DataCard label="Objetivo" value="Governança do cálculo" subvalue="Hipóteses macro e políticas de operação" />
|
| 17 |
+
<DataCard label="Critério" value="Payback e retorno" subvalue="Premissas usadas na leitura executiva" highlight />
|
| 18 |
+
<DataCard label="Impacto" value="Todas as rotas" subvalue="Simulador, comparador e proposta" />
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<div
|
| 22 |
+
className="module-content"
|
| 23 |
+
style={{ display: "flex", flexDirection: "column", gap: "24px" }}
|
| 24 |
+
>
|
| 25 |
+
<Card title="Hipóteses Macroeconômicas e Objetivos">
|
| 26 |
+
<div
|
| 27 |
+
style={{
|
| 28 |
+
display: "grid",
|
| 29 |
+
gridTemplateColumns: "1fr 1fr 1fr",
|
| 30 |
+
gap: "16px",
|
| 31 |
+
}}
|
| 32 |
+
>
|
| 33 |
+
<Input label="Retorno Alvo Anual" defaultValue={15} suffix="%" />
|
| 34 |
+
<Input label="Custo de Portabilidade" defaultValue={0} suffix="%" />
|
| 35 |
+
<Select
|
| 36 |
+
label="Objetivo da Operação"
|
| 37 |
+
options={[
|
| 38 |
+
{ label: "Formação de Patrimônio", value: "patrimonio" },
|
| 39 |
+
{ label: "Alavancagem", value: "alavancagem" },
|
| 40 |
+
]}
|
| 41 |
+
/>
|
| 42 |
+
<Select
|
| 43 |
+
label="Regra de Reajuste"
|
| 44 |
+
options={[
|
| 45 |
+
{ label: "INCC + IGP-M Anual", value: "incc" },
|
| 46 |
+
{ label: "Fixo", value: "fixo" },
|
| 47 |
+
]}
|
| 48 |
+
/>
|
| 49 |
+
<Input label="Payback Desejado" defaultValue={48} suffix=" Meses" />
|
| 50 |
+
</div>
|
| 51 |
+
</Card>
|
| 52 |
+
|
| 53 |
+
<Card title="Eventos e Faturamento">
|
| 54 |
+
<div
|
| 55 |
+
style={{
|
| 56 |
+
display: "grid",
|
| 57 |
+
gridTemplateColumns: "1fr 1fr",
|
| 58 |
+
gap: "16px",
|
| 59 |
+
}}
|
| 60 |
+
>
|
| 61 |
+
<Input label="Data do Próximo Sorteio" type="date" />
|
| 62 |
+
<Input label="Data da Próxima AGE" type="date" />
|
| 63 |
+
<Input label="Último Lance Vencedor" defaultValue={32} suffix="%" />
|
| 64 |
+
<Select
|
| 65 |
+
label="Política para Não Contemplados"
|
| 66 |
+
options={[
|
| 67 |
+
{ label: "Liquidar no secundário em 12m", value: "12m" },
|
| 68 |
+
{ label: "Manter até o fim", value: "fim" },
|
| 69 |
+
]}
|
| 70 |
+
/>
|
| 71 |
+
</div>
|
| 72 |
+
</Card>
|
| 73 |
+
|
| 74 |
+
<div
|
| 75 |
+
style={{
|
| 76 |
+
display: "flex",
|
| 77 |
+
justifyContent: "flex-end",
|
| 78 |
+
gap: "12px",
|
| 79 |
+
marginTop: "16px",
|
| 80 |
+
}}
|
| 81 |
+
>
|
| 82 |
+
<button className="btn btn-secondary">Descartar Alterações</button>
|
| 83 |
+
<button className="btn btn-primary">Salvar Premissas</button>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
</div>
|
| 87 |
+
);
|
| 88 |
+
}
|
apps/web_comercial/src/app/proposta/page.tsx
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { DataCard } from "@/components/ui/Card";
|
| 4 |
+
import {
|
| 5 |
+
Table,
|
| 6 |
+
TableBody,
|
| 7 |
+
TableCell,
|
| 8 |
+
TableHead,
|
| 9 |
+
TableHeader,
|
| 10 |
+
TableRow,
|
| 11 |
+
} from "@/components/ui/Table";
|
| 12 |
+
import { calcularSimulacao } from "@simulador-consorcio/simulation-engine";
|
| 13 |
+
import { useMemo } from "react";
|
| 14 |
+
|
| 15 |
+
function formatCurrency(value: number) {
|
| 16 |
+
return new Intl.NumberFormat("pt-BR", {
|
| 17 |
+
style: "currency",
|
| 18 |
+
currency: "BRL",
|
| 19 |
+
}).format(value);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export default function PropostaPage() {
|
| 23 |
+
// Configuração Fixa Simulada para a Proposta Executiva
|
| 24 |
+
const propostaData = useMemo(() => {
|
| 25 |
+
return calcularSimulacao({
|
| 26 |
+
credito_desejado: 500000,
|
| 27 |
+
parametros: {
|
| 28 |
+
id_plano: "PADRAO",
|
| 29 |
+
nome_plano: "Plano Padrão Imóvel",
|
| 30 |
+
prazo_meses: 180,
|
| 31 |
+
taxa_administracao_total: 0.15,
|
| 32 |
+
fundo_reserva_mensal: 0.0005,
|
| 33 |
+
seguro_prestamista: 0.035,
|
| 34 |
+
},
|
| 35 |
+
tem_lance: true,
|
| 36 |
+
lance_percentual: 0.3,
|
| 37 |
+
});
|
| 38 |
+
}, []);
|
| 39 |
+
|
| 40 |
+
return (
|
| 41 |
+
<div className="module-container" style={{ maxWidth: "960px", margin: "0 auto" }}>
|
| 42 |
+
<div className="page-kpi-strip">
|
| 43 |
+
<DataCard label="Formato" value="Proposta cliente" subvalue="Documento financeiro compacto" />
|
| 44 |
+
<DataCard label="Base" value="Simulação determinística" subvalue="Valores derivados do motor atual" highlight />
|
| 45 |
+
<DataCard label="Uso" value="PDF / reunião" subvalue="Leitura mais limpa que a visão operacional" />
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<div className="sheet-frame">
|
| 49 |
+
<div
|
| 50 |
+
className="module-header"
|
| 51 |
+
style={{
|
| 52 |
+
borderBottom: "2px solid var(--color-brand-dark)",
|
| 53 |
+
padding: "32px 32px 16px",
|
| 54 |
+
marginBottom: 0,
|
| 55 |
+
}}
|
| 56 |
+
>
|
| 57 |
+
<h1
|
| 58 |
+
className="module-title"
|
| 59 |
+
style={{
|
| 60 |
+
textAlign: "center",
|
| 61 |
+
color: "var(--color-brand-dark)",
|
| 62 |
+
textTransform: "uppercase",
|
| 63 |
+
letterSpacing: "1px",
|
| 64 |
+
}}
|
| 65 |
+
>
|
| 66 |
+
Proposta Personalizada
|
| 67 |
+
</h1>
|
| 68 |
+
<p className="module-subtitle" style={{ textAlign: "center" }}>
|
| 69 |
+
Apex Consórcios - Especialistas em Patrimônio
|
| 70 |
+
</p>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<div
|
| 74 |
+
className="module-content"
|
| 75 |
+
style={{
|
| 76 |
+
padding: "32px",
|
| 77 |
+
backgroundColor: "transparent",
|
| 78 |
+
border: "none",
|
| 79 |
+
boxShadow: "none",
|
| 80 |
+
}}
|
| 81 |
+
>
|
| 82 |
+
<div style={{ marginBottom: "32px" }}>
|
| 83 |
+
<h2
|
| 84 |
+
style={{
|
| 85 |
+
fontSize: "1.2rem",
|
| 86 |
+
color: "var(--color-brand-accent)",
|
| 87 |
+
borderBottom: "1px solid var(--border-color)",
|
| 88 |
+
paddingBottom: "8px",
|
| 89 |
+
marginBottom: "16px",
|
| 90 |
+
}}
|
| 91 |
+
>
|
| 92 |
+
Dados Gerais
|
| 93 |
+
</h2>
|
| 94 |
+
<div
|
| 95 |
+
style={{
|
| 96 |
+
display: "grid",
|
| 97 |
+
gridTemplateColumns: "1fr 1fr",
|
| 98 |
+
gap: "16px",
|
| 99 |
+
}}
|
| 100 |
+
>
|
| 101 |
+
<DataCard label="Cliente" value="João da Silva" />
|
| 102 |
+
<DataCard label="Especialista" value="Maria Oliveira" />
|
| 103 |
+
<DataCard
|
| 104 |
+
label="Data da Proposta"
|
| 105 |
+
value={new Date().toLocaleDateString("pt-BR")}
|
| 106 |
+
/>
|
| 107 |
+
<DataCard label="Validade" value="15 Dias" />
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<div style={{ marginBottom: "32px" }}>
|
| 112 |
+
<h2
|
| 113 |
+
style={{
|
| 114 |
+
fontSize: "1.2rem",
|
| 115 |
+
color: "var(--color-brand-accent)",
|
| 116 |
+
borderBottom: "1px solid var(--border-color)",
|
| 117 |
+
paddingBottom: "8px",
|
| 118 |
+
marginBottom: "16px",
|
| 119 |
+
}}
|
| 120 |
+
>
|
| 121 |
+
Plano Recomendado: Imóvel Padrão Linear
|
| 122 |
+
</h2>
|
| 123 |
+
<Table>
|
| 124 |
+
<TableHead>
|
| 125 |
+
<TableRow>
|
| 126 |
+
<TableHeader>Parâmetro</TableHeader>
|
| 127 |
+
<TableHeader style={{ textAlign: "right" }}>Valor</TableHeader>
|
| 128 |
+
</TableRow>
|
| 129 |
+
</TableHead>
|
| 130 |
+
<TableBody>
|
| 131 |
+
<TableRow>
|
| 132 |
+
<TableCell>Crédito Total</TableCell>
|
| 133 |
+
<TableCell style={{ textAlign: "right", fontWeight: 600 }}>
|
| 134 |
+
{formatCurrency(propostaData.credito)}
|
| 135 |
+
</TableCell>
|
| 136 |
+
</TableRow>
|
| 137 |
+
<TableRow>
|
| 138 |
+
<TableCell>Prazo Total</TableCell>
|
| 139 |
+
<TableCell style={{ textAlign: "right" }}>
|
| 140 |
+
{propostaData.prazo} Meses
|
| 141 |
+
</TableCell>
|
| 142 |
+
</TableRow>
|
| 143 |
+
<TableRow>
|
| 144 |
+
<TableCell>Parcela Base (S/ Redução)</TableCell>
|
| 145 |
+
<TableCell
|
| 146 |
+
style={{
|
| 147 |
+
textAlign: "right",
|
| 148 |
+
color: "var(--color-brand-accent)",
|
| 149 |
+
fontWeight: 600,
|
| 150 |
+
}}
|
| 151 |
+
>
|
| 152 |
+
{formatCurrency(propostaData.parcela_integral.total)}
|
| 153 |
+
</TableCell>
|
| 154 |
+
</TableRow>
|
| 155 |
+
<TableRow>
|
| 156 |
+
<TableCell>Taxa de Administração Total</TableCell>
|
| 157 |
+
<TableCell style={{ textAlign: "right" }}>15,00%</TableCell>
|
| 158 |
+
</TableRow>
|
| 159 |
+
<TableRow>
|
| 160 |
+
<TableCell>Fundo de Reserva</TableCell>
|
| 161 |
+
<TableCell style={{ textAlign: "right" }}>Mensal</TableCell>
|
| 162 |
+
</TableRow>
|
| 163 |
+
</TableBody>
|
| 164 |
+
</Table>
|
| 165 |
+
</div>
|
| 166 |
+
|
| 167 |
+
<div style={{ marginBottom: "32px" }}>
|
| 168 |
+
<h2
|
| 169 |
+
style={{
|
| 170 |
+
fontSize: "1.2rem",
|
| 171 |
+
color: "var(--color-brand-accent)",
|
| 172 |
+
borderBottom: "1px solid var(--border-color)",
|
| 173 |
+
paddingBottom: "8px",
|
| 174 |
+
marginBottom: "16px",
|
| 175 |
+
}}
|
| 176 |
+
>
|
| 177 |
+
Estimativa Pós Contemplação (Lance 30%)
|
| 178 |
+
</h2>
|
| 179 |
+
<div
|
| 180 |
+
style={{
|
| 181 |
+
display: "grid",
|
| 182 |
+
gridTemplateColumns: "1fr 1fr 1fr",
|
| 183 |
+
gap: "16px",
|
| 184 |
+
}}
|
| 185 |
+
>
|
| 186 |
+
<DataCard
|
| 187 |
+
label="Lance Embutido Estimado"
|
| 188 |
+
value={formatCurrency(propostaData.valor_lance || 0)}
|
| 189 |
+
/>
|
| 190 |
+
<DataCard
|
| 191 |
+
label="Crédito Disponível Líquido"
|
| 192 |
+
value={formatCurrency(
|
| 193 |
+
propostaData.credito - (propostaData.valor_lance || 0),
|
| 194 |
+
)}
|
| 195 |
+
highlight
|
| 196 |
+
/>
|
| 197 |
+
<DataCard
|
| 198 |
+
label="Prazo Restante Estimado"
|
| 199 |
+
value={`${propostaData.prazo_apos_lance || propostaData.prazo} Meses`}
|
| 200 |
+
/>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<div>
|
| 205 |
+
<h2
|
| 206 |
+
style={{
|
| 207 |
+
fontSize: "1.2rem",
|
| 208 |
+
color: "var(--color-brand-accent)",
|
| 209 |
+
borderBottom: "1px solid var(--border-color)",
|
| 210 |
+
paddingBottom: "8px",
|
| 211 |
+
marginBottom: "16px",
|
| 212 |
+
}}
|
| 213 |
+
>
|
| 214 |
+
Informações Adicionais
|
| 215 |
+
</h2>
|
| 216 |
+
<p
|
| 217 |
+
style={{
|
| 218 |
+
fontSize: "0.85rem",
|
| 219 |
+
color: "var(--text-muted)",
|
| 220 |
+
lineHeight: 1.5,
|
| 221 |
+
}}
|
| 222 |
+
>
|
| 223 |
+
Os valores apresentados acima representam uma estimativa
|
| 224 |
+
determinística de cenário futuro e não constituem garantia de
|
| 225 |
+
contemplação. O custo efetivo total não considera reajustes
|
| 226 |
+
monetários aplicáveis conforme contrato. A taxa administrativa já
|
| 227 |
+
engloba o período selecionado de parcelamento. A viabilidade é
|
| 228 |
+
baseada nas regras operacionais vigentes.
|
| 229 |
+
</p>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
);
|
| 235 |
+
}
|
apps/web_comercial/src/components/layout/AppLayout.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import { Sidebar } from "./Sidebar";
|
| 3 |
+
import { Topbar } from "./Topbar";
|
| 4 |
+
|
| 5 |
+
interface AppLayoutProps {
|
| 6 |
+
children: React.ReactNode;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
export function AppLayout({ children }: AppLayoutProps) {
|
| 10 |
+
return (
|
| 11 |
+
<div className="app-container">
|
| 12 |
+
<Sidebar />
|
| 13 |
+
<div className="app-main">
|
| 14 |
+
<Topbar />
|
| 15 |
+
<main className="app-content">{children}</main>
|
| 16 |
+
</div>
|
| 17 |
+
</div>
|
| 18 |
+
);
|
| 19 |
+
}
|
apps/web_comercial/src/components/layout/Sidebar.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import Link from "next/link";
|
| 4 |
+
import { usePathname } from "next/navigation";
|
| 5 |
+
|
| 6 |
+
const navigationItems = [
|
| 7 |
+
{ href: "/", label: "Simulador", meta: "Operacional" },
|
| 8 |
+
{
|
| 9 |
+
href: "/mercado-secundario",
|
| 10 |
+
label: "Mercado Secundário",
|
| 11 |
+
meta: "Portfólio",
|
| 12 |
+
},
|
| 13 |
+
{ href: "/comparador", label: "Comparador", meta: "Cenários" },
|
| 14 |
+
{ href: "/proposta", label: "Proposta", meta: "Cliente" },
|
| 15 |
+
{ href: "/premissas", label: "Premissas", meta: "Regras" },
|
| 16 |
+
{ href: "/configuracoes", label: "Configurações", meta: "Modos" },
|
| 17 |
+
];
|
| 18 |
+
|
| 19 |
+
export function Sidebar() {
|
| 20 |
+
const pathname = usePathname();
|
| 21 |
+
|
| 22 |
+
return (
|
| 23 |
+
<aside className="app-sidebar">
|
| 24 |
+
<div className="sidebar-header">
|
| 25 |
+
<p className="sidebar-kicker">Base Rodobens Imóvel</p>
|
| 26 |
+
<div className="logos">
|
| 27 |
+
<div className="logo-lockup">
|
| 28 |
+
<div className="logo-placeholder btg-style">BTG Pactual</div>
|
| 29 |
+
<span className="logo-caption">
|
| 30 |
+
Camada comercial e governança executiva.
|
| 31 |
+
</span>
|
| 32 |
+
</div>
|
| 33 |
+
<div className="logo-lockup">
|
| 34 |
+
<div className="logo-placeholder rodobens-style">Rodobens</div>
|
| 35 |
+
<span className="logo-caption">
|
| 36 |
+
Tabelas, premissas e padrões operacionais da carteira imobiliária.
|
| 37 |
+
</span>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
|
| 42 |
+
<nav className="sidebar-nav">
|
| 43 |
+
<div className="sidebar-section-label">Módulos</div>
|
| 44 |
+
<ul>
|
| 45 |
+
{navigationItems.map((item) => {
|
| 46 |
+
const isActive =
|
| 47 |
+
item.href === "/"
|
| 48 |
+
? pathname === item.href
|
| 49 |
+
: pathname?.startsWith(item.href);
|
| 50 |
+
|
| 51 |
+
return (
|
| 52 |
+
<li key={item.href}>
|
| 53 |
+
<Link
|
| 54 |
+
href={item.href}
|
| 55 |
+
className={`nav-link ${isActive ? "active" : ""}`}
|
| 56 |
+
>
|
| 57 |
+
<span>{item.label}</span>
|
| 58 |
+
<span className="nav-link__meta">{item.meta}</span>
|
| 59 |
+
</Link>
|
| 60 |
+
</li>
|
| 61 |
+
);
|
| 62 |
+
})}
|
| 63 |
+
</ul>
|
| 64 |
+
</nav>
|
| 65 |
+
|
| 66 |
+
<div className="sidebar-footer">
|
| 67 |
+
<strong>Workspace operacional</strong>
|
| 68 |
+
<span>Simulador interno, proposta cliente e mercado secundário.</span>
|
| 69 |
+
</div>
|
| 70 |
+
</aside>
|
| 71 |
+
);
|
| 72 |
+
}
|
apps/web_comercial/src/components/layout/Topbar.tsx
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { usePathname } from "next/navigation";
|
| 4 |
+
|
| 5 |
+
const routeConfig: Record<
|
| 6 |
+
string,
|
| 7 |
+
{ title: string; subtitle: string; validity: string }
|
| 8 |
+
> = {
|
| 9 |
+
"/": {
|
| 10 |
+
title: "Simulador imobiliário",
|
| 11 |
+
subtitle: "Montagem de proposta com leitura operacional em formato workbook.",
|
| 12 |
+
validity: "Vigência 31/10/2026",
|
| 13 |
+
},
|
| 14 |
+
"/mercado-secundario": {
|
| 15 |
+
title: "Mercado secundário",
|
| 16 |
+
subtitle: "Análise de fluxo, portabilidade, spread e retorno por cliente SCP.",
|
| 17 |
+
validity: "Ciclo março/2026",
|
| 18 |
+
},
|
| 19 |
+
"/comparador": {
|
| 20 |
+
title: "Comparador de cenários",
|
| 21 |
+
subtitle: "Confronto entre planos, prazo, lance e impacto de parcela.",
|
| 22 |
+
validity: "Revisão diária",
|
| 23 |
+
},
|
| 24 |
+
"/proposta": {
|
| 25 |
+
title: "Proposta personalizada",
|
| 26 |
+
subtitle: "Documento executivo orientado a PDF e apresentação ao cliente.",
|
| 27 |
+
validity: "Validade 15 dias",
|
| 28 |
+
},
|
| 29 |
+
"/premissas": {
|
| 30 |
+
title: "Premissas operacionais",
|
| 31 |
+
subtitle: "Hipóteses, gatilhos e regras que sustentam o cálculo determinístico.",
|
| 32 |
+
validity: "Baseline homologada",
|
| 33 |
+
},
|
| 34 |
+
"/configuracoes": {
|
| 35 |
+
title: "Configurações",
|
| 36 |
+
subtitle: "Modos de leitura, densidade e visibilidade de memória de cálculo.",
|
| 37 |
+
validity: "Perfil operacional",
|
| 38 |
+
},
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
export function Topbar() {
|
| 42 |
+
const pathname = usePathname();
|
| 43 |
+
const config =
|
| 44 |
+
routeConfig[pathname ?? "/"] ?? routeConfig["/"];
|
| 45 |
+
|
| 46 |
+
return (
|
| 47 |
+
<header className="app-topbar">
|
| 48 |
+
<div className="topbar-heading">
|
| 49 |
+
<span className="topbar-kicker">Consórcio imobiliário Rodobens</span>
|
| 50 |
+
<h1 className="topbar-title">{config.title}</h1>
|
| 51 |
+
<p className="topbar-subtitle">{config.subtitle}</p>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<div style={{ display: "grid", gap: "10px", justifyItems: "end" }}>
|
| 55 |
+
<div className="topbar-context">
|
| 56 |
+
<span className="context-pill">Parceiro Apex</span>
|
| 57 |
+
<span className="context-pill">Modo operacional</span>
|
| 58 |
+
<span className="context-pill highlight">BTG + Rodobens</span>
|
| 59 |
+
<span className="context-pill">{config.validity}</span>
|
| 60 |
+
</div>
|
| 61 |
+
<div className="topbar-actions">
|
| 62 |
+
<button className="btn btn-secondary">Visualizar layout</button>
|
| 63 |
+
<button className="btn btn-primary">Imprimir proposta</button>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</header>
|
| 67 |
+
);
|
| 68 |
+
}
|
apps/web_comercial/src/components/layout/layout.css
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.app-container {
|
| 2 |
+
display: flex;
|
| 3 |
+
min-height: 100vh;
|
| 4 |
+
background: var(--background);
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
.app-sidebar {
|
| 8 |
+
width: 276px;
|
| 9 |
+
flex-shrink: 0;
|
| 10 |
+
display: flex;
|
| 11 |
+
flex-direction: column;
|
| 12 |
+
background:
|
| 13 |
+
linear-gradient(180deg, rgba(255, 255, 255, 0.04), transparent 28%),
|
| 14 |
+
var(--bg-sidebar);
|
| 15 |
+
color: #f8fbff;
|
| 16 |
+
border-right: 1px solid rgba(255, 255, 255, 0.06);
|
| 17 |
+
box-shadow: 12px 0 28px rgba(9, 22, 38, 0.12);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.sidebar-header {
|
| 21 |
+
padding: 26px 24px 22px;
|
| 22 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.sidebar-kicker {
|
| 26 |
+
margin: 0 0 18px;
|
| 27 |
+
color: rgba(255, 255, 255, 0.56);
|
| 28 |
+
font-size: 0.72rem;
|
| 29 |
+
font-weight: 700;
|
| 30 |
+
letter-spacing: 0.14em;
|
| 31 |
+
text-transform: uppercase;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.logos {
|
| 35 |
+
display: flex;
|
| 36 |
+
flex-direction: column;
|
| 37 |
+
gap: 14px;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.logo-lockup {
|
| 41 |
+
display: grid;
|
| 42 |
+
gap: 6px;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.logo-placeholder {
|
| 46 |
+
display: inline-flex;
|
| 47 |
+
align-items: center;
|
| 48 |
+
width: fit-content;
|
| 49 |
+
min-height: 34px;
|
| 50 |
+
padding: 0 12px;
|
| 51 |
+
border-radius: 8px;
|
| 52 |
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
| 53 |
+
font-weight: 700;
|
| 54 |
+
letter-spacing: 0.05em;
|
| 55 |
+
font-size: 0.82rem;
|
| 56 |
+
text-transform: uppercase;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.btg-style {
|
| 60 |
+
color: #ffffff;
|
| 61 |
+
background: rgba(255, 255, 255, 0.06);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.rodobens-style {
|
| 65 |
+
color: #c5d4e3;
|
| 66 |
+
background: rgba(255, 255, 255, 0.03);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.logo-caption {
|
| 70 |
+
color: rgba(255, 255, 255, 0.62);
|
| 71 |
+
font-size: 0.78rem;
|
| 72 |
+
line-height: 1.45;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.sidebar-nav {
|
| 76 |
+
padding: 18px 16px;
|
| 77 |
+
flex: 1;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.sidebar-section-label {
|
| 81 |
+
padding: 0 10px 10px;
|
| 82 |
+
color: rgba(255, 255, 255, 0.44);
|
| 83 |
+
font-size: 0.69rem;
|
| 84 |
+
font-weight: 700;
|
| 85 |
+
letter-spacing: 0.16em;
|
| 86 |
+
text-transform: uppercase;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.sidebar-nav ul {
|
| 90 |
+
list-style: none;
|
| 91 |
+
padding: 0;
|
| 92 |
+
margin: 0;
|
| 93 |
+
display: flex;
|
| 94 |
+
flex-direction: column;
|
| 95 |
+
gap: 6px;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.nav-link {
|
| 99 |
+
display: flex;
|
| 100 |
+
align-items: center;
|
| 101 |
+
justify-content: space-between;
|
| 102 |
+
gap: 12px;
|
| 103 |
+
min-height: 48px;
|
| 104 |
+
padding: 0 14px;
|
| 105 |
+
border-radius: 10px;
|
| 106 |
+
color: rgba(245, 249, 255, 0.76);
|
| 107 |
+
text-decoration: none;
|
| 108 |
+
font-weight: 600;
|
| 109 |
+
font-size: 0.93rem;
|
| 110 |
+
border: 1px solid transparent;
|
| 111 |
+
transition: all 0.18s ease;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.nav-link:hover {
|
| 115 |
+
background: rgba(255, 255, 255, 0.05);
|
| 116 |
+
border-color: rgba(255, 255, 255, 0.08);
|
| 117 |
+
color: #ffffff;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.nav-link.active {
|
| 121 |
+
background: linear-gradient(90deg, rgba(236, 91, 19, 0.18), rgba(236, 91, 19, 0.06));
|
| 122 |
+
border-color: rgba(236, 91, 19, 0.26);
|
| 123 |
+
color: #ffffff;
|
| 124 |
+
box-shadow: inset 3px 0 0 var(--color-brand-accent);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.nav-link__meta {
|
| 128 |
+
color: rgba(255, 255, 255, 0.4);
|
| 129 |
+
font-size: 0.72rem;
|
| 130 |
+
font-weight: 700;
|
| 131 |
+
letter-spacing: 0.08em;
|
| 132 |
+
text-transform: uppercase;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.nav-link.active .nav-link__meta {
|
| 136 |
+
color: rgba(255, 255, 255, 0.72);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.sidebar-footer {
|
| 140 |
+
padding: 18px 24px 24px;
|
| 141 |
+
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
| 142 |
+
display: grid;
|
| 143 |
+
gap: 8px;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.sidebar-footer strong {
|
| 147 |
+
font-size: 0.82rem;
|
| 148 |
+
color: #ffffff;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.sidebar-footer span {
|
| 152 |
+
font-size: 0.76rem;
|
| 153 |
+
color: rgba(255, 255, 255, 0.56);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.app-main {
|
| 157 |
+
display: flex;
|
| 158 |
+
flex-direction: column;
|
| 159 |
+
flex: 1;
|
| 160 |
+
min-width: 0;
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
.app-topbar {
|
| 164 |
+
position: sticky;
|
| 165 |
+
top: 0;
|
| 166 |
+
z-index: 20;
|
| 167 |
+
display: flex;
|
| 168 |
+
justify-content: space-between;
|
| 169 |
+
align-items: flex-start;
|
| 170 |
+
gap: 18px;
|
| 171 |
+
padding: 22px 28px 18px;
|
| 172 |
+
background: rgba(243, 240, 235, 0.92);
|
| 173 |
+
backdrop-filter: blur(10px);
|
| 174 |
+
border-bottom: 1px solid rgba(197, 188, 174, 0.9);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.topbar-heading {
|
| 178 |
+
display: grid;
|
| 179 |
+
gap: 6px;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.topbar-kicker {
|
| 183 |
+
color: var(--text-muted);
|
| 184 |
+
font-size: 0.72rem;
|
| 185 |
+
font-weight: 700;
|
| 186 |
+
letter-spacing: 0.14em;
|
| 187 |
+
text-transform: uppercase;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.topbar-title {
|
| 191 |
+
margin: 0;
|
| 192 |
+
font-size: 1.35rem;
|
| 193 |
+
font-weight: 700;
|
| 194 |
+
color: var(--color-brand-dark);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.topbar-subtitle {
|
| 198 |
+
margin: 0;
|
| 199 |
+
color: var(--text-muted);
|
| 200 |
+
font-size: 0.86rem;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.topbar-context {
|
| 204 |
+
display: flex;
|
| 205 |
+
flex-wrap: wrap;
|
| 206 |
+
gap: 8px;
|
| 207 |
+
align-items: center;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.context-pill {
|
| 211 |
+
display: inline-flex;
|
| 212 |
+
align-items: center;
|
| 213 |
+
min-height: 30px;
|
| 214 |
+
padding: 0 10px;
|
| 215 |
+
border-radius: 999px;
|
| 216 |
+
background: var(--bg-panel);
|
| 217 |
+
border: 1px solid var(--border-color);
|
| 218 |
+
color: var(--text-main);
|
| 219 |
+
font-size: 0.74rem;
|
| 220 |
+
font-weight: 700;
|
| 221 |
+
letter-spacing: 0.04em;
|
| 222 |
+
text-transform: uppercase;
|
| 223 |
+
box-shadow: var(--shadow-soft);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.context-pill.highlight {
|
| 227 |
+
background: rgba(236, 91, 19, 0.1);
|
| 228 |
+
border-color: rgba(236, 91, 19, 0.24);
|
| 229 |
+
color: var(--color-brand-accent-strong);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.topbar-actions {
|
| 233 |
+
display: flex;
|
| 234 |
+
align-items: center;
|
| 235 |
+
gap: 10px;
|
| 236 |
+
flex-wrap: wrap;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
.app-content {
|
| 240 |
+
padding: 28px;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
.btn {
|
| 244 |
+
display: inline-flex;
|
| 245 |
+
align-items: center;
|
| 246 |
+
justify-content: center;
|
| 247 |
+
min-height: 40px;
|
| 248 |
+
padding: 0 16px;
|
| 249 |
+
border-radius: 10px;
|
| 250 |
+
cursor: pointer;
|
| 251 |
+
transition: all 0.18s ease;
|
| 252 |
+
border: 1px solid transparent;
|
| 253 |
+
font-family: var(--font-sans);
|
| 254 |
+
font-size: 0.82rem;
|
| 255 |
+
font-weight: 700;
|
| 256 |
+
letter-spacing: 0.04em;
|
| 257 |
+
text-transform: uppercase;
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
.btn-primary {
|
| 261 |
+
background: var(--color-brand-accent);
|
| 262 |
+
color: white;
|
| 263 |
+
box-shadow: 0 8px 18px rgba(236, 91, 19, 0.18);
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
.btn-primary:hover {
|
| 267 |
+
background: var(--color-brand-accent-strong);
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
.btn-secondary {
|
| 271 |
+
background: var(--bg-panel);
|
| 272 |
+
color: var(--color-brand-dark);
|
| 273 |
+
border-color: var(--border-color);
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
.btn-secondary:hover {
|
| 277 |
+
background: var(--bg-panel-soft);
|
| 278 |
+
border-color: var(--border-strong);
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.module-container {
|
| 282 |
+
display: flex;
|
| 283 |
+
flex-direction: column;
|
| 284 |
+
gap: 20px;
|
| 285 |
+
min-width: 0;
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
.module-header {
|
| 289 |
+
display: flex;
|
| 290 |
+
flex-wrap: wrap;
|
| 291 |
+
justify-content: space-between;
|
| 292 |
+
align-items: flex-start;
|
| 293 |
+
gap: 16px;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.module-title {
|
| 297 |
+
margin: 0 0 6px;
|
| 298 |
+
font-size: 1.55rem;
|
| 299 |
+
font-weight: 700;
|
| 300 |
+
color: var(--color-brand-dark);
|
| 301 |
+
letter-spacing: -0.02em;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.module-subtitle {
|
| 305 |
+
margin: 0;
|
| 306 |
+
color: var(--text-muted);
|
| 307 |
+
font-size: 0.92rem;
|
| 308 |
+
line-height: 1.55;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.module-content {
|
| 312 |
+
background: var(--bg-panel);
|
| 313 |
+
border: 1px solid var(--border-color);
|
| 314 |
+
border-radius: 12px;
|
| 315 |
+
padding: 24px;
|
| 316 |
+
box-shadow: var(--shadow-panel);
|
| 317 |
+
min-width: 0;
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
@media (max-width: 1100px) {
|
| 321 |
+
.app-container {
|
| 322 |
+
flex-direction: column;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
.app-sidebar {
|
| 326 |
+
width: 100%;
|
| 327 |
+
box-shadow: none;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.sidebar-nav ul {
|
| 331 |
+
display: grid;
|
| 332 |
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
@media (max-width: 768px) {
|
| 337 |
+
.app-topbar,
|
| 338 |
+
.app-content {
|
| 339 |
+
padding-left: 18px;
|
| 340 |
+
padding-right: 18px;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.module-content {
|
| 344 |
+
padding: 18px;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
.topbar-title {
|
| 348 |
+
font-size: 1.12rem;
|
| 349 |
+
}
|
| 350 |
+
}
|
apps/web_comercial/src/components/simulator/SimulatorForm.tsx
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import {
|
| 4 |
+
calcularSimulacao,
|
| 5 |
+
type ParametrosPlano,
|
| 6 |
+
type ResultadoSimulacao,
|
| 7 |
+
type SimulacaoInput,
|
| 8 |
+
} from "@simulador-consorcio/simulation-engine";
|
| 9 |
+
import { useState } from "react";
|
| 10 |
+
import { Card, DataCard } from "../ui/Card";
|
| 11 |
+
import { Input, Select } from "../ui/FormControls";
|
| 12 |
+
import {
|
| 13 |
+
Table,
|
| 14 |
+
TableBody,
|
| 15 |
+
TableCell,
|
| 16 |
+
TableHead,
|
| 17 |
+
TableHeader,
|
| 18 |
+
TableRow,
|
| 19 |
+
} from "../ui/Table";
|
| 20 |
+
|
| 21 |
+
export function SimulatorForm() {
|
| 22 |
+
const [produto, setProduto] = useState("imovel");
|
| 23 |
+
const [modalidade, setModalidade] = useState("linear_integral");
|
| 24 |
+
const [credito, setCredito] = useState<number>(300000);
|
| 25 |
+
const [prazo, setPrazo] = useState<number>(180);
|
| 26 |
+
const [temLance, setTemLance] = useState<boolean>(false);
|
| 27 |
+
const [lancePercentual, setLancePercentual] = useState<number>(30);
|
| 28 |
+
const [resultado, setResultado] = useState<ResultadoSimulacao | null>(null);
|
| 29 |
+
|
| 30 |
+
const formatCurrency = (value: number) => {
|
| 31 |
+
return new Intl.NumberFormat("pt-BR", {
|
| 32 |
+
style: "currency",
|
| 33 |
+
currency: "BRL",
|
| 34 |
+
}).format(value);
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const handleSimular = () => {
|
| 38 |
+
const parametros: ParametrosPlano = {
|
| 39 |
+
id_plano: "PADRAO",
|
| 40 |
+
nome_plano: "Plano Padrão Imóvel",
|
| 41 |
+
prazo_meses: prazo,
|
| 42 |
+
taxa_administracao_total: 0.15,
|
| 43 |
+
fundo_reserva_mensal: 0.0005,
|
| 44 |
+
seguro_prestamista: 0.035,
|
| 45 |
+
};
|
| 46 |
+
|
| 47 |
+
const input: SimulacaoInput = {
|
| 48 |
+
credito_desejado: credito,
|
| 49 |
+
parametros,
|
| 50 |
+
tem_lance: temLance,
|
| 51 |
+
lance_percentual: temLance ? lancePercentual / 100 : undefined,
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
const res = calcularSimulacao(input);
|
| 55 |
+
setResultado(res);
|
| 56 |
+
};
|
| 57 |
+
|
| 58 |
+
const modalidadeLabelMap: Record<string, string> = {
|
| 59 |
+
linear_integral: "Linear Integral",
|
| 60 |
+
linear_50: "Linear Reduzido 50%",
|
| 61 |
+
linear_70: "Linear Reduzido 70%",
|
| 62 |
+
degrau: "Degrau",
|
| 63 |
+
degrau_reduzido: "Degrau Reduzido",
|
| 64 |
+
pontual: "Pontual",
|
| 65 |
+
pontual_reduzido: "Pontual Reduzido",
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
const prazoLabel =
|
| 69 |
+
prazo === 216 ? "Longo prazo" : prazo >= 180 ? "Prazo padrão" : "Prazo curto";
|
| 70 |
+
|
| 71 |
+
return (
|
| 72 |
+
<div className="section-grid section-grid--simulator">
|
| 73 |
+
<div className="section-stack">
|
| 74 |
+
<Card
|
| 75 |
+
title="Leitura operacional"
|
| 76 |
+
headerAction={<span className="status-chip status-info">{modalidadeLabelMap[modalidade]}</span>}
|
| 77 |
+
>
|
| 78 |
+
<div className="page-kpi-strip" style={{ marginBottom: 0 }}>
|
| 79 |
+
<DataCard
|
| 80 |
+
label="Produto"
|
| 81 |
+
value="Imóvel"
|
| 82 |
+
subvalue="Escopo exclusivo do simulador"
|
| 83 |
+
/>
|
| 84 |
+
<DataCard
|
| 85 |
+
label="Plano selecionado"
|
| 86 |
+
value={modalidadeLabelMap[modalidade]}
|
| 87 |
+
subvalue={prazoLabel}
|
| 88 |
+
/>
|
| 89 |
+
<DataCard
|
| 90 |
+
label="Crédito em análise"
|
| 91 |
+
value={formatCurrency(credito)}
|
| 92 |
+
subvalue={`Prazo base ${prazo} meses`}
|
| 93 |
+
highlight
|
| 94 |
+
/>
|
| 95 |
+
<DataCard
|
| 96 |
+
label="Lance"
|
| 97 |
+
value={temLance ? `${lancePercentual}%` : "Sem lance"}
|
| 98 |
+
subvalue="Operação editável"
|
| 99 |
+
/>
|
| 100 |
+
</div>
|
| 101 |
+
</Card>
|
| 102 |
+
|
| 103 |
+
<Card title="Informações Básicas">
|
| 104 |
+
<div className="field-grid field-grid--2">
|
| 105 |
+
<Select
|
| 106 |
+
label="Tipo Produto"
|
| 107 |
+
value={produto}
|
| 108 |
+
onChange={(e) => setProduto(e.target.value)}
|
| 109 |
+
options={[{ label: "Imóvel", value: "imovel" }]}
|
| 110 |
+
disabled // Locked to 'imovel' per instruction
|
| 111 |
+
/>
|
| 112 |
+
<Select
|
| 113 |
+
label="Modalidade"
|
| 114 |
+
value={modalidade}
|
| 115 |
+
onChange={(e) => setModalidade(e.target.value)}
|
| 116 |
+
options={[
|
| 117 |
+
{ label: "Linear Integral", value: "linear_integral" },
|
| 118 |
+
{ label: "Linear Reduzido 50%", value: "linear_50" },
|
| 119 |
+
{ label: "Linear Reduzido 70%", value: "linear_70" },
|
| 120 |
+
{ label: "Degrau", value: "degrau" },
|
| 121 |
+
{ label: "Degrau Reduzido", value: "degrau_reduzido" },
|
| 122 |
+
{ label: "Pontual", value: "pontual" },
|
| 123 |
+
{ label: "Pontual Reduzido", value: "pontual_reduzido" },
|
| 124 |
+
]}
|
| 125 |
+
/>
|
| 126 |
+
<Input label="Grupo" placeholder="Ex: 4001" />
|
| 127 |
+
<Input label="Número de Cotas" type="number" defaultValue={1} />
|
| 128 |
+
</div>
|
| 129 |
+
</Card>
|
| 130 |
+
|
| 131 |
+
<Card title="Informações do Plano">
|
| 132 |
+
<div className="field-grid field-grid--2">
|
| 133 |
+
<Input
|
| 134 |
+
label="Valor do Crédito"
|
| 135 |
+
type="number"
|
| 136 |
+
value={credito}
|
| 137 |
+
onChange={(e) => setCredito(Number(e.target.value))}
|
| 138 |
+
prefix="R$"
|
| 139 |
+
/>
|
| 140 |
+
<Select
|
| 141 |
+
label="Prazo do Plano"
|
| 142 |
+
value={prazo}
|
| 143 |
+
onChange={(e) => setPrazo(Number(e.target.value))}
|
| 144 |
+
options={[
|
| 145 |
+
{ label: "120 meses", value: "120" },
|
| 146 |
+
{ label: "150 meses", value: "150" },
|
| 147 |
+
{ label: "180 meses", value: "180" },
|
| 148 |
+
{ label: "216 meses", value: "216" },
|
| 149 |
+
]}
|
| 150 |
+
/>
|
| 151 |
+
<Input
|
| 152 |
+
label="Taxa de Administração Total"
|
| 153 |
+
defaultValue="15.00"
|
| 154 |
+
suffix="%"
|
| 155 |
+
/>
|
| 156 |
+
<Input label="Fundo de Reserva" defaultValue="0.05" suffix="%" />
|
| 157 |
+
<Input label="Seguro Prestamista" defaultValue="3.50" suffix="%" />
|
| 158 |
+
<Input label="Taxa de Adesão" defaultValue="1.00" suffix="%" />
|
| 159 |
+
</div>
|
| 160 |
+
</Card>
|
| 161 |
+
|
| 162 |
+
<Card title="Informações de Lance">
|
| 163 |
+
<div className="field-grid field-grid--2">
|
| 164 |
+
<div className="ui-form-group" style={{ gridColumn: "1 / -1" }}>
|
| 165 |
+
<label className="inline-toggle">
|
| 166 |
+
<input
|
| 167 |
+
type="checkbox"
|
| 168 |
+
checked={temLance}
|
| 169 |
+
onChange={(e) => setTemLance(e.target.checked)}
|
| 170 |
+
/>
|
| 171 |
+
Simular contemplação via lance
|
| 172 |
+
</label>
|
| 173 |
+
</div>
|
| 174 |
+
{temLance && (
|
| 175 |
+
<>
|
| 176 |
+
<Select
|
| 177 |
+
label="Tipo de Lance"
|
| 178 |
+
options={[
|
| 179 |
+
{ label: "Fixo", value: "fixo" },
|
| 180 |
+
{ label: "Livre", value: "livre" },
|
| 181 |
+
]}
|
| 182 |
+
/>
|
| 183 |
+
<Input
|
| 184 |
+
label="Lance Total (%)"
|
| 185 |
+
type="number"
|
| 186 |
+
value={lancePercentual}
|
| 187 |
+
onChange={(e) => setLancePercentual(Number(e.target.value))}
|
| 188 |
+
suffix="%"
|
| 189 |
+
/>
|
| 190 |
+
<Input
|
| 191 |
+
label="Embutido Percentual"
|
| 192 |
+
type="number"
|
| 193 |
+
defaultValue={10}
|
| 194 |
+
suffix="%"
|
| 195 |
+
tooltip="Percentual do lance a ser retirado do próprio crédito."
|
| 196 |
+
/>
|
| 197 |
+
<Select
|
| 198 |
+
label="Mês Previsto de Lance"
|
| 199 |
+
options={Array.from({ length: 24 }, (_, i) => ({
|
| 200 |
+
label: `Mês ${i + 1}`,
|
| 201 |
+
value: i + 1,
|
| 202 |
+
}))}
|
| 203 |
+
/>
|
| 204 |
+
</>
|
| 205 |
+
)}
|
| 206 |
+
</div>
|
| 207 |
+
|
| 208 |
+
<div style={{ marginTop: "20px", display: "flex", justifyContent: "flex-end", gap: "12px" }}>
|
| 209 |
+
<button className="btn btn-secondary">Limpar</button>
|
| 210 |
+
<button className="btn btn-primary" onClick={handleSimular}>
|
| 211 |
+
Calcular Cenário
|
| 212 |
+
</button>
|
| 213 |
+
</div>
|
| 214 |
+
</Card>
|
| 215 |
+
</div>
|
| 216 |
+
|
| 217 |
+
<div className="summary-rail">
|
| 218 |
+
<Card title="Resumo Geral" highlight={true}>
|
| 219 |
+
{resultado ? (
|
| 220 |
+
<div className="section-stack" style={{ gap: "12px" }}>
|
| 221 |
+
<DataCard
|
| 222 |
+
label="Crédito Disponível"
|
| 223 |
+
value={formatCurrency(resultado.credito || credito)}
|
| 224 |
+
/>
|
| 225 |
+
<DataCard
|
| 226 |
+
label="Parcela Inicial (Integral)"
|
| 227 |
+
value={formatCurrency(resultado.parcela_integral.total)}
|
| 228 |
+
highlight
|
| 229 |
+
/>
|
| 230 |
+
{resultado.parcela_reduzida && (
|
| 231 |
+
<DataCard
|
| 232 |
+
label="Parcela Reduzida"
|
| 233 |
+
value={formatCurrency(resultado.parcela_reduzida.total)}
|
| 234 |
+
/>
|
| 235 |
+
)}
|
| 236 |
+
{temLance && resultado.valor_lance && (
|
| 237 |
+
<>
|
| 238 |
+
<DataCard
|
| 239 |
+
label="Valor Total do Lance"
|
| 240 |
+
value={formatCurrency(resultado.valor_lance)}
|
| 241 |
+
/>
|
| 242 |
+
<DataCard
|
| 243 |
+
label="Prazo Restante"
|
| 244 |
+
value={`${resultado.prazo_apos_lance} meses`}
|
| 245 |
+
/>
|
| 246 |
+
</>
|
| 247 |
+
)}
|
| 248 |
+
</div>
|
| 249 |
+
) : (
|
| 250 |
+
<p className="plain-note">
|
| 251 |
+
Preencha os blocos operacionais para liberar o resumo, o CET e a
|
| 252 |
+
composição da parcela.
|
| 253 |
+
</p>
|
| 254 |
+
)}
|
| 255 |
+
</Card>
|
| 256 |
+
|
| 257 |
+
<Card title="CET / Ao mês">
|
| 258 |
+
{resultado ? (
|
| 259 |
+
<div className="section-stack" style={{ gap: "12px" }}>
|
| 260 |
+
<DataCard
|
| 261 |
+
label="Custo Efetivo Total (Ao Ano)"
|
| 262 |
+
value="11.45%"
|
| 263 |
+
subvalue="Estimativa com base nas taxas atuais"
|
| 264 |
+
/>
|
| 265 |
+
<DataCard label="Taxa Equivalente (Ao Mês)" value="0.91%" />
|
| 266 |
+
</div>
|
| 267 |
+
) : (
|
| 268 |
+
<p className="plain-note">Aguardando geração do cenário.</p>
|
| 269 |
+
)}
|
| 270 |
+
</Card>
|
| 271 |
+
|
| 272 |
+
<Card title="Checklist operacional">
|
| 273 |
+
<div className="section-stack" style={{ gap: "10px" }}>
|
| 274 |
+
<div className="executive-banner" style={{ padding: "12px 14px" }}>
|
| 275 |
+
<div>
|
| 276 |
+
<strong>Validação de escopo:</strong> produto travado em imóvel e
|
| 277 |
+
modalidades alinhadas ao workbook.
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
<p className="plain-note">
|
| 281 |
+
Antes de emitir proposta, confirme grupo, prazo, lance, CET, faixa
|
| 282 |
+
de parcela e crédito líquido disponível.
|
| 283 |
+
</p>
|
| 284 |
+
</div>
|
| 285 |
+
</Card>
|
| 286 |
+
|
| 287 |
+
{resultado && (
|
| 288 |
+
<Card title="Composição da Parcela" noPadding>
|
| 289 |
+
<Table>
|
| 290 |
+
<TableHead>
|
| 291 |
+
<TableRow>
|
| 292 |
+
<TableHeader>Rubrica</TableHeader>
|
| 293 |
+
<TableHeader style={{ textAlign: "right" }}>
|
| 294 |
+
Valor (R$)
|
| 295 |
+
</TableHeader>
|
| 296 |
+
</TableRow>
|
| 297 |
+
</TableHead>
|
| 298 |
+
<TableBody>
|
| 299 |
+
<TableRow>
|
| 300 |
+
<TableCell>Fundo Comum</TableCell>
|
| 301 |
+
<TableCell
|
| 302 |
+
style={{ textAlign: "right", fontFamily: "monospace" }}
|
| 303 |
+
>
|
| 304 |
+
{formatCurrency(resultado.parcela_integral.fundo_comum)}
|
| 305 |
+
</TableCell>
|
| 306 |
+
</TableRow>
|
| 307 |
+
<TableRow>
|
| 308 |
+
<TableCell>Taxa Administrativa</TableCell>
|
| 309 |
+
<TableCell
|
| 310 |
+
style={{ textAlign: "right", fontFamily: "monospace" }}
|
| 311 |
+
>
|
| 312 |
+
{formatCurrency(
|
| 313 |
+
resultado.parcela_integral.taxa_administracao,
|
| 314 |
+
)}
|
| 315 |
+
</TableCell>
|
| 316 |
+
</TableRow>
|
| 317 |
+
<TableRow>
|
| 318 |
+
<TableCell>Fundo de Reserva</TableCell>
|
| 319 |
+
<TableCell
|
| 320 |
+
style={{ textAlign: "right", fontFamily: "monospace" }}
|
| 321 |
+
>
|
| 322 |
+
{formatCurrency(resultado.parcela_integral.fundo_reserva)}
|
| 323 |
+
</TableCell>
|
| 324 |
+
</TableRow>
|
| 325 |
+
<TableRow>
|
| 326 |
+
<TableCell>Seguro Prestamista</TableCell>
|
| 327 |
+
<TableCell
|
| 328 |
+
style={{ textAlign: "right", fontFamily: "monospace" }}
|
| 329 |
+
>
|
| 330 |
+
{formatCurrency(resultado.parcela_integral.seguro)}
|
| 331 |
+
</TableCell>
|
| 332 |
+
</TableRow>
|
| 333 |
+
<TableRow
|
| 334 |
+
style={{ backgroundColor: "var(--bg-main)", fontWeight: 600 }}
|
| 335 |
+
>
|
| 336 |
+
<TableCell>Total</TableCell>
|
| 337 |
+
<TableCell
|
| 338 |
+
style={{ textAlign: "right", fontFamily: "monospace" }}
|
| 339 |
+
>
|
| 340 |
+
{formatCurrency(resultado.parcela_integral.total)}
|
| 341 |
+
</TableCell>
|
| 342 |
+
</TableRow>
|
| 343 |
+
</TableBody>
|
| 344 |
+
</Table>
|
| 345 |
+
<div className="table-note">
|
| 346 |
+
<span>Leitura resumida da memória de cálculo.</span>
|
| 347 |
+
<span>Base atual: parcela integral do motor determinístico.</span>
|
| 348 |
+
</div>
|
| 349 |
+
</Card>
|
| 350 |
+
)}
|
| 351 |
+
</div>
|
| 352 |
+
</div>
|
| 353 |
+
);
|
| 354 |
+
}
|
apps/web_comercial/src/components/ui/Card.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
|
| 3 |
+
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
| 4 |
+
children: React.ReactNode;
|
| 5 |
+
title?: string;
|
| 6 |
+
headerAction?: React.ReactNode;
|
| 7 |
+
noPadding?: boolean;
|
| 8 |
+
highlight?: boolean;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export function Card({
|
| 12 |
+
children,
|
| 13 |
+
title,
|
| 14 |
+
headerAction,
|
| 15 |
+
noPadding = false,
|
| 16 |
+
highlight = false,
|
| 17 |
+
className = "",
|
| 18 |
+
...props
|
| 19 |
+
}: CardProps) {
|
| 20 |
+
return (
|
| 21 |
+
<div className={`ui-card ${highlight ? "highlight" : ""} ${className}`} {...props}>
|
| 22 |
+
{(title || headerAction) && (
|
| 23 |
+
<div className="ui-card-header">
|
| 24 |
+
{title && <h3 className="ui-card-title">{title}</h3>}
|
| 25 |
+
{headerAction && <div className="ui-card-action">{headerAction}</div>}
|
| 26 |
+
</div>
|
| 27 |
+
)}
|
| 28 |
+
<div className={`ui-card-content ${noPadding ? "no-padding" : ""}`}>
|
| 29 |
+
{children}
|
| 30 |
+
</div>
|
| 31 |
+
</div>
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
export function DataCard({
|
| 36 |
+
label,
|
| 37 |
+
value,
|
| 38 |
+
subvalue,
|
| 39 |
+
highlight = false,
|
| 40 |
+
}: {
|
| 41 |
+
label: string;
|
| 42 |
+
value: string | React.ReactNode;
|
| 43 |
+
subvalue?: string;
|
| 44 |
+
highlight?: boolean;
|
| 45 |
+
}) {
|
| 46 |
+
return (
|
| 47 |
+
<div className={`ui-data-card ${highlight ? "highlight" : ""}`}>
|
| 48 |
+
<span className="ui-data-label">{label}</span>
|
| 49 |
+
<span className="ui-data-value">{value}</span>
|
| 50 |
+
{subvalue && <span className="ui-data-subvalue">{subvalue}</span>}
|
| 51 |
+
</div>
|
| 52 |
+
);
|
| 53 |
+
}
|
apps/web_comercial/src/components/ui/FormControls.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import { Tooltip } from "./Tooltip";
|
| 3 |
+
|
| 4 |
+
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
| 5 |
+
label: string;
|
| 6 |
+
tooltip?: string;
|
| 7 |
+
error?: string;
|
| 8 |
+
suffix?: string;
|
| 9 |
+
prefix?: string;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export function Input({
|
| 13 |
+
label,
|
| 14 |
+
tooltip,
|
| 15 |
+
error,
|
| 16 |
+
suffix,
|
| 17 |
+
prefix,
|
| 18 |
+
className = "",
|
| 19 |
+
...props
|
| 20 |
+
}: InputProps) {
|
| 21 |
+
return (
|
| 22 |
+
<div className={`ui-form-group ${className}`}>
|
| 23 |
+
<div className="ui-label-wrapper">
|
| 24 |
+
<label className="ui-form-label">{label}</label>
|
| 25 |
+
{tooltip && (
|
| 26 |
+
<Tooltip content={tooltip}>
|
| 27 |
+
<span className="ui-tooltip-icon">?</span>
|
| 28 |
+
</Tooltip>
|
| 29 |
+
)}
|
| 30 |
+
</div>
|
| 31 |
+
<div className="ui-input-wrapper">
|
| 32 |
+
{prefix && <span className="ui-input-prefix">{prefix}</span>}
|
| 33 |
+
<input
|
| 34 |
+
className={`ui-input ${prefix ? "has-prefix" : ""} ${suffix ? "has-suffix" : ""} ${error ? "is-invalid" : ""}`}
|
| 35 |
+
{...props}
|
| 36 |
+
/>
|
| 37 |
+
{suffix && <span className="ui-input-suffix">{suffix}</span>}
|
| 38 |
+
</div>
|
| 39 |
+
{error && <span className="ui-form-error">{error}</span>}
|
| 40 |
+
</div>
|
| 41 |
+
);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
|
| 45 |
+
label: string;
|
| 46 |
+
tooltip?: string;
|
| 47 |
+
options: { label: string; value: string | number }[];
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
export function Select({
|
| 51 |
+
label,
|
| 52 |
+
tooltip,
|
| 53 |
+
options,
|
| 54 |
+
className = "",
|
| 55 |
+
...props
|
| 56 |
+
}: SelectProps) {
|
| 57 |
+
return (
|
| 58 |
+
<div className={`ui-form-group ${className}`}>
|
| 59 |
+
<div className="ui-label-wrapper">
|
| 60 |
+
<label className="ui-form-label">{label}</label>
|
| 61 |
+
{tooltip && (
|
| 62 |
+
<Tooltip content={tooltip}>
|
| 63 |
+
<span className="ui-tooltip-icon">?</span>
|
| 64 |
+
</Tooltip>
|
| 65 |
+
)}
|
| 66 |
+
</div>
|
| 67 |
+
<select className="ui-select" {...props}>
|
| 68 |
+
{options.map((opt) => (
|
| 69 |
+
<option key={opt.value} value={opt.value}>
|
| 70 |
+
{opt.label}
|
| 71 |
+
</option>
|
| 72 |
+
))}
|
| 73 |
+
</select>
|
| 74 |
+
</div>
|
| 75 |
+
);
|
| 76 |
+
}
|
apps/web_comercial/src/components/ui/Table.tsx
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
|
| 3 |
+
interface TableProps extends React.TableHTMLAttributes<HTMLTableElement> {
|
| 4 |
+
children: React.ReactNode;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
export function Table({ children, className = "", ...props }: TableProps) {
|
| 8 |
+
return (
|
| 9 |
+
<div className="ui-table-container">
|
| 10 |
+
<table className={`ui-table ${className}`} {...props}>
|
| 11 |
+
{children}
|
| 12 |
+
</table>
|
| 13 |
+
</div>
|
| 14 |
+
);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export function TableHead({ children }: { children: React.ReactNode }) {
|
| 18 |
+
return <thead className="ui-table-head">{children}</thead>;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export function TableBody({ children }: { children: React.ReactNode }) {
|
| 22 |
+
return <tbody className="ui-table-body">{children}</tbody>;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export function TableRow({
|
| 26 |
+
children,
|
| 27 |
+
className = "",
|
| 28 |
+
...props
|
| 29 |
+
}: React.HTMLAttributes<HTMLTableRowElement>) {
|
| 30 |
+
return (
|
| 31 |
+
<tr className={`ui-table-row ${className}`} {...props}>
|
| 32 |
+
{children}
|
| 33 |
+
</tr>
|
| 34 |
+
);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
export function TableHeader({
|
| 38 |
+
children,
|
| 39 |
+
className = "",
|
| 40 |
+
...props
|
| 41 |
+
}: React.ThHTMLAttributes<HTMLTableHeaderCellElement>) {
|
| 42 |
+
return (
|
| 43 |
+
<th className={`ui-table-header ${className}`} {...props}>
|
| 44 |
+
{children}
|
| 45 |
+
</th>
|
| 46 |
+
);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
export function TableCell({
|
| 50 |
+
children,
|
| 51 |
+
className = "",
|
| 52 |
+
...props
|
| 53 |
+
}: React.TdHTMLAttributes<HTMLTableDataCellElement>) {
|
| 54 |
+
return (
|
| 55 |
+
<td className={`ui-table-cell ${className}`} {...props}>
|
| 56 |
+
{children}
|
| 57 |
+
</td>
|
| 58 |
+
);
|
| 59 |
+
}
|
apps/web_comercial/src/components/ui/Tabs.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from "react";
|
| 2 |
+
|
| 3 |
+
interface TabsProps {
|
| 4 |
+
tabs: { id: string; label: string; content: React.ReactNode }[];
|
| 5 |
+
defaultTab?: string;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export function Tabs({ tabs, defaultTab }: TabsProps) {
|
| 9 |
+
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id);
|
| 10 |
+
|
| 11 |
+
return (
|
| 12 |
+
<div className="ui-tabs-container">
|
| 13 |
+
<div className="ui-tabs-header">
|
| 14 |
+
{tabs.map((tab) => (
|
| 15 |
+
<button
|
| 16 |
+
key={tab.id}
|
| 17 |
+
className={`ui-tab-button ${activeTab === tab.id ? "active" : ""}`}
|
| 18 |
+
onClick={() => setActiveTab(tab.id)}
|
| 19 |
+
>
|
| 20 |
+
{tab.label}
|
| 21 |
+
</button>
|
| 22 |
+
))}
|
| 23 |
+
</div>
|
| 24 |
+
<div className="ui-tabs-content">
|
| 25 |
+
{tabs.find((tab) => tab.id === activeTab)?.content}
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
);
|
| 29 |
+
}
|
apps/web_comercial/src/components/ui/Tooltip.tsx
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
|
| 3 |
+
interface TooltipProps {
|
| 4 |
+
content: string;
|
| 5 |
+
children: React.ReactNode;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export function Tooltip({ content, children }: TooltipProps) {
|
| 9 |
+
return (
|
| 10 |
+
<div className="ui-tooltip-container">
|
| 11 |
+
{children}
|
| 12 |
+
<div className="ui-tooltip-content">{content}</div>
|
| 13 |
+
</div>
|
| 14 |
+
);
|
| 15 |
+
}
|
apps/web_comercial/src/components/ui/ui.css
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Card */
|
| 2 |
+
.ui-card {
|
| 3 |
+
background-color: var(--bg-panel);
|
| 4 |
+
border: 1px solid var(--border-color);
|
| 5 |
+
border-radius: 10px;
|
| 6 |
+
overflow: hidden;
|
| 7 |
+
display: flex;
|
| 8 |
+
flex-direction: column;
|
| 9 |
+
box-shadow: var(--shadow-soft);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
.ui-card.highlight {
|
| 13 |
+
border-color: rgba(13, 45, 82, 0.16);
|
| 14 |
+
box-shadow:
|
| 15 |
+
inset 0 1px 0 rgba(255, 255, 255, 0.7),
|
| 16 |
+
0 10px 22px rgba(13, 45, 82, 0.07);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.ui-card-header {
|
| 20 |
+
min-height: 50px;
|
| 21 |
+
padding: 0 18px;
|
| 22 |
+
border-bottom: 1px solid var(--border-color);
|
| 23 |
+
display: flex;
|
| 24 |
+
justify-content: space-between;
|
| 25 |
+
align-items: center;
|
| 26 |
+
background:
|
| 27 |
+
linear-gradient(180deg, rgba(13, 45, 82, 0.04), rgba(13, 45, 82, 0.01)),
|
| 28 |
+
var(--bg-panel-soft);
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
.ui-card-title {
|
| 32 |
+
font-size: 0.88rem;
|
| 33 |
+
font-weight: 700;
|
| 34 |
+
color: var(--color-brand-dark);
|
| 35 |
+
margin: 0;
|
| 36 |
+
letter-spacing: 0.04em;
|
| 37 |
+
text-transform: uppercase;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.ui-card-content {
|
| 41 |
+
padding: 18px;
|
| 42 |
+
flex: 1;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.ui-card-content.no-padding {
|
| 46 |
+
padding: 0;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.ui-data-card {
|
| 50 |
+
display: flex;
|
| 51 |
+
flex-direction: column;
|
| 52 |
+
min-height: 88px;
|
| 53 |
+
padding: 12px 14px;
|
| 54 |
+
border: 1px solid var(--border-color);
|
| 55 |
+
border-radius: 8px;
|
| 56 |
+
background:
|
| 57 |
+
linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(248, 245, 241, 0.95)),
|
| 58 |
+
var(--bg-panel-soft);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
.ui-data-card.highlight {
|
| 62 |
+
background:
|
| 63 |
+
linear-gradient(180deg, rgba(237, 244, 251, 0.95), rgba(223, 237, 248, 0.98)),
|
| 64 |
+
var(--bg-panel-accent);
|
| 65 |
+
border-color: rgba(13, 45, 82, 0.18);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.ui-data-label {
|
| 69 |
+
font-size: 0.68rem;
|
| 70 |
+
font-weight: 700;
|
| 71 |
+
color: var(--text-muted);
|
| 72 |
+
text-transform: uppercase;
|
| 73 |
+
letter-spacing: 0.08em;
|
| 74 |
+
margin-bottom: 8px;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.ui-data-value {
|
| 78 |
+
font-size: 1.18rem;
|
| 79 |
+
font-weight: 700;
|
| 80 |
+
color: var(--text-main);
|
| 81 |
+
line-height: 1.2;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.ui-data-subvalue {
|
| 85 |
+
font-size: 0.75rem;
|
| 86 |
+
color: var(--text-muted);
|
| 87 |
+
margin-top: 4px;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
/* Form Controls */
|
| 91 |
+
.ui-form-group {
|
| 92 |
+
display: flex;
|
| 93 |
+
flex-direction: column;
|
| 94 |
+
gap: 6px;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.ui-label-wrapper {
|
| 98 |
+
display: flex;
|
| 99 |
+
align-items: center;
|
| 100 |
+
gap: 6px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.ui-form-label {
|
| 104 |
+
font-size: 0.74rem;
|
| 105 |
+
font-weight: 700;
|
| 106 |
+
color: var(--text-muted);
|
| 107 |
+
text-transform: uppercase;
|
| 108 |
+
letter-spacing: 0.07em;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.ui-tooltip-icon {
|
| 112 |
+
display: inline-flex;
|
| 113 |
+
align-items: center;
|
| 114 |
+
justify-content: center;
|
| 115 |
+
width: 15px;
|
| 116 |
+
height: 15px;
|
| 117 |
+
border-radius: 50%;
|
| 118 |
+
background-color: var(--color-brand-dark);
|
| 119 |
+
color: white;
|
| 120 |
+
font-size: 10px;
|
| 121 |
+
font-weight: 700;
|
| 122 |
+
cursor: help;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
.ui-input-wrapper {
|
| 126 |
+
position: relative;
|
| 127 |
+
display: flex;
|
| 128 |
+
align-items: center;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.ui-input,
|
| 132 |
+
.ui-select {
|
| 133 |
+
width: 100%;
|
| 134 |
+
min-height: 42px;
|
| 135 |
+
padding: 10px 12px;
|
| 136 |
+
font-size: 0.92rem;
|
| 137 |
+
border: 1px solid var(--border-color);
|
| 138 |
+
border-radius: 8px;
|
| 139 |
+
background-color: #fffdfb;
|
| 140 |
+
color: var(--text-main);
|
| 141 |
+
transition:
|
| 142 |
+
border-color 0.18s,
|
| 143 |
+
box-shadow 0.18s,
|
| 144 |
+
background-color 0.18s;
|
| 145 |
+
font-family: inherit;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.ui-input:hover,
|
| 149 |
+
.ui-select:hover {
|
| 150 |
+
border-color: var(--border-strong);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
.ui-input:focus,
|
| 154 |
+
.ui-select:focus {
|
| 155 |
+
outline: none;
|
| 156 |
+
border-color: rgba(13, 45, 82, 0.5);
|
| 157 |
+
box-shadow: 0 0 0 3px rgba(13, 45, 82, 0.08);
|
| 158 |
+
background-color: white;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.ui-input.is-invalid {
|
| 162 |
+
border-color: var(--status-negative);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.ui-input-prefix,
|
| 166 |
+
.ui-input-suffix {
|
| 167 |
+
position: absolute;
|
| 168 |
+
color: var(--text-muted);
|
| 169 |
+
font-size: 0.86rem;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.ui-input-prefix {
|
| 173 |
+
left: 12px;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.ui-input-suffix {
|
| 177 |
+
right: 12px;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.ui-input.has-prefix {
|
| 181 |
+
padding-left: 34px;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.ui-input.has-suffix {
|
| 185 |
+
padding-right: 40px;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.ui-form-error {
|
| 189 |
+
color: var(--status-negative);
|
| 190 |
+
font-size: 0.75rem;
|
| 191 |
+
margin-top: 2px;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/* Tooltip */
|
| 195 |
+
.ui-tooltip-container {
|
| 196 |
+
position: relative;
|
| 197 |
+
display: inline-flex;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.ui-tooltip-content {
|
| 201 |
+
visibility: hidden;
|
| 202 |
+
position: absolute;
|
| 203 |
+
bottom: 150%;
|
| 204 |
+
left: 50%;
|
| 205 |
+
transform: translateX(-50%);
|
| 206 |
+
background-color: #13263e;
|
| 207 |
+
color: white;
|
| 208 |
+
text-align: center;
|
| 209 |
+
padding: 7px 10px;
|
| 210 |
+
border-radius: 6px;
|
| 211 |
+
font-size: 0.74rem;
|
| 212 |
+
white-space: nowrap;
|
| 213 |
+
opacity: 0;
|
| 214 |
+
transition: opacity 0.2s;
|
| 215 |
+
z-index: 100;
|
| 216 |
+
pointer-events: none;
|
| 217 |
+
box-shadow: var(--shadow-soft);
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.ui-tooltip-content::after {
|
| 221 |
+
content: "";
|
| 222 |
+
position: absolute;
|
| 223 |
+
top: 100%;
|
| 224 |
+
left: 50%;
|
| 225 |
+
margin-left: -5px;
|
| 226 |
+
border-width: 5px;
|
| 227 |
+
border-style: solid;
|
| 228 |
+
border-color: #13263e transparent transparent transparent;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
.ui-tooltip-container:hover .ui-tooltip-content {
|
| 232 |
+
visibility: visible;
|
| 233 |
+
opacity: 1;
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
/* Table */
|
| 237 |
+
.ui-table-container {
|
| 238 |
+
width: 100%;
|
| 239 |
+
overflow-x: auto;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.ui-table {
|
| 243 |
+
width: 100%;
|
| 244 |
+
border-collapse: collapse;
|
| 245 |
+
font-size: 0.84rem;
|
| 246 |
+
text-align: left;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.ui-table-head {
|
| 250 |
+
background: var(--bg-panel-soft);
|
| 251 |
+
border-bottom: 1px solid var(--border-color);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.ui-table-header {
|
| 255 |
+
padding: 12px 16px;
|
| 256 |
+
font-weight: 700;
|
| 257 |
+
color: var(--text-muted);
|
| 258 |
+
text-transform: uppercase;
|
| 259 |
+
font-size: 0.7rem;
|
| 260 |
+
letter-spacing: 0.08em;
|
| 261 |
+
white-space: nowrap;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.ui-table-row {
|
| 265 |
+
border-bottom: 1px solid rgba(215, 210, 200, 0.8);
|
| 266 |
+
transition: background-color 0.18s;
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
.ui-table-row:hover {
|
| 270 |
+
background-color: rgba(13, 45, 82, 0.025);
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.ui-table-cell {
|
| 274 |
+
padding: 12px 16px;
|
| 275 |
+
color: var(--text-main);
|
| 276 |
+
vertical-align: middle;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
/* Tabs */
|
| 280 |
+
.ui-tabs-container {
|
| 281 |
+
display: flex;
|
| 282 |
+
flex-direction: column;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
.ui-tabs-header {
|
| 286 |
+
display: flex;
|
| 287 |
+
border-bottom: 1px solid var(--border-color);
|
| 288 |
+
margin-bottom: 24px;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.ui-tab-button {
|
| 292 |
+
padding: 12px 18px;
|
| 293 |
+
background: none;
|
| 294 |
+
border: none;
|
| 295 |
+
border-bottom: 2px solid transparent;
|
| 296 |
+
font-size: 0.86rem;
|
| 297 |
+
font-weight: 700;
|
| 298 |
+
color: var(--text-muted);
|
| 299 |
+
cursor: pointer;
|
| 300 |
+
transition: all 0.18s;
|
| 301 |
+
font-family: inherit;
|
| 302 |
+
margin-bottom: -1px;
|
| 303 |
+
text-transform: uppercase;
|
| 304 |
+
letter-spacing: 0.05em;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.ui-tab-button:hover {
|
| 308 |
+
color: var(--color-brand-dark);
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
.ui-tab-button.active {
|
| 312 |
+
color: var(--color-brand-dark);
|
| 313 |
+
border-bottom-color: var(--color-brand-accent);
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
.ui-tabs-content {
|
| 317 |
+
flex: 1;
|
| 318 |
+
}
|
apps/web_comercial/tsconfig.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2017",
|
| 4 |
+
"lib": [
|
| 5 |
+
"dom",
|
| 6 |
+
"dom.iterable",
|
| 7 |
+
"esnext"
|
| 8 |
+
],
|
| 9 |
+
"allowJs": true,
|
| 10 |
+
"skipLibCheck": true,
|
| 11 |
+
"strict": true,
|
| 12 |
+
"noEmit": true,
|
| 13 |
+
"esModuleInterop": true,
|
| 14 |
+
"module": "esnext",
|
| 15 |
+
"moduleResolution": "bundler",
|
| 16 |
+
"resolveJsonModule": true,
|
| 17 |
+
"isolatedModules": true,
|
| 18 |
+
"jsx": "react-jsx",
|
| 19 |
+
"incremental": true,
|
| 20 |
+
"plugins": [
|
| 21 |
+
{
|
| 22 |
+
"name": "next"
|
| 23 |
+
}
|
| 24 |
+
],
|
| 25 |
+
"paths": {
|
| 26 |
+
"@/*": [
|
| 27 |
+
"./src/*"
|
| 28 |
+
]
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
"include": [
|
| 32 |
+
"next-env.d.ts",
|
| 33 |
+
"**/*.ts",
|
| 34 |
+
"**/*.tsx",
|
| 35 |
+
".next/types/**/*.ts",
|
| 36 |
+
".next/dev/types/**/*.ts",
|
| 37 |
+
"**/*.mts"
|
| 38 |
+
],
|
| 39 |
+
"exclude": [
|
| 40 |
+
"node_modules"
|
| 41 |
+
]
|
| 42 |
+
}
|
packages/simulation-engine/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "@simulador-consorcio/simulation-engine",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"type": "module",
|
| 6 |
+
"main": "./src/index.js",
|
| 7 |
+
"exports": {
|
| 8 |
+
".": "./src/index.js"
|
| 9 |
+
},
|
| 10 |
+
"types": "./src/index.d.ts",
|
| 11 |
+
"scripts": {
|
| 12 |
+
"test": "node --test tests/*.test.js"
|
| 13 |
+
}
|
| 14 |
+
}
|
packages/simulation-engine/src/generated/rodobensFieldMatrix.v1.json
ADDED
|
@@ -0,0 +1,838 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"artifact_version": "1.0",
|
| 3 |
+
"source_workbook": "SIMULADOR RODOBENS - BTG 5.xlsm",
|
| 4 |
+
"rows": [
|
| 5 |
+
{
|
| 6 |
+
"field_id": "partner_name",
|
| 7 |
+
"plan": "degrau",
|
| 8 |
+
"section": "header",
|
| 9 |
+
"source_sheet": null,
|
| 10 |
+
"source_cell": null,
|
| 11 |
+
"source_formula": "=IF('Degrau e Degrau Reduzido'!#REF!=\"\",\"\",'Degrau e Degrau Reduzido'!#REF!)",
|
| 12 |
+
"layout_sheet": "Simulador Degrau",
|
| 13 |
+
"layout_cell": "D6",
|
| 14 |
+
"status": "pending",
|
| 15 |
+
"current_value": "#REF!",
|
| 16 |
+
"notes": "Único #REF! confirmado do workbook; sem origem homologada."
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"field_id": "proposal_valid_until",
|
| 20 |
+
"plan": "degrau",
|
| 21 |
+
"section": "header",
|
| 22 |
+
"source_sheet": "Simulador Degrau",
|
| 23 |
+
"source_cell": "D7",
|
| 24 |
+
"source_formula": "=TODAY()+3",
|
| 25 |
+
"layout_sheet": "Simulador Degrau",
|
| 26 |
+
"layout_cell": "D7",
|
| 27 |
+
"status": "presentation_only",
|
| 28 |
+
"current_value": "2026-02-13T00:00:00",
|
| 29 |
+
"notes": "Regra de apresentação via TODAY()+3."
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"field_id": "plan_label",
|
| 33 |
+
"plan": "degrau",
|
| 34 |
+
"section": "header",
|
| 35 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 36 |
+
"source_cell": "J36",
|
| 37 |
+
"source_formula": "=\"Degrau \" &P8",
|
| 38 |
+
"layout_sheet": "Simulador Degrau",
|
| 39 |
+
"layout_cell": "C9",
|
| 40 |
+
"status": "extracted",
|
| 41 |
+
"current_value": "Degrau Integral",
|
| 42 |
+
"notes": "Nome comercial do plano derivado de P8."
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"field_id": "numero_cotas",
|
| 46 |
+
"plan": "degrau",
|
| 47 |
+
"section": "input",
|
| 48 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 49 |
+
"source_cell": "M5",
|
| 50 |
+
"source_formula": null,
|
| 51 |
+
"layout_sheet": "Simulador Degrau",
|
| 52 |
+
"layout_cell": "I46",
|
| 53 |
+
"status": "extracted",
|
| 54 |
+
"current_value": 1,
|
| 55 |
+
"notes": "Fonte numérica em M5; layout final textual em L32/I46."
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"field_id": "credito_contratado",
|
| 59 |
+
"plan": "degrau",
|
| 60 |
+
"section": "output",
|
| 61 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 62 |
+
"source_cell": "T3",
|
| 63 |
+
"source_formula": "=P4",
|
| 64 |
+
"layout_sheet": "Simulador Degrau",
|
| 65 |
+
"layout_cell": "D33",
|
| 66 |
+
"status": "extracted",
|
| 67 |
+
"current_value": 1000000,
|
| 68 |
+
"notes": "Layout multiplica T3 por M5."
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"field_id": "lance_total",
|
| 72 |
+
"plan": "degrau",
|
| 73 |
+
"section": "output",
|
| 74 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 75 |
+
"source_cell": "C24",
|
| 76 |
+
"source_formula": "=IFERROR(S6*M5,\"\")",
|
| 77 |
+
"layout_sheet": "Simulador Degrau",
|
| 78 |
+
"layout_cell": "D34",
|
| 79 |
+
"status": "extracted",
|
| 80 |
+
"current_value": 650740.7407407408,
|
| 81 |
+
"notes": "Valor total consolidado do lance."
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"field_id": "lance_embutido_valor",
|
| 85 |
+
"plan": "degrau",
|
| 86 |
+
"section": "output",
|
| 87 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 88 |
+
"source_cell": "D27",
|
| 89 |
+
"source_formula": "=T9*M5",
|
| 90 |
+
"layout_sheet": "Simulador Degrau",
|
| 91 |
+
"layout_cell": "D35",
|
| 92 |
+
"status": "extracted",
|
| 93 |
+
"current_value": 300000,
|
| 94 |
+
"notes": "Deriva de AD32 = P4 * P10."
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"field_id": "recurso_proprio",
|
| 98 |
+
"plan": "degrau",
|
| 99 |
+
"section": "output",
|
| 100 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 101 |
+
"source_cell": "C28",
|
| 102 |
+
"source_formula": "=IFERROR((C24-D27),\"\")",
|
| 103 |
+
"layout_sheet": "Simulador Degrau",
|
| 104 |
+
"layout_cell": "D36",
|
| 105 |
+
"status": "extracted",
|
| 106 |
+
"current_value": 350740.7407407408,
|
| 107 |
+
"notes": "Valor residual após lance embutido."
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"field_id": "primeira_parcela",
|
| 111 |
+
"plan": "degrau",
|
| 112 |
+
"section": "output",
|
| 113 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 114 |
+
"source_cell": "C26",
|
| 115 |
+
"source_formula": "=S8*M5",
|
| 116 |
+
"layout_sheet": "Simulador Degrau",
|
| 117 |
+
"layout_cell": "D37",
|
| 118 |
+
"status": "extracted",
|
| 119 |
+
"current_value": 6851.8518518519,
|
| 120 |
+
"notes": "Escolhe parcela integral ou reduzida conforme o plano."
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"field_id": "credito_disponivel",
|
| 124 |
+
"plan": "degrau",
|
| 125 |
+
"section": "output",
|
| 126 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 127 |
+
"source_cell": "B42",
|
| 128 |
+
"source_formula": "=C33",
|
| 129 |
+
"layout_sheet": "Simulador Degrau",
|
| 130 |
+
"layout_cell": "D38",
|
| 131 |
+
"status": "extracted",
|
| 132 |
+
"current_value": 700000,
|
| 133 |
+
"notes": "Aponta para C33 na memória de cálculo."
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"field_id": "credito_liquido",
|
| 137 |
+
"plan": "degrau",
|
| 138 |
+
"section": "output",
|
| 139 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 140 |
+
"source_cell": "C33",
|
| 141 |
+
"source_formula": "=T15*M5",
|
| 142 |
+
"layout_sheet": "Simulador Degrau",
|
| 143 |
+
"layout_cell": "D42",
|
| 144 |
+
"status": "review",
|
| 145 |
+
"current_value": 700000,
|
| 146 |
+
"notes": "Hoje coincide com crédito disponível; requer saneamento semântico."
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"field_id": "furo",
|
| 150 |
+
"plan": "degrau",
|
| 151 |
+
"section": "input",
|
| 152 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 153 |
+
"source_cell": "M8",
|
| 154 |
+
"source_formula": null,
|
| 155 |
+
"layout_sheet": "Simulador Degrau",
|
| 156 |
+
"layout_cell": "D43",
|
| 157 |
+
"status": "extracted",
|
| 158 |
+
"current_value": 0,
|
| 159 |
+
"notes": "Quantidade de parcelas em atraso/furo."
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"field_id": "mes_previsto_lance",
|
| 163 |
+
"plan": "degrau",
|
| 164 |
+
"section": "input",
|
| 165 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 166 |
+
"source_cell": "P17",
|
| 167 |
+
"source_formula": null,
|
| 168 |
+
"layout_sheet": "Simulador Degrau",
|
| 169 |
+
"layout_cell": "D44",
|
| 170 |
+
"status": "review",
|
| 171 |
+
"current_value": 12,
|
| 172 |
+
"notes": "Layout rotula como mês de contemplação, mas a origem funcional é mês previsto de lance."
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"field_id": "seguro_prestamista",
|
| 176 |
+
"plan": "degrau",
|
| 177 |
+
"section": "output",
|
| 178 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 179 |
+
"source_cell": "AD46",
|
| 180 |
+
"source_formula": null,
|
| 181 |
+
"layout_sheet": "Simulador Degrau",
|
| 182 |
+
"layout_cell": "I42",
|
| 183 |
+
"status": "extracted_partial",
|
| 184 |
+
"current_value": 0,
|
| 185 |
+
"notes": "Fórmula de array; manter fixture própria."
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"field_id": "fundo_reserva",
|
| 189 |
+
"plan": "degrau",
|
| 190 |
+
"section": "output",
|
| 191 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 192 |
+
"source_cell": "AD43",
|
| 193 |
+
"source_formula": null,
|
| 194 |
+
"layout_sheet": "Simulador Degrau",
|
| 195 |
+
"layout_cell": "I43",
|
| 196 |
+
"status": "extracted",
|
| 197 |
+
"current_value": 0,
|
| 198 |
+
"notes": "Atualmente zero."
|
| 199 |
+
},
|
| 200 |
+
{
|
| 201 |
+
"field_id": "taxa_administracao_mensal_exibida",
|
| 202 |
+
"plan": "degrau",
|
| 203 |
+
"section": "output",
|
| 204 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 205 |
+
"source_cell": "J42",
|
| 206 |
+
"source_formula": "=ROUND((P6/P5),6)*100 & \" % a.m.\"",
|
| 207 |
+
"layout_sheet": "Simulador Degrau",
|
| 208 |
+
"layout_cell": "I44",
|
| 209 |
+
"status": "review",
|
| 210 |
+
"current_value": "0,1111 % a.m.",
|
| 211 |
+
"notes": "Layout mostra taxa mensal em vez de taxa total."
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"field_id": "taxa_adesao",
|
| 215 |
+
"plan": "degrau",
|
| 216 |
+
"section": "output",
|
| 217 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 218 |
+
"source_cell": "AD42",
|
| 219 |
+
"source_formula": null,
|
| 220 |
+
"layout_sheet": "Simulador Degrau",
|
| 221 |
+
"layout_cell": "I45",
|
| 222 |
+
"status": "extracted",
|
| 223 |
+
"current_value": 0,
|
| 224 |
+
"notes": "Atualmente zero."
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"field_id": "diluicao_50_faixa_1",
|
| 228 |
+
"plan": "degrau",
|
| 229 |
+
"section": "output",
|
| 230 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 231 |
+
"source_cell": "S11",
|
| 232 |
+
"source_formula": "=\"de \" & P17+1& \" º até \"&P5*0.5&\"º \"",
|
| 233 |
+
"layout_sheet": "Simulador Degrau",
|
| 234 |
+
"layout_cell": "H34",
|
| 235 |
+
"status": "extracted",
|
| 236 |
+
"current_value": "de 13 º até 108º ",
|
| 237 |
+
"notes": "Faixa textual de diluição 50% - primeira metade."
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"field_id": "diluicao_50_valor_1",
|
| 241 |
+
"plan": "degrau",
|
| 242 |
+
"section": "output",
|
| 243 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 244 |
+
"source_cell": "D29",
|
| 245 |
+
"source_formula": "=IFERROR(T11*M5,\"\")",
|
| 246 |
+
"layout_sheet": "Simulador Degrau",
|
| 247 |
+
"layout_cell": "I34",
|
| 248 |
+
"status": "extracted",
|
| 249 |
+
"current_value": 4511.0578204103,
|
| 250 |
+
"notes": "Valor monetário da primeira faixa de diluição 50%."
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"field_id": "diluicao_50_faixa_2",
|
| 254 |
+
"plan": "degrau",
|
| 255 |
+
"section": "output",
|
| 256 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 257 |
+
"source_cell": "S12",
|
| 258 |
+
"source_formula": "=IFERROR(\"de \" & (P5*0.5)+1 &\"º até \" & ROUND((P5-AD36),\"00\")&\"º\",\" \")",
|
| 259 |
+
"layout_sheet": "Simulador Degrau",
|
| 260 |
+
"layout_cell": "H35",
|
| 261 |
+
"status": "extracted",
|
| 262 |
+
"current_value": "de 109º até 151º",
|
| 263 |
+
"notes": "Faixa textual de diluição 50% - segunda metade."
|
| 264 |
+
},
|
| 265 |
+
{
|
| 266 |
+
"field_id": "diluicao_50_valor_2",
|
| 267 |
+
"plan": "degrau",
|
| 268 |
+
"section": "output",
|
| 269 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 270 |
+
"source_cell": "D30",
|
| 271 |
+
"source_formula": "=IFERROR(T12*M5,\"\")",
|
| 272 |
+
"layout_sheet": "Simulador Degrau",
|
| 273 |
+
"layout_cell": "I35",
|
| 274 |
+
"status": "extracted",
|
| 275 |
+
"current_value": 2288.8355981881,
|
| 276 |
+
"notes": "Valor monetário da segunda faixa de diluição 50%."
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
"field_id": "diluicao_100_faixa_1",
|
| 280 |
+
"plan": "degrau",
|
| 281 |
+
"section": "output",
|
| 282 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 283 |
+
"source_cell": "S13",
|
| 284 |
+
"source_formula": "=\"de \" & P17+1& \" º até \"&P5*0.5&\"º \"",
|
| 285 |
+
"layout_sheet": "Simulador Degrau",
|
| 286 |
+
"layout_cell": "H37",
|
| 287 |
+
"status": "extracted",
|
| 288 |
+
"current_value": "de 13 º até 108º ",
|
| 289 |
+
"notes": "Faixa textual de diluição 100% - primeira metade."
|
| 290 |
+
},
|
| 291 |
+
{
|
| 292 |
+
"field_id": "diluicao_100_valor_1",
|
| 293 |
+
"plan": "degrau",
|
| 294 |
+
"section": "output",
|
| 295 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 296 |
+
"source_cell": "D31",
|
| 297 |
+
"source_formula": "=IFERROR(T13*M5,\"\")",
|
| 298 |
+
"layout_sheet": "Simulador Degrau",
|
| 299 |
+
"layout_cell": "I37",
|
| 300 |
+
"status": "extracted",
|
| 301 |
+
"current_value": 3661.9462599855,
|
| 302 |
+
"notes": "Valor monetário da primeira faixa de diluição 100%."
|
| 303 |
+
},
|
| 304 |
+
{
|
| 305 |
+
"field_id": "diluicao_100_faixa_2",
|
| 306 |
+
"plan": "degrau",
|
| 307 |
+
"section": "output",
|
| 308 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 309 |
+
"source_cell": "S14",
|
| 310 |
+
"source_formula": "= \"de \" &(P5/2)+1 & \"º até \" &P5-M8 &\"º \"",
|
| 311 |
+
"layout_sheet": "Simulador Degrau",
|
| 312 |
+
"layout_cell": "H38",
|
| 313 |
+
"status": "extracted",
|
| 314 |
+
"current_value": "de 109º até 216º ",
|
| 315 |
+
"notes": "Faixa textual de diluição 100% - segunda metade."
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"field_id": "diluicao_100_valor_2",
|
| 319 |
+
"plan": "degrau",
|
| 320 |
+
"section": "output",
|
| 321 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 322 |
+
"source_cell": "D32",
|
| 323 |
+
"source_formula": "=IFERROR(T14*M5,\"\")",
|
| 324 |
+
"layout_sheet": "Simulador Degrau",
|
| 325 |
+
"layout_cell": "I38",
|
| 326 |
+
"status": "extracted",
|
| 327 |
+
"current_value": 1439.7240377633,
|
| 328 |
+
"notes": "Valor monetário da segunda faixa de diluição 100%."
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
"field_id": "lance_observacao",
|
| 332 |
+
"plan": "degrau",
|
| 333 |
+
"section": "presentation",
|
| 334 |
+
"source_sheet": "Degrau e Degrau Reduzido",
|
| 335 |
+
"source_cell": "L35",
|
| 336 |
+
"source_formula": "=IF(AND(M6=\"Livre\",P5>48),\"Opção de utilizar o lance para quitar até 03 parcelas seguintes à contemplação\",\"Opção de utilizar o lance para quitar até 01 parcela seguinte à contemplação\")",
|
| 337 |
+
"layout_sheet": "Simulador Degrau",
|
| 338 |
+
"layout_cell": "B45",
|
| 339 |
+
"status": "extracted",
|
| 340 |
+
"current_value": "Opção de utilizar o lance para quitar até 03 parcelas seguintes à contemplação",
|
| 341 |
+
"notes": "Texto comercial dependente de modalidade de lance."
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"field_id": "partner_name",
|
| 345 |
+
"plan": "linear",
|
| 346 |
+
"section": "header",
|
| 347 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 348 |
+
"source_cell": "AF1",
|
| 349 |
+
"source_formula": null,
|
| 350 |
+
"layout_sheet": "Simulador Linear",
|
| 351 |
+
"layout_cell": "D6",
|
| 352 |
+
"status": "review",
|
| 353 |
+
"current_value": "Paula",
|
| 354 |
+
"notes": "Aba chama o campo de Nome Cliente; layout imprime Parceiro."
|
| 355 |
+
},
|
| 356 |
+
{
|
| 357 |
+
"field_id": "proposal_valid_until",
|
| 358 |
+
"plan": "linear",
|
| 359 |
+
"section": "header",
|
| 360 |
+
"source_sheet": "Simulador Linear",
|
| 361 |
+
"source_cell": "D7",
|
| 362 |
+
"source_formula": "=TODAY()+3",
|
| 363 |
+
"layout_sheet": "Simulador Linear",
|
| 364 |
+
"layout_cell": "D7",
|
| 365 |
+
"status": "presentation_only",
|
| 366 |
+
"current_value": "2026-02-13T00:00:00",
|
| 367 |
+
"notes": "Regra de apresentação via TODAY()+3."
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
"field_id": "plan_label",
|
| 371 |
+
"plan": "linear",
|
| 372 |
+
"section": "header",
|
| 373 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 374 |
+
"source_cell": "K8",
|
| 375 |
+
"source_formula": null,
|
| 376 |
+
"layout_sheet": "Simulador Linear",
|
| 377 |
+
"layout_cell": "C9",
|
| 378 |
+
"status": "extracted",
|
| 379 |
+
"current_value": "Linear Reduzido 70%",
|
| 380 |
+
"notes": "Nome do plano atual."
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"field_id": "grupo",
|
| 384 |
+
"plan": "linear",
|
| 385 |
+
"section": "input",
|
| 386 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 387 |
+
"source_cell": "G4",
|
| 388 |
+
"source_formula": null,
|
| 389 |
+
"layout_sheet": "Simulador Linear",
|
| 390 |
+
"layout_cell": "D36",
|
| 391 |
+
"status": "extracted",
|
| 392 |
+
"current_value": 2234,
|
| 393 |
+
"notes": "Grupo comercial."
|
| 394 |
+
},
|
| 395 |
+
{
|
| 396 |
+
"field_id": "numero_cotas",
|
| 397 |
+
"plan": "linear",
|
| 398 |
+
"section": "input",
|
| 399 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 400 |
+
"source_cell": "G5",
|
| 401 |
+
"source_formula": null,
|
| 402 |
+
"layout_sheet": null,
|
| 403 |
+
"layout_cell": null,
|
| 404 |
+
"status": "extracted",
|
| 405 |
+
"current_value": 5,
|
| 406 |
+
"notes": "Quantidade numérica; layout só expõe texto em AE13."
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"field_id": "modalidade_lance",
|
| 410 |
+
"plan": "linear",
|
| 411 |
+
"section": "input",
|
| 412 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 413 |
+
"source_cell": "G6",
|
| 414 |
+
"source_formula": null,
|
| 415 |
+
"layout_sheet": null,
|
| 416 |
+
"layout_cell": null,
|
| 417 |
+
"status": "extracted",
|
| 418 |
+
"current_value": "Fixo",
|
| 419 |
+
"notes": "Livre ou fixo."
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"field_id": "tipo_produto",
|
| 423 |
+
"plan": "linear",
|
| 424 |
+
"section": "input",
|
| 425 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 426 |
+
"source_cell": "G7",
|
| 427 |
+
"source_formula": null,
|
| 428 |
+
"layout_sheet": null,
|
| 429 |
+
"layout_cell": null,
|
| 430 |
+
"status": "extracted",
|
| 431 |
+
"current_value": "Imóvel",
|
| 432 |
+
"notes": "Produto do cenário."
|
| 433 |
+
},
|
| 434 |
+
{
|
| 435 |
+
"field_id": "furo",
|
| 436 |
+
"plan": "linear",
|
| 437 |
+
"section": "input",
|
| 438 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 439 |
+
"source_cell": "AN19",
|
| 440 |
+
"source_formula": "=G8",
|
| 441 |
+
"layout_sheet": "Simulador Linear",
|
| 442 |
+
"layout_cell": "D35",
|
| 443 |
+
"status": "extracted",
|
| 444 |
+
"current_value": 0,
|
| 445 |
+
"notes": "AN19 espelha G8."
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"field_id": "credito_base",
|
| 449 |
+
"plan": "linear",
|
| 450 |
+
"section": "input",
|
| 451 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 452 |
+
"source_cell": "K4",
|
| 453 |
+
"source_formula": null,
|
| 454 |
+
"layout_sheet": null,
|
| 455 |
+
"layout_cell": null,
|
| 456 |
+
"status": "extracted",
|
| 457 |
+
"current_value": 200000,
|
| 458 |
+
"notes": "Base do cálculo."
|
| 459 |
+
},
|
| 460 |
+
{
|
| 461 |
+
"field_id": "prazo_cliente",
|
| 462 |
+
"plan": "linear",
|
| 463 |
+
"section": "input",
|
| 464 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 465 |
+
"source_cell": "K5",
|
| 466 |
+
"source_formula": null,
|
| 467 |
+
"layout_sheet": null,
|
| 468 |
+
"layout_cell": null,
|
| 469 |
+
"status": "extracted",
|
| 470 |
+
"current_value": 180,
|
| 471 |
+
"notes": "Prazo em meses."
|
| 472 |
+
},
|
| 473 |
+
{
|
| 474 |
+
"field_id": "taxa_administracao_total",
|
| 475 |
+
"plan": "linear",
|
| 476 |
+
"section": "input",
|
| 477 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 478 |
+
"source_cell": "K6",
|
| 479 |
+
"source_formula": null,
|
| 480 |
+
"layout_sheet": null,
|
| 481 |
+
"layout_cell": null,
|
| 482 |
+
"status": "extracted",
|
| 483 |
+
"current_value": 0.21,
|
| 484 |
+
"notes": "Taxa contratual total."
|
| 485 |
+
},
|
| 486 |
+
{
|
| 487 |
+
"field_id": "taxa_administracao_mensal_exibida",
|
| 488 |
+
"plan": "linear",
|
| 489 |
+
"section": "output",
|
| 490 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 491 |
+
"source_cell": "M75",
|
| 492 |
+
"source_formula": "=ROUND(((K6/K5))*100,5)&\" % a.m.\"",
|
| 493 |
+
"layout_sheet": "Simulador Linear",
|
| 494 |
+
"layout_cell": "I36",
|
| 495 |
+
"status": "review",
|
| 496 |
+
"current_value": "0.11667 % a.m.",
|
| 497 |
+
"notes": "Layout mostra taxa mensal e não a taxa total."
|
| 498 |
+
},
|
| 499 |
+
{
|
| 500 |
+
"field_id": "seguro_prestamista",
|
| 501 |
+
"plan": "linear",
|
| 502 |
+
"section": "output",
|
| 503 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 504 |
+
"source_cell": "AF30",
|
| 505 |
+
"source_formula": "=IF(K7=\"Não\",0,_xlfn.XLOOKUP(G7,Tabela2[Produto],Tabela2[Taxa],0,0))",
|
| 506 |
+
"layout_sheet": "Simulador Linear",
|
| 507 |
+
"layout_cell": "I35",
|
| 508 |
+
"status": "extracted_partial",
|
| 509 |
+
"current_value": 0.0003245,
|
| 510 |
+
"notes": "Lookup por produto em Tabela2."
|
| 511 |
+
},
|
| 512 |
+
{
|
| 513 |
+
"field_id": "tipo_lance",
|
| 514 |
+
"plan": "linear",
|
| 515 |
+
"section": "input",
|
| 516 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 517 |
+
"source_cell": "K9",
|
| 518 |
+
"source_formula": null,
|
| 519 |
+
"layout_sheet": null,
|
| 520 |
+
"layout_cell": null,
|
| 521 |
+
"status": "extracted",
|
| 522 |
+
"current_value": "Lance Parcela Integral",
|
| 523 |
+
"notes": "Tipo de lance do cenário."
|
| 524 |
+
},
|
| 525 |
+
{
|
| 526 |
+
"field_id": "embutido_percentual",
|
| 527 |
+
"plan": "linear",
|
| 528 |
+
"section": "input",
|
| 529 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 530 |
+
"source_cell": "K10",
|
| 531 |
+
"source_formula": null,
|
| 532 |
+
"layout_sheet": null,
|
| 533 |
+
"layout_cell": null,
|
| 534 |
+
"status": "extracted",
|
| 535 |
+
"current_value": 0,
|
| 536 |
+
"notes": "Percentual de embutido."
|
| 537 |
+
},
|
| 538 |
+
{
|
| 539 |
+
"field_id": "parcelas_lance",
|
| 540 |
+
"plan": "linear",
|
| 541 |
+
"section": "input",
|
| 542 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 543 |
+
"source_cell": "K13",
|
| 544 |
+
"source_formula": null,
|
| 545 |
+
"layout_sheet": null,
|
| 546 |
+
"layout_cell": null,
|
| 547 |
+
"status": "extracted",
|
| 548 |
+
"current_value": 54,
|
| 549 |
+
"notes": "Quantidade de parcelas do lance."
|
| 550 |
+
},
|
| 551 |
+
{
|
| 552 |
+
"field_id": "mes_previsto_lance",
|
| 553 |
+
"plan": "linear",
|
| 554 |
+
"section": "input",
|
| 555 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 556 |
+
"source_cell": "K14",
|
| 557 |
+
"source_formula": null,
|
| 558 |
+
"layout_sheet": "Simulador Linear",
|
| 559 |
+
"layout_cell": "D37",
|
| 560 |
+
"status": "review",
|
| 561 |
+
"current_value": 36,
|
| 562 |
+
"notes": "Layout trata como mês de contemplação."
|
| 563 |
+
},
|
| 564 |
+
{
|
| 565 |
+
"field_id": "lance_total",
|
| 566 |
+
"plan": "linear",
|
| 567 |
+
"section": "output",
|
| 568 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 569 |
+
"source_cell": "AN20",
|
| 570 |
+
"source_formula": "=IFERROR(O7*G5,\"Verificar Tipo de Lance\")",
|
| 571 |
+
"layout_sheet": null,
|
| 572 |
+
"layout_cell": null,
|
| 573 |
+
"status": "extracted",
|
| 574 |
+
"current_value": 384202.83,
|
| 575 |
+
"notes": "AN20 consolida o valor total por número de cotas."
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"field_id": "credito_disponivel",
|
| 579 |
+
"plan": "linear",
|
| 580 |
+
"section": "output",
|
| 581 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 582 |
+
"source_cell": "AN27",
|
| 583 |
+
"source_formula": "=O14*G5",
|
| 584 |
+
"layout_sheet": null,
|
| 585 |
+
"layout_cell": null,
|
| 586 |
+
"status": "extracted",
|
| 587 |
+
"current_value": 1000000,
|
| 588 |
+
"notes": "AN27 consolida O14 por número de cotas."
|
| 589 |
+
},
|
| 590 |
+
{
|
| 591 |
+
"field_id": "diluicao_50_texto",
|
| 592 |
+
"plan": "linear",
|
| 593 |
+
"section": "output",
|
| 594 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 595 |
+
"source_cell": "AE12",
|
| 596 |
+
"source_formula": "=IF(K8=\"Linear Integral\",\"\",\"Plano de Parcela Reduzida até a contemplação\")",
|
| 597 |
+
"layout_sheet": "Simulador Linear",
|
| 598 |
+
"layout_cell": "B38",
|
| 599 |
+
"status": "extracted_partial",
|
| 600 |
+
"current_value": "Plano de Parcela Reduzida até a contemplação",
|
| 601 |
+
"notes": "Layout não expõe os valores monetários de diluição."
|
| 602 |
+
},
|
| 603 |
+
{
|
| 604 |
+
"field_id": "diluicao_50_valor",
|
| 605 |
+
"plan": "linear",
|
| 606 |
+
"section": "output",
|
| 607 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 608 |
+
"source_cell": "O12",
|
| 609 |
+
"source_formula": "=IF(IFERROR(G18-AF18,\"Verificar Tipo de Lance\")<0,\"\",IFERROR(G18-AF18,\"Verificar Tipo de Lance\"))",
|
| 610 |
+
"layout_sheet": null,
|
| 611 |
+
"layout_cell": null,
|
| 612 |
+
"status": "extracted",
|
| 613 |
+
"current_value": 1218.6975213675,
|
| 614 |
+
"notes": "Valor monetário da diluição 50%."
|
| 615 |
+
},
|
| 616 |
+
{
|
| 617 |
+
"field_id": "diluicao_100_faixa",
|
| 618 |
+
"plan": "linear",
|
| 619 |
+
"section": "output",
|
| 620 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 621 |
+
"source_cell": "N13",
|
| 622 |
+
"source_formula": "=\"de \"&K14+1&\"º\" & \" a \" &(K5)-G8& \"º\"",
|
| 623 |
+
"layout_sheet": null,
|
| 624 |
+
"layout_cell": null,
|
| 625 |
+
"status": "extracted",
|
| 626 |
+
"current_value": "de 37º a 180º",
|
| 627 |
+
"notes": "Faixa textual da diluição 100%."
|
| 628 |
+
},
|
| 629 |
+
{
|
| 630 |
+
"field_id": "diluicao_100_valor",
|
| 631 |
+
"plan": "linear",
|
| 632 |
+
"section": "output",
|
| 633 |
+
"source_sheet": "Linear e Linear Reduzido",
|
| 634 |
+
"source_cell": "O13",
|
| 635 |
+
"source_formula": "=IFERROR(G18-AF19,\"Verificar Tipo de Lance\")",
|
| 636 |
+
"layout_sheet": null,
|
| 637 |
+
"layout_cell": null,
|
| 638 |
+
"status": "extracted",
|
| 639 |
+
"current_value": 990.1917361111,
|
| 640 |
+
"notes": "Valor monetário da diluição 100%."
|
| 641 |
+
},
|
| 642 |
+
{
|
| 643 |
+
"field_id": "partner_name",
|
| 644 |
+
"plan": "pontual",
|
| 645 |
+
"section": "header",
|
| 646 |
+
"source_sheet": "Pontual",
|
| 647 |
+
"source_cell": "AB6",
|
| 648 |
+
"source_formula": null,
|
| 649 |
+
"layout_sheet": "Simulador Pontual",
|
| 650 |
+
"layout_cell": "D6",
|
| 651 |
+
"status": "extracted",
|
| 652 |
+
"current_value": "Paula",
|
| 653 |
+
"notes": "Campo simples e estável."
|
| 654 |
+
},
|
| 655 |
+
{
|
| 656 |
+
"field_id": "taxa_adesao",
|
| 657 |
+
"plan": "pontual",
|
| 658 |
+
"section": "output",
|
| 659 |
+
"source_sheet": "Pontual",
|
| 660 |
+
"source_cell": "AB1",
|
| 661 |
+
"source_formula": null,
|
| 662 |
+
"layout_sheet": "Simulador Pontual",
|
| 663 |
+
"layout_cell": "D44",
|
| 664 |
+
"status": "extracted",
|
| 665 |
+
"current_value": 0,
|
| 666 |
+
"notes": "Atualmente zero."
|
| 667 |
+
},
|
| 668 |
+
{
|
| 669 |
+
"field_id": "fundo_reserva",
|
| 670 |
+
"plan": "pontual",
|
| 671 |
+
"section": "output",
|
| 672 |
+
"source_sheet": "Pontual",
|
| 673 |
+
"source_cell": "AB5",
|
| 674 |
+
"source_formula": null,
|
| 675 |
+
"layout_sheet": "Simulador Pontual",
|
| 676 |
+
"layout_cell": "I44",
|
| 677 |
+
"status": "extracted",
|
| 678 |
+
"current_value": 0,
|
| 679 |
+
"notes": "Atualmente zero."
|
| 680 |
+
},
|
| 681 |
+
{
|
| 682 |
+
"field_id": "furo",
|
| 683 |
+
"plan": "pontual",
|
| 684 |
+
"section": "input",
|
| 685 |
+
"source_sheet": "Pontual",
|
| 686 |
+
"source_cell": "O4",
|
| 687 |
+
"source_formula": null,
|
| 688 |
+
"layout_sheet": "Simulador Pontual",
|
| 689 |
+
"layout_cell": "D43",
|
| 690 |
+
"status": "extracted",
|
| 691 |
+
"current_value": 0,
|
| 692 |
+
"notes": "Quantidade de parcelas em atraso/furo."
|
| 693 |
+
},
|
| 694 |
+
{
|
| 695 |
+
"field_id": "option_title",
|
| 696 |
+
"plan": "pontual",
|
| 697 |
+
"section": "output",
|
| 698 |
+
"source_sheet": "Pontual",
|
| 699 |
+
"source_cell": "AA22",
|
| 700 |
+
"source_formula": "=IF($AB$3=\"Automóvel\",G31,D31)",
|
| 701 |
+
"layout_sheet": "Simulador Pontual",
|
| 702 |
+
"layout_cell": "C32",
|
| 703 |
+
"status": "domain_conflict",
|
| 704 |
+
"current_value": "Contemplação por Sorteio (Até 12 Meses)",
|
| 705 |
+
"notes": "Depende de AB3 e alterna entre branches Auto/Imóvel."
|
| 706 |
+
},
|
| 707 |
+
{
|
| 708 |
+
"field_id": "produto_base_controle",
|
| 709 |
+
"plan": "pontual",
|
| 710 |
+
"section": "input",
|
| 711 |
+
"source_sheet": "Pontual",
|
| 712 |
+
"source_cell": "AB3",
|
| 713 |
+
"source_formula": null,
|
| 714 |
+
"layout_sheet": null,
|
| 715 |
+
"layout_cell": null,
|
| 716 |
+
"status": "domain_conflict",
|
| 717 |
+
"current_value": "Automóvel",
|
| 718 |
+
"notes": "Workbook atual gravado como Automóvel."
|
| 719 |
+
},
|
| 720 |
+
{
|
| 721 |
+
"field_id": "tipo_produto_operacional",
|
| 722 |
+
"plan": "pontual",
|
| 723 |
+
"section": "input",
|
| 724 |
+
"source_sheet": "Pontual",
|
| 725 |
+
"source_cell": "O6",
|
| 726 |
+
"source_formula": null,
|
| 727 |
+
"layout_sheet": null,
|
| 728 |
+
"layout_cell": null,
|
| 729 |
+
"status": "domain_conflict",
|
| 730 |
+
"current_value": "Automóvel",
|
| 731 |
+
"notes": "Workbook atual gravado como Automóvel."
|
| 732 |
+
},
|
| 733 |
+
{
|
| 734 |
+
"field_id": "produto_chave_auto",
|
| 735 |
+
"plan": "pontual",
|
| 736 |
+
"section": "control",
|
| 737 |
+
"source_sheet": "Pontual",
|
| 738 |
+
"source_cell": "I47",
|
| 739 |
+
"source_formula": null,
|
| 740 |
+
"layout_sheet": null,
|
| 741 |
+
"layout_cell": null,
|
| 742 |
+
"status": "domain_conflict",
|
| 743 |
+
"current_value": "Automóvel",
|
| 744 |
+
"notes": "Chave de comparação legada para Auto."
|
| 745 |
+
},
|
| 746 |
+
{
|
| 747 |
+
"field_id": "produto_chave_imovel",
|
| 748 |
+
"plan": "pontual",
|
| 749 |
+
"section": "control",
|
| 750 |
+
"source_sheet": "Pontual",
|
| 751 |
+
"source_cell": "I48",
|
| 752 |
+
"source_formula": null,
|
| 753 |
+
"layout_sheet": null,
|
| 754 |
+
"layout_cell": null,
|
| 755 |
+
"status": "domain_conflict",
|
| 756 |
+
"current_value": "Imóvel",
|
| 757 |
+
"notes": "Chave de comparação legada para Imóvel."
|
| 758 |
+
},
|
| 759 |
+
{
|
| 760 |
+
"field_id": "numero_cotas",
|
| 761 |
+
"plan": "pontual",
|
| 762 |
+
"section": "input",
|
| 763 |
+
"source_sheet": "Pontual",
|
| 764 |
+
"source_cell": "O5",
|
| 765 |
+
"source_formula": null,
|
| 766 |
+
"layout_sheet": null,
|
| 767 |
+
"layout_cell": null,
|
| 768 |
+
"status": "extracted",
|
| 769 |
+
"current_value": 1,
|
| 770 |
+
"notes": "Quantidade numérica; texto final em P8."
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"field_id": "numero_cotas_texto",
|
| 774 |
+
"plan": "pontual",
|
| 775 |
+
"section": "presentation",
|
| 776 |
+
"source_sheet": "Pontual",
|
| 777 |
+
"source_cell": "P8",
|
| 778 |
+
"source_formula": "=IF(O5=1,O5&\" cota\",O5&\" cotas\")",
|
| 779 |
+
"layout_sheet": null,
|
| 780 |
+
"layout_cell": null,
|
| 781 |
+
"status": "extracted",
|
| 782 |
+
"current_value": "1 cota",
|
| 783 |
+
"notes": "Texto comercial da quantidade de cotas."
|
| 784 |
+
},
|
| 785 |
+
{
|
| 786 |
+
"field_id": "valor_parcela_com_seguro",
|
| 787 |
+
"plan": "pontual",
|
| 788 |
+
"section": "output",
|
| 789 |
+
"source_sheet": "Pontual",
|
| 790 |
+
"source_cell": "L7",
|
| 791 |
+
"source_formula": "=O47/L4",
|
| 792 |
+
"layout_sheet": null,
|
| 793 |
+
"layout_cell": null,
|
| 794 |
+
"status": "extracted",
|
| 795 |
+
"current_value": 1323.2462667038,
|
| 796 |
+
"notes": "Valor da parcela com seguro."
|
| 797 |
+
},
|
| 798 |
+
{
|
| 799 |
+
"field_id": "valor_parcela_sem_seguro",
|
| 800 |
+
"plan": "pontual",
|
| 801 |
+
"section": "output",
|
| 802 |
+
"source_sheet": "Pontual",
|
| 803 |
+
"source_cell": "L8",
|
| 804 |
+
"source_formula": "=O44/L4",
|
| 805 |
+
"layout_sheet": null,
|
| 806 |
+
"layout_cell": null,
|
| 807 |
+
"status": "extracted",
|
| 808 |
+
"current_value": 1257.6210833333,
|
| 809 |
+
"notes": "Valor da parcela sem seguro."
|
| 810 |
+
},
|
| 811 |
+
{
|
| 812 |
+
"field_id": "credito_mais_taxas",
|
| 813 |
+
"plan": "pontual",
|
| 814 |
+
"section": "output",
|
| 815 |
+
"source_sheet": "Pontual",
|
| 816 |
+
"source_cell": "O44",
|
| 817 |
+
"source_formula": "=L3+(L3*L6)",
|
| 818 |
+
"layout_sheet": null,
|
| 819 |
+
"layout_cell": null,
|
| 820 |
+
"status": "extracted",
|
| 821 |
+
"current_value": 75457.265,
|
| 822 |
+
"notes": "Base de cálculo do saldo devedor."
|
| 823 |
+
},
|
| 824 |
+
{
|
| 825 |
+
"field_id": "saldo_devedor_total",
|
| 826 |
+
"plan": "pontual",
|
| 827 |
+
"section": "output",
|
| 828 |
+
"source_sheet": "Pontual",
|
| 829 |
+
"source_cell": "O47",
|
| 830 |
+
"source_formula": "=O44+O46",
|
| 831 |
+
"layout_sheet": null,
|
| 832 |
+
"layout_cell": null,
|
| 833 |
+
"status": "extracted",
|
| 834 |
+
"current_value": 79394.77600223,
|
| 835 |
+
"notes": "O44 + O46."
|
| 836 |
+
}
|
| 837 |
+
]
|
| 838 |
+
}
|
packages/simulation-engine/src/index.d.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface ParametrosPlano {
|
| 2 |
+
id_plano: string;
|
| 3 |
+
nome_plano: string;
|
| 4 |
+
prazo_meses: number;
|
| 5 |
+
taxa_administracao_total: number;
|
| 6 |
+
fundo_reserva_mensal: number;
|
| 7 |
+
seguro_prestamista: number;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export interface SimulacaoInput {
|
| 11 |
+
credito_desejado: number;
|
| 12 |
+
parametros: ParametrosPlano;
|
| 13 |
+
tem_lance: boolean;
|
| 14 |
+
lance_percentual?: number;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export interface ResultadoParcela {
|
| 18 |
+
fundo_comum: number;
|
| 19 |
+
taxa_administracao: number;
|
| 20 |
+
fundo_reserva: number;
|
| 21 |
+
seguro: number;
|
| 22 |
+
total: number;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
export interface ResultadoSimulacao {
|
| 26 |
+
credito: number;
|
| 27 |
+
prazo: number;
|
| 28 |
+
parcela_integral: ResultadoParcela;
|
| 29 |
+
parcela_reduzida?: ResultadoParcela;
|
| 30 |
+
valor_lance?: number;
|
| 31 |
+
prazo_apos_lance?: number;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
export interface RodobensParityInput {
|
| 35 |
+
plan: "degrau" | "linear" | "pontual";
|
| 36 |
+
partner_name?: string | null;
|
| 37 |
+
grupo?: number | string | null;
|
| 38 |
+
numero_cotas?: number;
|
| 39 |
+
modalidade_lance?: string;
|
| 40 |
+
tipo_produto?: string;
|
| 41 |
+
produto_base_controle?: string;
|
| 42 |
+
furo?: number;
|
| 43 |
+
credito: number;
|
| 44 |
+
prazo_cliente: number;
|
| 45 |
+
taxa_administrativa_total: number;
|
| 46 |
+
seguro_prestamista_habilitado?: string | boolean;
|
| 47 |
+
plano?: string;
|
| 48 |
+
tipo_lance?: string;
|
| 49 |
+
embutido_percentual?: number;
|
| 50 |
+
parcelas_lance?: number;
|
| 51 |
+
mes_previsto_lance?: number;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
export interface ResultadoParidadeRodobens {
|
| 55 |
+
[fieldId: string]: string | number | null;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
export interface TransacaoPortabilidade {
|
| 59 |
+
valor: number;
|
| 60 |
+
contemplada: boolean;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
export interface SimulacaoSecundarioInput {
|
| 64 |
+
fluxos_mensais: number[];
|
| 65 |
+
transacoes_portabilidade?: TransacaoPortabilidade[];
|
| 66 |
+
portabilidade_percentual_contemplada?: number;
|
| 67 |
+
portabilidade_percentual_nao_contemplada?: number;
|
| 68 |
+
volume_contratado?: number;
|
| 69 |
+
fee_percentual?: number;
|
| 70 |
+
percentual_cartas_parcela_reduzida?: number;
|
| 71 |
+
meses_pos_ultima_contemplacao?: number;
|
| 72 |
+
sem_correcao_antes_contemplacao?: boolean;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
export interface RegrasSecundarioAplicadas {
|
| 76 |
+
portabilidade_contemplada: number;
|
| 77 |
+
portabilidade_nao_contemplada: number;
|
| 78 |
+
sem_correcao_antes_contemplacao: boolean;
|
| 79 |
+
fee_sobre_volume_reduzido_70: boolean;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
export interface ResultadoSimulacaoSecundario {
|
| 83 |
+
fluxo_ajustado: number[];
|
| 84 |
+
custo_portabilidade: number;
|
| 85 |
+
custo_fee_parcela_reduzida: number;
|
| 86 |
+
custo_total_deducoes: number;
|
| 87 |
+
caixa_maximo_negativo: number;
|
| 88 |
+
payback_meses: number | null;
|
| 89 |
+
tir_mensal: number | null;
|
| 90 |
+
tir_anual: number | null;
|
| 91 |
+
regras_aplicadas: RegrasSecundarioAplicadas;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
export interface ScpFlowPatternSummary {
|
| 95 |
+
id: string;
|
| 96 |
+
nome: string;
|
| 97 |
+
descricao: string;
|
| 98 |
+
pontos: number;
|
| 99 |
+
inicio?: string;
|
| 100 |
+
fim?: string;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
export interface ScpClientDefault {
|
| 104 |
+
client_id: string;
|
| 105 |
+
client_name: string;
|
| 106 |
+
default_pattern_id: string;
|
| 107 |
+
status: string;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
export interface ScpFlowPoint {
|
| 111 |
+
periodo: string;
|
| 112 |
+
fluxo: number;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
export interface MontarFluxoClienteScpInput {
|
| 116 |
+
client_id: string;
|
| 117 |
+
pattern_id?: string;
|
| 118 |
+
escala_fluxo?: number;
|
| 119 |
+
deslocamento_meses?: number;
|
| 120 |
+
novo_client_id?: string;
|
| 121 |
+
novo_client_name?: string;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
export interface MontarFluxoClienteScpOutput {
|
| 125 |
+
client_id: string;
|
| 126 |
+
client_name: string;
|
| 127 |
+
source_client_id: string;
|
| 128 |
+
pattern_id: string;
|
| 129 |
+
pattern_name: string;
|
| 130 |
+
escala_fluxo: number;
|
| 131 |
+
deslocamento_meses: number;
|
| 132 |
+
pontos: ScpFlowPoint[];
|
| 133 |
+
fluxos_mensais: number[];
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
export interface SimularSecundarioComPerfilScpInput
|
| 137 |
+
extends MontarFluxoClienteScpInput,
|
| 138 |
+
Omit<SimulacaoSecundarioInput, "fluxos_mensais"> {}
|
| 139 |
+
|
| 140 |
+
export interface ResultadoSimulacaoSecundarioComPerfil
|
| 141 |
+
extends ResultadoSimulacaoSecundario {
|
| 142 |
+
fluxo_cliente: MontarFluxoClienteScpOutput;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
export interface SimularConversaoScpConsorcioInput
|
| 146 |
+
extends SimularSecundarioComPerfilScpInput {
|
| 147 |
+
valor_imovel_base?: number;
|
| 148 |
+
desconto_imovel_percentual?: number;
|
| 149 |
+
percentual_entrada_imovel?: number;
|
| 150 |
+
agio_consorcio_percentual?: number;
|
| 151 |
+
taxa_custos_transacao_percentual?: number;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
export interface ResultadoModoConversaoScpConsorcio {
|
| 155 |
+
natureza_cenario: string;
|
| 156 |
+
observacao: string;
|
| 157 |
+
valor_imovel_base: number;
|
| 158 |
+
desconto_imovel_percentual: number;
|
| 159 |
+
percentual_entrada_imovel: number;
|
| 160 |
+
agio_consorcio_percentual: number;
|
| 161 |
+
taxa_custos_transacao_percentual: number;
|
| 162 |
+
valor_imovel_com_desconto: number;
|
| 163 |
+
valor_entrada_imovel: number;
|
| 164 |
+
valor_credito_consorcio: number;
|
| 165 |
+
valor_custos_transacao: number;
|
| 166 |
+
investimento_total_conversao: number;
|
| 167 |
+
valor_saida_consorcio_secundario: number;
|
| 168 |
+
lucro_projetado_conversao: number;
|
| 169 |
+
roi_projetado_conversao: number | null;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
export interface ResultadoSimulacaoConversaoScpConsorcio
|
| 173 |
+
extends ResultadoSimulacaoSecundarioComPerfil {
|
| 174 |
+
modo_conversao: ResultadoModoConversaoScpConsorcio;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
export declare function calcularSimulacao(input: SimulacaoInput): ResultadoSimulacao;
|
| 178 |
+
export declare function calcularParidadeRodobens(
|
| 179 |
+
input: RodobensParityInput
|
| 180 |
+
): ResultadoParidadeRodobens;
|
| 181 |
+
export declare function calcularIndicadoresSecundario(
|
| 182 |
+
input: SimulacaoSecundarioInput
|
| 183 |
+
): ResultadoSimulacaoSecundario;
|
| 184 |
+
export declare function listarPerfisFluxoScpDefault(): {
|
| 185 |
+
clients: ScpClientDefault[];
|
| 186 |
+
patterns: ScpFlowPatternSummary[];
|
| 187 |
+
};
|
| 188 |
+
export declare function montarFluxoClienteScp(
|
| 189 |
+
input: MontarFluxoClienteScpInput
|
| 190 |
+
): MontarFluxoClienteScpOutput;
|
| 191 |
+
export declare function simularSecundarioComPerfilScp(
|
| 192 |
+
input: SimularSecundarioComPerfilScpInput
|
| 193 |
+
): ResultadoSimulacaoSecundarioComPerfil;
|
| 194 |
+
export declare function simularModoConversaoScpConsorcio(
|
| 195 |
+
input: SimularConversaoScpConsorcioInput
|
| 196 |
+
): ResultadoSimulacaoConversaoScpConsorcio;
|
packages/simulation-engine/src/index.js
ADDED
|
@@ -0,0 +1,823 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { SCP_CLIENTS_DEFAULT, SCP_FLOW_PATTERNS_DEFAULT } from "./scpDefaults.js";
|
| 2 |
+
import rodobensFieldMatrixBundle from "./generated/rodobensFieldMatrix.v1.json" with { type: "json" };
|
| 3 |
+
|
| 4 |
+
function ensurePositiveNumber(name, value) {
|
| 5 |
+
if (typeof value !== "number" || Number.isNaN(value) || value <= 0) {
|
| 6 |
+
throw new Error(`${name} deve ser um numero maior que zero`);
|
| 7 |
+
}
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
function ensureNonNegativeNumber(name, value) {
|
| 11 |
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
| 12 |
+
throw new Error(`${name} deve ser um numero nao negativo`);
|
| 13 |
+
}
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
function ensurePercent(name, value) {
|
| 17 |
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0 || value > 1) {
|
| 18 |
+
throw new Error(`${name} deve estar entre 0 e 1`);
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
function ensureNumberArray(name, value) {
|
| 23 |
+
if (!Array.isArray(value) || value.length === 0) {
|
| 24 |
+
throw new Error(`${name} deve ser uma lista com ao menos um valor`);
|
| 25 |
+
}
|
| 26 |
+
for (const item of value) {
|
| 27 |
+
if (typeof item !== "number" || Number.isNaN(item)) {
|
| 28 |
+
throw new Error(`${name} deve conter apenas numeros`);
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
function ensureInteger(name, value) {
|
| 34 |
+
if (!Number.isInteger(value)) {
|
| 35 |
+
throw new Error(`${name} deve ser inteiro`);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
function normalizeLookupKey(value) {
|
| 40 |
+
return String(value || "")
|
| 41 |
+
.normalize("NFD")
|
| 42 |
+
.replace(/[\u0300-\u036f]/g, "")
|
| 43 |
+
.trim()
|
| 44 |
+
.toLowerCase();
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
function formatNumberWithComma(value, decimals) {
|
| 48 |
+
return Number(value).toFixed(decimals).replace(".", ",");
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function roundTo(value, decimals) {
|
| 52 |
+
const factor = 10 ** decimals;
|
| 53 |
+
return Math.round(value * factor) / factor;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
const RODOBENS_FIELD_MATRIX_ROWS = Array.isArray(rodobensFieldMatrixBundle?.rows)
|
| 57 |
+
? rodobensFieldMatrixBundle.rows
|
| 58 |
+
: [];
|
| 59 |
+
|
| 60 |
+
const RODOBENS_FIELD_MATRIX_BY_PLAN = RODOBENS_FIELD_MATRIX_ROWS.reduce((acc, row) => {
|
| 61 |
+
const current = acc.get(row.plan) || [];
|
| 62 |
+
current.push(row);
|
| 63 |
+
acc.set(row.plan, current);
|
| 64 |
+
return acc;
|
| 65 |
+
}, new Map());
|
| 66 |
+
|
| 67 |
+
const RODOBENS_PARITY_FIELD_ALIASES = {
|
| 68 |
+
mes_previsto_lance: "mes_de_contemplacao",
|
| 69 |
+
taxa_administracao_mensal_exibida: "taxa_administracao_exibida",
|
| 70 |
+
diluicao_50_texto: "texto_diluicao_50",
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
function addMonths(isoDate, deltaMonths) {
|
| 74 |
+
const [year, month, day] = isoDate.split("-").map(Number);
|
| 75 |
+
const base = new Date(Date.UTC(year, month - 1, day || 1));
|
| 76 |
+
base.setUTCMonth(base.getUTCMonth() + deltaMonths);
|
| 77 |
+
const y = base.getUTCFullYear();
|
| 78 |
+
const m = String(base.getUTCMonth() + 1).padStart(2, "0");
|
| 79 |
+
const d = String(base.getUTCDate()).padStart(2, "0");
|
| 80 |
+
return `${y}-${m}-${d}`;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
function validateInput(input) {
|
| 84 |
+
if (!input || typeof input !== "object") {
|
| 85 |
+
throw new Error("input invalido");
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
ensurePositiveNumber("credito_desejado", input.credito_desejado);
|
| 89 |
+
|
| 90 |
+
if (!input.parametros || typeof input.parametros !== "object") {
|
| 91 |
+
throw new Error("parametros invalidos");
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
const p = input.parametros;
|
| 95 |
+
|
| 96 |
+
ensurePositiveNumber("prazo_meses", p.prazo_meses);
|
| 97 |
+
ensureNonNegativeNumber("taxa_administracao_total", p.taxa_administracao_total);
|
| 98 |
+
ensureNonNegativeNumber("fundo_reserva_mensal", p.fundo_reserva_mensal);
|
| 99 |
+
ensureNonNegativeNumber("seguro_prestamista", p.seguro_prestamista);
|
| 100 |
+
|
| 101 |
+
if (input.tem_lance && input.lance_percentual !== undefined) {
|
| 102 |
+
ensureNonNegativeNumber("lance_percentual", input.lance_percentual);
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
export function listarPerfisFluxoScpDefault() {
|
| 107 |
+
return {
|
| 108 |
+
clients: SCP_CLIENTS_DEFAULT.map((client) => ({ ...client })),
|
| 109 |
+
patterns: SCP_FLOW_PATTERNS_DEFAULT.map((pattern) => ({
|
| 110 |
+
id: pattern.id,
|
| 111 |
+
nome: pattern.nome,
|
| 112 |
+
descricao: pattern.descricao,
|
| 113 |
+
pontos: pattern.pontos.length,
|
| 114 |
+
inicio: pattern.pontos[0]?.periodo,
|
| 115 |
+
fim: pattern.pontos[pattern.pontos.length - 1]?.periodo,
|
| 116 |
+
})),
|
| 117 |
+
};
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
function findPattern(patternId) {
|
| 121 |
+
return SCP_FLOW_PATTERNS_DEFAULT.find((pattern) => pattern.id === patternId);
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
function findClient(clientId) {
|
| 125 |
+
return SCP_CLIENTS_DEFAULT.find((client) => client.client_id === clientId);
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
function validateScpFlowInput(input) {
|
| 129 |
+
if (!input || typeof input !== "object") {
|
| 130 |
+
throw new Error("input de fluxo scp invalido");
|
| 131 |
+
}
|
| 132 |
+
if (typeof input.client_id !== "string" || !input.client_id.trim()) {
|
| 133 |
+
throw new Error("client_id obrigatorio");
|
| 134 |
+
}
|
| 135 |
+
if (input.pattern_id !== undefined && typeof input.pattern_id !== "string") {
|
| 136 |
+
throw new Error("pattern_id deve ser string");
|
| 137 |
+
}
|
| 138 |
+
if (input.escala_fluxo !== undefined) {
|
| 139 |
+
ensurePositiveNumber("escala_fluxo", input.escala_fluxo);
|
| 140 |
+
}
|
| 141 |
+
if (input.deslocamento_meses !== undefined) {
|
| 142 |
+
ensureInteger("deslocamento_meses", input.deslocamento_meses);
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
export function montarFluxoClienteScp(input) {
|
| 147 |
+
validateScpFlowInput(input);
|
| 148 |
+
|
| 149 |
+
const client = findClient(input.client_id);
|
| 150 |
+
if (!client) {
|
| 151 |
+
throw new Error(`cliente nao encontrado: ${input.client_id}`);
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
const selectedPatternId = input.pattern_id || client.default_pattern_id;
|
| 155 |
+
const pattern = findPattern(selectedPatternId);
|
| 156 |
+
if (!pattern) {
|
| 157 |
+
throw new Error(`padrao de fluxo nao encontrado: ${selectedPatternId}`);
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
const escalaFluxo = input.escala_fluxo !== undefined ? input.escala_fluxo : 1;
|
| 161 |
+
const deslocamentoMeses =
|
| 162 |
+
input.deslocamento_meses !== undefined ? input.deslocamento_meses : 0;
|
| 163 |
+
const novoClientId =
|
| 164 |
+
input.novo_client_id && String(input.novo_client_id).trim()
|
| 165 |
+
? String(input.novo_client_id).trim()
|
| 166 |
+
: client.client_id;
|
| 167 |
+
const novoClientName =
|
| 168 |
+
input.novo_client_name && String(input.novo_client_name).trim()
|
| 169 |
+
? String(input.novo_client_name).trim()
|
| 170 |
+
: client.client_name;
|
| 171 |
+
|
| 172 |
+
const pontos = pattern.pontos.map((point) => ({
|
| 173 |
+
periodo:
|
| 174 |
+
deslocamentoMeses === 0
|
| 175 |
+
? point.periodo
|
| 176 |
+
: addMonths(point.periodo, deslocamentoMeses),
|
| 177 |
+
fluxo: point.fluxo * escalaFluxo,
|
| 178 |
+
}));
|
| 179 |
+
const fluxosMensais = pontos.map((point) => point.fluxo);
|
| 180 |
+
|
| 181 |
+
return {
|
| 182 |
+
client_id: novoClientId,
|
| 183 |
+
client_name: novoClientName,
|
| 184 |
+
source_client_id: client.client_id,
|
| 185 |
+
pattern_id: pattern.id,
|
| 186 |
+
pattern_name: pattern.nome,
|
| 187 |
+
escala_fluxo: escalaFluxo,
|
| 188 |
+
deslocamento_meses: deslocamentoMeses,
|
| 189 |
+
pontos,
|
| 190 |
+
fluxos_mensais: fluxosMensais,
|
| 191 |
+
};
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
export function calcularSimulacao(input) {
|
| 195 |
+
validateInput(input);
|
| 196 |
+
|
| 197 |
+
const { credito_desejado, parametros, tem_lance, lance_percentual } = input;
|
| 198 |
+
const {
|
| 199 |
+
prazo_meses,
|
| 200 |
+
taxa_administracao_total,
|
| 201 |
+
fundo_reserva_mensal,
|
| 202 |
+
seguro_prestamista,
|
| 203 |
+
} = parametros;
|
| 204 |
+
|
| 205 |
+
const fcMensal = 1.0 / prazo_meses;
|
| 206 |
+
const taMensal = taxa_administracao_total / prazo_meses;
|
| 207 |
+
|
| 208 |
+
const valorFC = credito_desejado * fcMensal;
|
| 209 |
+
const valorTA = credito_desejado * taMensal;
|
| 210 |
+
const valorFR = credito_desejado * fundo_reserva_mensal;
|
| 211 |
+
|
| 212 |
+
const valorBaseSeguro =
|
| 213 |
+
credito_desejado + credito_desejado * taxa_administracao_total;
|
| 214 |
+
const taxaSeguroMensal = seguro_prestamista / prazo_meses;
|
| 215 |
+
const valorSeguro = valorBaseSeguro * taxaSeguroMensal;
|
| 216 |
+
|
| 217 |
+
const parcelaIntegral = {
|
| 218 |
+
fundo_comum: valorFC,
|
| 219 |
+
taxa_administracao: valorTA,
|
| 220 |
+
fundo_reserva: valorFR,
|
| 221 |
+
seguro: valorSeguro,
|
| 222 |
+
total: valorFC + valorTA + valorFR + valorSeguro,
|
| 223 |
+
};
|
| 224 |
+
|
| 225 |
+
const fatorReducao = 0.7;
|
| 226 |
+
const valorFCReduzido = valorFC * fatorReducao;
|
| 227 |
+
|
| 228 |
+
const parcelaReduzida = {
|
| 229 |
+
fundo_comum: valorFCReduzido,
|
| 230 |
+
taxa_administracao: valorTA,
|
| 231 |
+
fundo_reserva: valorFR,
|
| 232 |
+
seguro: valorSeguro,
|
| 233 |
+
total: valorFCReduzido + valorTA + valorFR + valorSeguro,
|
| 234 |
+
};
|
| 235 |
+
|
| 236 |
+
let valorLance = 0;
|
| 237 |
+
let prazoAposLance = prazo_meses;
|
| 238 |
+
|
| 239 |
+
if (tem_lance && lance_percentual && lance_percentual > 0) {
|
| 240 |
+
valorLance = credito_desejado * lance_percentual;
|
| 241 |
+
const numeroParcelasAntecipadas = Math.floor(valorLance / parcelaIntegral.total);
|
| 242 |
+
prazoAposLance = Math.max(1, prazo_meses - numeroParcelasAntecipadas);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
return {
|
| 246 |
+
credito: credito_desejado,
|
| 247 |
+
prazo: prazo_meses,
|
| 248 |
+
parcela_integral: parcelaIntegral,
|
| 249 |
+
parcela_reduzida: parcelaReduzida,
|
| 250 |
+
valor_lance: tem_lance ? valorLance : undefined,
|
| 251 |
+
prazo_apos_lance: tem_lance ? prazoAposLance : undefined,
|
| 252 |
+
};
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
function getSeguroRateByProduto(produto) {
|
| 256 |
+
const key = normalizeLookupKey(produto);
|
| 257 |
+
if (key === "imovel") {
|
| 258 |
+
return 0.0003245;
|
| 259 |
+
}
|
| 260 |
+
if (key === "automovel") {
|
| 261 |
+
return 0.0008697;
|
| 262 |
+
}
|
| 263 |
+
return 0;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
function validateRodobensParityInput(input) {
|
| 267 |
+
if (!input || typeof input !== "object") {
|
| 268 |
+
throw new Error("input de paridade rodobens invalido");
|
| 269 |
+
}
|
| 270 |
+
if (!["degrau", "linear", "pontual"].includes(input.plan)) {
|
| 271 |
+
throw new Error("plan deve ser degrau, linear ou pontual");
|
| 272 |
+
}
|
| 273 |
+
ensurePositiveNumber("credito", input.credito);
|
| 274 |
+
ensurePositiveNumber("prazo_cliente", input.prazo_cliente);
|
| 275 |
+
ensureNonNegativeNumber("taxa_administrativa_total", input.taxa_administrativa_total);
|
| 276 |
+
if (input.numero_cotas !== undefined) {
|
| 277 |
+
ensurePositiveNumber("numero_cotas", input.numero_cotas);
|
| 278 |
+
}
|
| 279 |
+
if (input.furo !== undefined) {
|
| 280 |
+
ensureNonNegativeNumber("furo", input.furo);
|
| 281 |
+
}
|
| 282 |
+
if (input.embutido_percentual !== undefined) {
|
| 283 |
+
ensureNonNegativeNumber("embutido_percentual", input.embutido_percentual);
|
| 284 |
+
}
|
| 285 |
+
if (input.parcelas_lance !== undefined) {
|
| 286 |
+
ensureNonNegativeNumber("parcelas_lance", input.parcelas_lance);
|
| 287 |
+
}
|
| 288 |
+
if (input.mes_previsto_lance !== undefined) {
|
| 289 |
+
ensureNonNegativeNumber("mes_previsto_lance", input.mes_previsto_lance);
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
function buildDegrauParityContext(input) {
|
| 294 |
+
const credito = input.credito;
|
| 295 |
+
const prazo = input.prazo_cliente;
|
| 296 |
+
const taxaAdmTotal = input.taxa_administrativa_total;
|
| 297 |
+
const numeroCotas = input.numero_cotas ?? 1;
|
| 298 |
+
const embutidoPercentual = input.embutido_percentual ?? 0;
|
| 299 |
+
const parcelasLance = input.parcelas_lance ?? 0;
|
| 300 |
+
const mesPrevistoLance = input.mes_previsto_lance ?? 0;
|
| 301 |
+
const furo = input.furo ?? 0;
|
| 302 |
+
const plano = input.plano || "Integral";
|
| 303 |
+
const tipoLance = input.tipo_lance || "Lance parcela Int";
|
| 304 |
+
const tipoProduto = input.tipo_produto || "Imóvel";
|
| 305 |
+
const seguroRate =
|
| 306 |
+
normalizeLookupKey(input.seguro_prestamista_habilitado) === "nao" ||
|
| 307 |
+
normalizeLookupKey(input.seguro_prestamista_habilitado) === "não"
|
| 308 |
+
? 0
|
| 309 |
+
: getSeguroRateByProduto(tipoProduto);
|
| 310 |
+
const reductionFactor = plano.includes("50%")
|
| 311 |
+
? 0.5
|
| 312 |
+
: plano.includes("70%")
|
| 313 |
+
? 0.7
|
| 314 |
+
: 1;
|
| 315 |
+
|
| 316 |
+
const parcelaSeguro = ((credito * taxaAdmTotal) + credito) * seguroRate;
|
| 317 |
+
const parcelaIntegralPrimeiraMetade =
|
| 318 |
+
credito / prazo + (credito * taxaAdmTotal) / (prazo / 2) + parcelaSeguro;
|
| 319 |
+
const parcelaIntegralSegundaMetade = credito / prazo + parcelaSeguro;
|
| 320 |
+
const parcelaReduzidaPrimeiraMetade =
|
| 321 |
+
((credito / prazo) + (credito * taxaAdmTotal) / (prazo / 2)) * reductionFactor +
|
| 322 |
+
parcelaSeguro;
|
| 323 |
+
const parcelaReduzidaSegundaMetade =
|
| 324 |
+
(credito / prazo) * reductionFactor + parcelaSeguro;
|
| 325 |
+
const primeiraParcelaPorCota =
|
| 326 |
+
plano === "Integral"
|
| 327 |
+
? parcelaIntegralPrimeiraMetade
|
| 328 |
+
: parcelaReduzidaPrimeiraMetade;
|
| 329 |
+
const lanceEmbutidoValor = credito * embutidoPercentual;
|
| 330 |
+
const usaLanceReduzido = normalizeLookupKey(tipoLance).includes("red");
|
| 331 |
+
const parcelaPrimeiraMetadeParaLance = usaLanceReduzido
|
| 332 |
+
? parcelaReduzidaPrimeiraMetade
|
| 333 |
+
: parcelaIntegralPrimeiraMetade;
|
| 334 |
+
const parcelaSegundaMetadeParaLance = usaLanceReduzido
|
| 335 |
+
? parcelaReduzidaSegundaMetade
|
| 336 |
+
: parcelaIntegralSegundaMetade;
|
| 337 |
+
const metadePrazo = prazo / 2;
|
| 338 |
+
const lanceTotalPorCota =
|
| 339 |
+
parcelasLance > metadePrazo
|
| 340 |
+
? metadePrazo * parcelaSegundaMetadeParaLance +
|
| 341 |
+
(parcelasLance - metadePrazo) * parcelaPrimeiraMetadeParaLance
|
| 342 |
+
: parcelasLance * parcelaSegundaMetadeParaLance;
|
| 343 |
+
const lanceTotal = lanceTotalPorCota * numeroCotas;
|
| 344 |
+
const recursoProprio = lanceTotal - lanceEmbutidoValor * numeroCotas;
|
| 345 |
+
const creditoDisponivel = (credito - lanceEmbutidoValor) * numeroCotas;
|
| 346 |
+
|
| 347 |
+
return {
|
| 348 |
+
partner_name: input.partner_name ?? null,
|
| 349 |
+
plan_label: `Degrau ${plano}`,
|
| 350 |
+
numero_cotas: numeroCotas,
|
| 351 |
+
credito_contratado: credito * numeroCotas,
|
| 352 |
+
lance_total: lanceTotal,
|
| 353 |
+
lance_embutido_valor: lanceEmbutidoValor * numeroCotas,
|
| 354 |
+
recurso_proprio: recursoProprio,
|
| 355 |
+
primeira_parcela: primeiraParcelaPorCota * numeroCotas,
|
| 356 |
+
credito_disponivel: creditoDisponivel,
|
| 357 |
+
credito_liquido: creditoDisponivel,
|
| 358 |
+
furo,
|
| 359 |
+
mes_de_contemplacao: mesPrevistoLance,
|
| 360 |
+
seguro_prestamista: seguroRate,
|
| 361 |
+
fundo_reserva: 0,
|
| 362 |
+
taxa_administracao_exibida: `${formatNumberWithComma(
|
| 363 |
+
roundTo((taxaAdmTotal / prazo) * 100, 6),
|
| 364 |
+
4,
|
| 365 |
+
)} % a.m.`,
|
| 366 |
+
taxa_adesao: 0,
|
| 367 |
+
diluicao_50_texto: null,
|
| 368 |
+
option_title: null,
|
| 369 |
+
saldo_devedor_total: null,
|
| 370 |
+
};
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
function buildLinearParityContext(input) {
|
| 374 |
+
const taxaAdmTotal = input.taxa_administrativa_total;
|
| 375 |
+
const prazo = input.prazo_cliente;
|
| 376 |
+
const plano = input.plano || "Linear Integral";
|
| 377 |
+
const tipoProduto = input.tipo_produto || "Imóvel";
|
| 378 |
+
const seguroRate =
|
| 379 |
+
normalizeLookupKey(input.seguro_prestamista_habilitado) === "sim"
|
| 380 |
+
? getSeguroRateByProduto(tipoProduto)
|
| 381 |
+
: 0;
|
| 382 |
+
|
| 383 |
+
return {
|
| 384 |
+
partner_name: input.partner_name || null,
|
| 385 |
+
furo: input.furo ?? 0,
|
| 386 |
+
seguro_prestamista: seguroRate,
|
| 387 |
+
grupo: input.grupo ?? null,
|
| 388 |
+
plan_label: plano,
|
| 389 |
+
numero_cotas: input.numero_cotas ?? null,
|
| 390 |
+
taxa_administracao_exibida: `${roundTo((taxaAdmTotal / prazo) * 100, 5).toFixed(
|
| 391 |
+
5,
|
| 392 |
+
)} % a.m.`,
|
| 393 |
+
mes_de_contemplacao: input.mes_previsto_lance ?? null,
|
| 394 |
+
texto_diluicao_50:
|
| 395 |
+
plano === "Linear Integral" ? "" : "Plano de Parcela Reduzida até a contemplação",
|
| 396 |
+
credito_contratado: null,
|
| 397 |
+
lance_total: null,
|
| 398 |
+
lance_embutido_valor: null,
|
| 399 |
+
recurso_proprio: null,
|
| 400 |
+
primeira_parcela: null,
|
| 401 |
+
credito_disponivel: null,
|
| 402 |
+
credito_liquido: null,
|
| 403 |
+
taxa_adesao: null,
|
| 404 |
+
option_title: null,
|
| 405 |
+
saldo_devedor_total: null,
|
| 406 |
+
};
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
function buildPontualParityContext(input) {
|
| 410 |
+
const produtoBaseControle = input.produto_base_controle || input.tipo_produto || "Automóvel";
|
| 411 |
+
const saldoBase = input.credito * (1 + input.taxa_administrativa_total);
|
| 412 |
+
const seguroRate = getSeguroRateByProduto(produtoBaseControle);
|
| 413 |
+
const saldoDevedorTotal = saldoBase * (1 + input.prazo_cliente * seguroRate);
|
| 414 |
+
const usaBranchAuto = normalizeLookupKey(produtoBaseControle) === "automovel";
|
| 415 |
+
|
| 416 |
+
return {
|
| 417 |
+
partner_name: input.partner_name || null,
|
| 418 |
+
option_title: usaBranchAuto
|
| 419 |
+
? "Contemplação por Sorteio (Até 12 Meses)"
|
| 420 |
+
: "Contemplação por Sorteio (Até 24 Meses)",
|
| 421 |
+
furo: input.furo ?? 0,
|
| 422 |
+
plan_label: usaBranchAuto ? "Plano Pontual - Auto" : "Plano Pontual - Imóvel",
|
| 423 |
+
numero_cotas: input.numero_cotas ?? null,
|
| 424 |
+
taxa_adesao: 0,
|
| 425 |
+
fundo_reserva: 0,
|
| 426 |
+
saldo_devedor_total: saldoDevedorTotal,
|
| 427 |
+
credito_contratado: null,
|
| 428 |
+
lance_total: null,
|
| 429 |
+
lance_embutido_valor: null,
|
| 430 |
+
recurso_proprio: null,
|
| 431 |
+
primeira_parcela: null,
|
| 432 |
+
credito_disponivel: null,
|
| 433 |
+
credito_liquido: null,
|
| 434 |
+
grupo: input.grupo ?? null,
|
| 435 |
+
mes_de_contemplacao: null,
|
| 436 |
+
seguro_prestamista: null,
|
| 437 |
+
taxa_administracao_exibida: null,
|
| 438 |
+
texto_diluicao_50: null,
|
| 439 |
+
};
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
function getRodobensParityMatrixRows(plan) {
|
| 443 |
+
const rows = RODOBENS_FIELD_MATRIX_BY_PLAN.get(plan) || [];
|
| 444 |
+
return rows.filter((row) => {
|
| 445 |
+
if (row.status === "pending") {
|
| 446 |
+
return false;
|
| 447 |
+
}
|
| 448 |
+
if (row.layout_cell) {
|
| 449 |
+
return true;
|
| 450 |
+
}
|
| 451 |
+
return row.field_id === "saldo_devedor_total";
|
| 452 |
+
});
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
function resolveRodobensParityField(row, input, context) {
|
| 456 |
+
const outputFieldId = RODOBENS_PARITY_FIELD_ALIASES[row.field_id] || row.field_id;
|
| 457 |
+
if (Object.hasOwn(context, outputFieldId)) {
|
| 458 |
+
return context[outputFieldId];
|
| 459 |
+
}
|
| 460 |
+
if (Object.hasOwn(context, row.field_id)) {
|
| 461 |
+
return context[row.field_id];
|
| 462 |
+
}
|
| 463 |
+
if (Object.hasOwn(input, outputFieldId)) {
|
| 464 |
+
return input[outputFieldId];
|
| 465 |
+
}
|
| 466 |
+
if (Object.hasOwn(input, row.field_id)) {
|
| 467 |
+
return input[row.field_id];
|
| 468 |
+
}
|
| 469 |
+
if (row.current_value !== undefined && row.current_value !== null && row.status === "review") {
|
| 470 |
+
return row.current_value;
|
| 471 |
+
}
|
| 472 |
+
return undefined;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
export function calcularParidadeRodobens(input) {
|
| 476 |
+
validateRodobensParityInput(input);
|
| 477 |
+
|
| 478 |
+
let context;
|
| 479 |
+
if (input.plan === "degrau") {
|
| 480 |
+
context = buildDegrauParityContext(input);
|
| 481 |
+
} else if (input.plan === "linear") {
|
| 482 |
+
context = buildLinearParityContext(input);
|
| 483 |
+
} else {
|
| 484 |
+
context = buildPontualParityContext(input);
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
const result = {};
|
| 488 |
+
for (const row of getRodobensParityMatrixRows(input.plan)) {
|
| 489 |
+
const value = resolveRodobensParityField(row, input, context);
|
| 490 |
+
const outputFieldId = RODOBENS_PARITY_FIELD_ALIASES[row.field_id] || row.field_id;
|
| 491 |
+
if (value !== undefined) {
|
| 492 |
+
result[outputFieldId] = value;
|
| 493 |
+
}
|
| 494 |
+
}
|
| 495 |
+
return result;
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
function npv(rate, cashflows) {
|
| 499 |
+
let value = 0;
|
| 500 |
+
for (let i = 0; i < cashflows.length; i += 1) {
|
| 501 |
+
value += cashflows[i] / (1 + rate) ** i;
|
| 502 |
+
}
|
| 503 |
+
return value;
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
function hasSignChange(cashflows) {
|
| 507 |
+
let hasPositive = false;
|
| 508 |
+
let hasNegative = false;
|
| 509 |
+
for (const value of cashflows) {
|
| 510 |
+
if (value > 0) {
|
| 511 |
+
hasPositive = true;
|
| 512 |
+
}
|
| 513 |
+
if (value < 0) {
|
| 514 |
+
hasNegative = true;
|
| 515 |
+
}
|
| 516 |
+
if (hasPositive && hasNegative) {
|
| 517 |
+
return true;
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
return false;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
function calculateIrrMonthly(cashflows) {
|
| 524 |
+
if (!hasSignChange(cashflows)) {
|
| 525 |
+
return null;
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
let low = -0.9999;
|
| 529 |
+
let high = 10;
|
| 530 |
+
let npvLow = npv(low, cashflows);
|
| 531 |
+
let npvHigh = npv(high, cashflows);
|
| 532 |
+
|
| 533 |
+
if (npvLow === 0) {
|
| 534 |
+
return low;
|
| 535 |
+
}
|
| 536 |
+
if (npvHigh === 0) {
|
| 537 |
+
return high;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
let attempts = 0;
|
| 541 |
+
while (npvLow * npvHigh > 0 && attempts < 50) {
|
| 542 |
+
high *= 2;
|
| 543 |
+
npvHigh = npv(high, cashflows);
|
| 544 |
+
attempts += 1;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
if (npvLow * npvHigh > 0) {
|
| 548 |
+
return null;
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
for (let i = 0; i < 200; i += 1) {
|
| 552 |
+
const mid = (low + high) / 2;
|
| 553 |
+
const npvMid = npv(mid, cashflows);
|
| 554 |
+
if (Math.abs(npvMid) < 1e-8) {
|
| 555 |
+
return mid;
|
| 556 |
+
}
|
| 557 |
+
if (npvLow * npvMid < 0) {
|
| 558 |
+
high = mid;
|
| 559 |
+
npvHigh = npvMid;
|
| 560 |
+
} else {
|
| 561 |
+
low = mid;
|
| 562 |
+
npvLow = npvMid;
|
| 563 |
+
}
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
return (low + high) / 2;
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
function validateSecondaryInput(input) {
|
| 570 |
+
if (!input || typeof input !== "object") {
|
| 571 |
+
throw new Error("input secundario invalido");
|
| 572 |
+
}
|
| 573 |
+
ensureNumberArray("fluxos_mensais", input.fluxos_mensais);
|
| 574 |
+
|
| 575 |
+
if (input.portabilidade_percentual_contemplada !== undefined) {
|
| 576 |
+
ensurePercent(
|
| 577 |
+
"portabilidade_percentual_contemplada",
|
| 578 |
+
input.portabilidade_percentual_contemplada
|
| 579 |
+
);
|
| 580 |
+
}
|
| 581 |
+
if (input.portabilidade_percentual_nao_contemplada !== undefined) {
|
| 582 |
+
ensurePercent(
|
| 583 |
+
"portabilidade_percentual_nao_contemplada",
|
| 584 |
+
input.portabilidade_percentual_nao_contemplada
|
| 585 |
+
);
|
| 586 |
+
}
|
| 587 |
+
if (input.fee_percentual !== undefined) {
|
| 588 |
+
ensurePercent("fee_percentual", input.fee_percentual);
|
| 589 |
+
}
|
| 590 |
+
if (input.percentual_cartas_parcela_reduzida !== undefined) {
|
| 591 |
+
ensurePercent(
|
| 592 |
+
"percentual_cartas_parcela_reduzida",
|
| 593 |
+
input.percentual_cartas_parcela_reduzida
|
| 594 |
+
);
|
| 595 |
+
}
|
| 596 |
+
if (input.volume_contratado !== undefined) {
|
| 597 |
+
ensureNonNegativeNumber("volume_contratado", input.volume_contratado);
|
| 598 |
+
}
|
| 599 |
+
if (input.meses_pos_ultima_contemplacao !== undefined) {
|
| 600 |
+
ensureNonNegativeNumber(
|
| 601 |
+
"meses_pos_ultima_contemplacao",
|
| 602 |
+
input.meses_pos_ultima_contemplacao
|
| 603 |
+
);
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
if (input.transacoes_portabilidade !== undefined) {
|
| 607 |
+
if (!Array.isArray(input.transacoes_portabilidade)) {
|
| 608 |
+
throw new Error("transacoes_portabilidade deve ser uma lista");
|
| 609 |
+
}
|
| 610 |
+
for (const transacao of input.transacoes_portabilidade) {
|
| 611 |
+
if (!transacao || typeof transacao !== "object") {
|
| 612 |
+
throw new Error("transacoes_portabilidade contem item invalido");
|
| 613 |
+
}
|
| 614 |
+
ensureNonNegativeNumber("transacao.valor", transacao.valor);
|
| 615 |
+
if (typeof transacao.contemplada !== "boolean") {
|
| 616 |
+
throw new Error("transacao.contemplada deve ser boolean");
|
| 617 |
+
}
|
| 618 |
+
}
|
| 619 |
+
}
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
export function calcularIndicadoresSecundario(input) {
|
| 623 |
+
validateSecondaryInput(input);
|
| 624 |
+
|
| 625 |
+
const portabilidadeContemplada =
|
| 626 |
+
input.portabilidade_percentual_contemplada !== undefined
|
| 627 |
+
? input.portabilidade_percentual_contemplada
|
| 628 |
+
: 0.01;
|
| 629 |
+
const portabilidadeNaoContemplada =
|
| 630 |
+
input.portabilidade_percentual_nao_contemplada !== undefined
|
| 631 |
+
? input.portabilidade_percentual_nao_contemplada
|
| 632 |
+
: 0.005;
|
| 633 |
+
const feePercentual = input.fee_percentual !== undefined ? input.fee_percentual : 0;
|
| 634 |
+
const volumeContratado =
|
| 635 |
+
input.volume_contratado !== undefined ? input.volume_contratado : 0;
|
| 636 |
+
const percentualCartasParcelaReduzida =
|
| 637 |
+
input.percentual_cartas_parcela_reduzida !== undefined
|
| 638 |
+
? input.percentual_cartas_parcela_reduzida
|
| 639 |
+
: 0;
|
| 640 |
+
const mesesPosUltimaContemplacao =
|
| 641 |
+
input.meses_pos_ultima_contemplacao !== undefined
|
| 642 |
+
? input.meses_pos_ultima_contemplacao
|
| 643 |
+
: 0;
|
| 644 |
+
|
| 645 |
+
let custoPortabilidade = 0;
|
| 646 |
+
const transacoes = input.transacoes_portabilidade || [];
|
| 647 |
+
for (const transacao of transacoes) {
|
| 648 |
+
const taxa = transacao.contemplada
|
| 649 |
+
? portabilidadeContemplada
|
| 650 |
+
: portabilidadeNaoContemplada;
|
| 651 |
+
custoPortabilidade += transacao.valor * taxa;
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
const fatorFeeVolume =
|
| 655 |
+
(1 - percentualCartasParcelaReduzida) + percentualCartasParcelaReduzida * 0.7;
|
| 656 |
+
const custoFeeParcelaReduzida = volumeContratado * fatorFeeVolume * feePercentual;
|
| 657 |
+
const custoTotalDeducoes = custoPortabilidade + custoFeeParcelaReduzida;
|
| 658 |
+
|
| 659 |
+
const fluxosAjustados = [...input.fluxos_mensais];
|
| 660 |
+
fluxosAjustados[0] -= custoTotalDeducoes;
|
| 661 |
+
|
| 662 |
+
let acumulado = 0;
|
| 663 |
+
let caixaMaximoNegativo = 0;
|
| 664 |
+
let paybackBase = null;
|
| 665 |
+
|
| 666 |
+
for (let i = 0; i < fluxosAjustados.length; i += 1) {
|
| 667 |
+
acumulado += fluxosAjustados[i];
|
| 668 |
+
if (acumulado < caixaMaximoNegativo) {
|
| 669 |
+
caixaMaximoNegativo = acumulado;
|
| 670 |
+
}
|
| 671 |
+
if (paybackBase === null && acumulado >= 0) {
|
| 672 |
+
paybackBase = i + 1;
|
| 673 |
+
}
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
const tirMensal = calculateIrrMonthly(fluxosAjustados);
|
| 677 |
+
const tirAnual = tirMensal === null ? null : (1 + tirMensal) ** 12 - 1;
|
| 678 |
+
|
| 679 |
+
const paybackMeses =
|
| 680 |
+
paybackBase === null ? null : paybackBase + mesesPosUltimaContemplacao;
|
| 681 |
+
|
| 682 |
+
return {
|
| 683 |
+
fluxo_ajustado: fluxosAjustados,
|
| 684 |
+
custo_portabilidade: custoPortabilidade,
|
| 685 |
+
custo_fee_parcela_reduzida: custoFeeParcelaReduzida,
|
| 686 |
+
custo_total_deducoes: custoTotalDeducoes,
|
| 687 |
+
caixa_maximo_negativo: caixaMaximoNegativo,
|
| 688 |
+
payback_meses: paybackMeses,
|
| 689 |
+
tir_mensal: tirMensal,
|
| 690 |
+
tir_anual: tirAnual,
|
| 691 |
+
regras_aplicadas: {
|
| 692 |
+
portabilidade_contemplada: portabilidadeContemplada,
|
| 693 |
+
portabilidade_nao_contemplada: portabilidadeNaoContemplada,
|
| 694 |
+
sem_correcao_antes_contemplacao:
|
| 695 |
+
input.sem_correcao_antes_contemplacao !== undefined
|
| 696 |
+
? Boolean(input.sem_correcao_antes_contemplacao)
|
| 697 |
+
: true,
|
| 698 |
+
fee_sobre_volume_reduzido_70: true,
|
| 699 |
+
},
|
| 700 |
+
};
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
export function simularSecundarioComPerfilScp(input) {
|
| 704 |
+
if (!input || typeof input !== "object") {
|
| 705 |
+
throw new Error("input de simulacao scp invalido");
|
| 706 |
+
}
|
| 707 |
+
const fluxoCliente = montarFluxoClienteScp({
|
| 708 |
+
client_id: input.client_id,
|
| 709 |
+
pattern_id: input.pattern_id,
|
| 710 |
+
escala_fluxo: input.escala_fluxo,
|
| 711 |
+
deslocamento_meses: input.deslocamento_meses,
|
| 712 |
+
novo_client_id: input.novo_client_id,
|
| 713 |
+
novo_client_name: input.novo_client_name,
|
| 714 |
+
});
|
| 715 |
+
|
| 716 |
+
const simulacao = calcularIndicadoresSecundario({
|
| 717 |
+
fluxos_mensais: fluxoCliente.fluxos_mensais,
|
| 718 |
+
transacoes_portabilidade: input.transacoes_portabilidade,
|
| 719 |
+
portabilidade_percentual_contemplada:
|
| 720 |
+
input.portabilidade_percentual_contemplada,
|
| 721 |
+
portabilidade_percentual_nao_contemplada:
|
| 722 |
+
input.portabilidade_percentual_nao_contemplada,
|
| 723 |
+
volume_contratado: input.volume_contratado,
|
| 724 |
+
fee_percentual: input.fee_percentual,
|
| 725 |
+
percentual_cartas_parcela_reduzida:
|
| 726 |
+
input.percentual_cartas_parcela_reduzida,
|
| 727 |
+
meses_pos_ultima_contemplacao: input.meses_pos_ultima_contemplacao,
|
| 728 |
+
sem_correcao_antes_contemplacao: input.sem_correcao_antes_contemplacao,
|
| 729 |
+
});
|
| 730 |
+
|
| 731 |
+
return {
|
| 732 |
+
...simulacao,
|
| 733 |
+
fluxo_cliente: fluxoCliente,
|
| 734 |
+
};
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
function validateConversaoInput(input) {
|
| 738 |
+
if (!input || typeof input !== "object") {
|
| 739 |
+
throw new Error("input de conversao invalido");
|
| 740 |
+
}
|
| 741 |
+
if (input.valor_imovel_base !== undefined) {
|
| 742 |
+
ensurePositiveNumber("valor_imovel_base", input.valor_imovel_base);
|
| 743 |
+
}
|
| 744 |
+
if (input.desconto_imovel_percentual !== undefined) {
|
| 745 |
+
ensurePercent("desconto_imovel_percentual", input.desconto_imovel_percentual);
|
| 746 |
+
}
|
| 747 |
+
if (input.percentual_entrada_imovel !== undefined) {
|
| 748 |
+
ensurePercent("percentual_entrada_imovel", input.percentual_entrada_imovel);
|
| 749 |
+
}
|
| 750 |
+
if (input.agio_consorcio_percentual !== undefined) {
|
| 751 |
+
ensurePercent("agio_consorcio_percentual", input.agio_consorcio_percentual);
|
| 752 |
+
}
|
| 753 |
+
if (input.taxa_custos_transacao_percentual !== undefined) {
|
| 754 |
+
ensurePercent(
|
| 755 |
+
"taxa_custos_transacao_percentual",
|
| 756 |
+
input.taxa_custos_transacao_percentual
|
| 757 |
+
);
|
| 758 |
+
}
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
export function simularModoConversaoScpConsorcio(input) {
|
| 762 |
+
validateConversaoInput(input);
|
| 763 |
+
|
| 764 |
+
const secundario = simularSecundarioComPerfilScp(input);
|
| 765 |
+
const valorImovelBase =
|
| 766 |
+
input.valor_imovel_base !== undefined ? input.valor_imovel_base : 500000;
|
| 767 |
+
const descontoImovelPercentual =
|
| 768 |
+
input.desconto_imovel_percentual !== undefined
|
| 769 |
+
? input.desconto_imovel_percentual
|
| 770 |
+
: 0.15;
|
| 771 |
+
const percentualEntradaImovel =
|
| 772 |
+
input.percentual_entrada_imovel !== undefined
|
| 773 |
+
? input.percentual_entrada_imovel
|
| 774 |
+
: 0.2;
|
| 775 |
+
const agioConsorcioPercentual =
|
| 776 |
+
input.agio_consorcio_percentual !== undefined
|
| 777 |
+
? input.agio_consorcio_percentual
|
| 778 |
+
: 0.12;
|
| 779 |
+
const taxaCustosTransacaoPercentual =
|
| 780 |
+
input.taxa_custos_transacao_percentual !== undefined
|
| 781 |
+
? input.taxa_custos_transacao_percentual
|
| 782 |
+
: 0;
|
| 783 |
+
|
| 784 |
+
const valorImovelComDesconto =
|
| 785 |
+
valorImovelBase * (1 - descontoImovelPercentual);
|
| 786 |
+
const valorEntrada = valorImovelComDesconto * percentualEntradaImovel;
|
| 787 |
+
const valorCreditoConsorcio =
|
| 788 |
+
valorImovelComDesconto - valorEntrada;
|
| 789 |
+
const valorCustosTransacao =
|
| 790 |
+
valorImovelComDesconto * taxaCustosTransacaoPercentual;
|
| 791 |
+
const investimentoTotalConversao =
|
| 792 |
+
valorEntrada + valorCustosTransacao + secundario.custo_total_deducoes;
|
| 793 |
+
const valorSaidaConsorcioSecundario =
|
| 794 |
+
valorCreditoConsorcio * (1 + agioConsorcioPercentual);
|
| 795 |
+
const lucroProjetadoConversao =
|
| 796 |
+
valorSaidaConsorcioSecundario - investimentoTotalConversao;
|
| 797 |
+
const roiProjetadoConversao =
|
| 798 |
+
investimentoTotalConversao > 0
|
| 799 |
+
? lucroProjetadoConversao / investimentoTotalConversao
|
| 800 |
+
: null;
|
| 801 |
+
|
| 802 |
+
return {
|
| 803 |
+
...secundario,
|
| 804 |
+
modo_conversao: {
|
| 805 |
+
natureza_cenario: "fechado_customizavel",
|
| 806 |
+
observacao:
|
| 807 |
+
"Cenario de conversao SCP para compra de imovel com desconto e operacao no secundario.",
|
| 808 |
+
valor_imovel_base: valorImovelBase,
|
| 809 |
+
desconto_imovel_percentual: descontoImovelPercentual,
|
| 810 |
+
percentual_entrada_imovel: percentualEntradaImovel,
|
| 811 |
+
agio_consorcio_percentual: agioConsorcioPercentual,
|
| 812 |
+
taxa_custos_transacao_percentual: taxaCustosTransacaoPercentual,
|
| 813 |
+
valor_imovel_com_desconto: valorImovelComDesconto,
|
| 814 |
+
valor_entrada_imovel: valorEntrada,
|
| 815 |
+
valor_credito_consorcio: valorCreditoConsorcio,
|
| 816 |
+
valor_custos_transacao: valorCustosTransacao,
|
| 817 |
+
investimento_total_conversao: investimentoTotalConversao,
|
| 818 |
+
valor_saida_consorcio_secundario: valorSaidaConsorcioSecundario,
|
| 819 |
+
lucro_projetado_conversao: lucroProjetadoConversao,
|
| 820 |
+
roi_projetado_conversao: roiProjetadoConversao,
|
| 821 |
+
},
|
| 822 |
+
};
|
| 823 |
+
}
|
packages/simulation-engine/src/scpDefaults.js
ADDED
|
@@ -0,0 +1,658 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const EMPTY_FLOW_PATTERN = {
|
| 2 |
+
id: "scp_sem_fluxo",
|
| 3 |
+
nome: "SCP sem fluxo histórico",
|
| 4 |
+
descricao: "Template vazio para clientes sem histórico carregado.",
|
| 5 |
+
pontos: [{ periodo: "2021-01-01", fluxo: 0 }],
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
const EXTRATO_6_FLOW_PATTERN = {
|
| 9 |
+
id: "scp_extrato_6_consolidado",
|
| 10 |
+
nome: "SCP Extrato Investidor 6 (Consolidado)",
|
| 11 |
+
descricao: "Fluxo consolidado mensal normalizado do investidor 6 (2021-02 a 2034-01).",
|
| 12 |
+
pontos: [
|
| 13 |
+
{
|
| 14 |
+
"periodo": "2021-02-01",
|
| 15 |
+
"fluxo": -737564.52
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"periodo": "2021-03-01",
|
| 19 |
+
"fluxo": -89500.47
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"periodo": "2021-04-01",
|
| 23 |
+
"fluxo": -98532.08
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"periodo": "2021-06-01",
|
| 27 |
+
"fluxo": -93528.63
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"periodo": "2021-07-01",
|
| 31 |
+
"fluxo": -92248.24
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"periodo": "2021-09-01",
|
| 35 |
+
"fluxo": -89535.06
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"periodo": "2021-10-01",
|
| 39 |
+
"fluxo": -88098.84
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"periodo": "2021-12-01",
|
| 43 |
+
"fluxo": -156634.54
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"periodo": "2022-02-01",
|
| 47 |
+
"fluxo": -150588.0
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"periodo": "2022-03-01",
|
| 51 |
+
"fluxo": -50408.16
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"periodo": "2022-04-01",
|
| 55 |
+
"fluxo": -29554.74
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"periodo": "2022-05-01",
|
| 59 |
+
"fluxo": -618506.94
|
| 60 |
+
},
|
| 61 |
+
{
|
| 62 |
+
"periodo": "2022-06-01",
|
| 63 |
+
"fluxo": -736408.34
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"periodo": "2022-07-01",
|
| 67 |
+
"fluxo": -188867.66
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"periodo": "2022-08-01",
|
| 71 |
+
"fluxo": -95358.15
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"periodo": "2022-09-01",
|
| 75 |
+
"fluxo": -224642.19
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"periodo": "2022-10-01",
|
| 79 |
+
"fluxo": -281891.02
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"periodo": "2022-11-01",
|
| 83 |
+
"fluxo": -693594.29
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"periodo": "2022-12-01",
|
| 87 |
+
"fluxo": -499447.91
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"periodo": "2023-01-01",
|
| 91 |
+
"fluxo": -646374.71
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"periodo": "2023-02-01",
|
| 95 |
+
"fluxo": -919693.27
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"periodo": "2023-03-01",
|
| 99 |
+
"fluxo": -1067632.66
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"periodo": "2023-04-01",
|
| 103 |
+
"fluxo": -992735.29
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"periodo": "2023-05-01",
|
| 107 |
+
"fluxo": -445363.53
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"periodo": "2023-06-01",
|
| 111 |
+
"fluxo": -296225.14
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"periodo": "2023-07-01",
|
| 115 |
+
"fluxo": -251036.26
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"periodo": "2023-08-01",
|
| 119 |
+
"fluxo": -187543.36
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"periodo": "2023-09-01",
|
| 123 |
+
"fluxo": -255700.26
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"periodo": "2023-10-01",
|
| 127 |
+
"fluxo": -23630.06
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"periodo": "2023-11-01",
|
| 131 |
+
"fluxo": -186845.68
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"periodo": "2023-12-01",
|
| 135 |
+
"fluxo": -145086.8
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"periodo": "2024-01-01",
|
| 139 |
+
"fluxo": -20697.02
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"periodo": "2024-02-01",
|
| 143 |
+
"fluxo": -122169.49
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"periodo": "2024-03-01",
|
| 147 |
+
"fluxo": -73130.94
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"periodo": "2024-04-01",
|
| 151 |
+
"fluxo": -125938.56
|
| 152 |
+
},
|
| 153 |
+
{
|
| 154 |
+
"periodo": "2024-07-01",
|
| 155 |
+
"fluxo": -39950.54
|
| 156 |
+
},
|
| 157 |
+
{
|
| 158 |
+
"periodo": "2024-08-01",
|
| 159 |
+
"fluxo": -61941.22
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"periodo": "2024-09-01",
|
| 163 |
+
"fluxo": -54658.48
|
| 164 |
+
},
|
| 165 |
+
{
|
| 166 |
+
"periodo": "2024-10-01",
|
| 167 |
+
"fluxo": -225120.87
|
| 168 |
+
},
|
| 169 |
+
{
|
| 170 |
+
"periodo": "2024-11-01",
|
| 171 |
+
"fluxo": -125567.88
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"periodo": "2024-12-01",
|
| 175 |
+
"fluxo": -124604.04
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"periodo": "2025-01-01",
|
| 179 |
+
"fluxo": -162232.609879
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"periodo": "2025-02-01",
|
| 183 |
+
"fluxo": 9840.556197
|
| 184 |
+
},
|
| 185 |
+
{
|
| 186 |
+
"periodo": "2025-03-01",
|
| 187 |
+
"fluxo": -432548.202122
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"periodo": "2025-04-01",
|
| 191 |
+
"fluxo": -458262.952122
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"periodo": "2025-05-01",
|
| 195 |
+
"fluxo": -423097.697657
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"periodo": "2025-06-01",
|
| 199 |
+
"fluxo": -434612.579173
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"periodo": "2025-07-01",
|
| 203 |
+
"fluxo": -121779.498649
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"periodo": "2025-08-01",
|
| 207 |
+
"fluxo": -307297.083824
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
"periodo": "2025-09-01",
|
| 211 |
+
"fluxo": -329799.813629
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"periodo": "2025-10-01",
|
| 215 |
+
"fluxo": -225956.066969
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"periodo": "2025-11-01",
|
| 219 |
+
"fluxo": -288445.512606
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"periodo": "2025-12-01",
|
| 223 |
+
"fluxo": -283414.556707
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"periodo": "2026-01-01",
|
| 227 |
+
"fluxo": 527518.470288
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"periodo": "2026-02-01",
|
| 231 |
+
"fluxo": 269150.845685
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"periodo": "2026-03-01",
|
| 235 |
+
"fluxo": 1305376.352862
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"periodo": "2026-04-01",
|
| 239 |
+
"fluxo": 280794.893435
|
| 240 |
+
},
|
| 241 |
+
{
|
| 242 |
+
"periodo": "2026-05-01",
|
| 243 |
+
"fluxo": 209821.215034
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"periodo": "2026-06-01",
|
| 247 |
+
"fluxo": 60100.726869
|
| 248 |
+
},
|
| 249 |
+
{
|
| 250 |
+
"periodo": "2026-07-01",
|
| 251 |
+
"fluxo": 337614.567424
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"periodo": "2026-08-01",
|
| 255 |
+
"fluxo": 294318.884378
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"periodo": "2026-09-01",
|
| 259 |
+
"fluxo": -46141.362479
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"periodo": "2026-10-01",
|
| 263 |
+
"fluxo": -55076.915784
|
| 264 |
+
},
|
| 265 |
+
{
|
| 266 |
+
"periodo": "2026-11-01",
|
| 267 |
+
"fluxo": 19929.808238
|
| 268 |
+
},
|
| 269 |
+
{
|
| 270 |
+
"periodo": "2026-12-01",
|
| 271 |
+
"fluxo": 500315.052052
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"periodo": "2027-01-01",
|
| 275 |
+
"fluxo": -218977.546392
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"periodo": "2027-02-01",
|
| 279 |
+
"fluxo": 994.423556
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
"periodo": "2027-03-01",
|
| 283 |
+
"fluxo": 404025.712056
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"periodo": "2027-04-01",
|
| 287 |
+
"fluxo": 233575.22893
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"periodo": "2027-05-01",
|
| 291 |
+
"fluxo": 238878.882716
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
"periodo": "2027-06-01",
|
| 295 |
+
"fluxo": 6990240.630296
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"periodo": "2027-07-01",
|
| 299 |
+
"fluxo": 1874488.174107
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"periodo": "2027-08-01",
|
| 303 |
+
"fluxo": 1113986.478002
|
| 304 |
+
},
|
| 305 |
+
{
|
| 306 |
+
"periodo": "2027-09-01",
|
| 307 |
+
"fluxo": 272380.708805
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"periodo": "2027-10-01",
|
| 311 |
+
"fluxo": 1301052.16967
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
"periodo": "2027-11-01",
|
| 315 |
+
"fluxo": 1277465.729149
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"periodo": "2027-12-01",
|
| 319 |
+
"fluxo": 1206630.595673
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"periodo": "2028-01-01",
|
| 323 |
+
"fluxo": 1084333.681223
|
| 324 |
+
},
|
| 325 |
+
{
|
| 326 |
+
"periodo": "2028-02-01",
|
| 327 |
+
"fluxo": 79390.514072
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"periodo": "2028-03-01",
|
| 331 |
+
"fluxo": 104139.639919
|
| 332 |
+
},
|
| 333 |
+
{
|
| 334 |
+
"periodo": "2028-04-01",
|
| 335 |
+
"fluxo": 34705.936188
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"periodo": "2028-05-01",
|
| 339 |
+
"fluxo": 35063.791729
|
| 340 |
+
},
|
| 341 |
+
{
|
| 342 |
+
"periodo": "2028-06-01",
|
| 343 |
+
"fluxo": 35424.772993
|
| 344 |
+
},
|
| 345 |
+
{
|
| 346 |
+
"periodo": "2028-07-01",
|
| 347 |
+
"fluxo": 40009.831092
|
| 348 |
+
},
|
| 349 |
+
{
|
| 350 |
+
"periodo": "2028-08-01",
|
| 351 |
+
"fluxo": 40377.145945
|
| 352 |
+
},
|
| 353 |
+
{
|
| 354 |
+
"periodo": "2028-09-01",
|
| 355 |
+
"fluxo": 40747.669145
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"periodo": "2028-10-01",
|
| 359 |
+
"fluxo": 41121.428714
|
| 360 |
+
},
|
| 361 |
+
{
|
| 362 |
+
"periodo": "2028-11-01",
|
| 363 |
+
"fluxo": 41498.452921
|
| 364 |
+
},
|
| 365 |
+
{
|
| 366 |
+
"periodo": "2028-12-01",
|
| 367 |
+
"fluxo": 40274.370333
|
| 368 |
+
},
|
| 369 |
+
{
|
| 370 |
+
"periodo": "2029-01-01",
|
| 371 |
+
"fluxo": 42262.40956
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"periodo": "2029-02-01",
|
| 375 |
+
"fluxo": 43242.020582
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"periodo": "2029-03-01",
|
| 379 |
+
"fluxo": 43632.390996
|
| 380 |
+
},
|
| 381 |
+
{
|
| 382 |
+
"periodo": "2029-04-01",
|
| 383 |
+
"fluxo": 44026.171137
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"periodo": "2029-05-01",
|
| 387 |
+
"fluxo": 44423.390787
|
| 388 |
+
},
|
| 389 |
+
{
|
| 390 |
+
"periodo": "2029-06-01",
|
| 391 |
+
"fluxo": 44824.07999
|
| 392 |
+
},
|
| 393 |
+
{
|
| 394 |
+
"periodo": "2029-07-01",
|
| 395 |
+
"fluxo": 42992.830516
|
| 396 |
+
},
|
| 397 |
+
{
|
| 398 |
+
"periodo": "2029-08-01",
|
| 399 |
+
"fluxo": 44180.546937
|
| 400 |
+
},
|
| 401 |
+
{
|
| 402 |
+
"periodo": "2029-09-01",
|
| 403 |
+
"fluxo": 46015.260076
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"periodo": "2029-10-01",
|
| 407 |
+
"fluxo": 46348.355526
|
| 408 |
+
},
|
| 409 |
+
{
|
| 410 |
+
"periodo": "2029-11-01",
|
| 411 |
+
"fluxo": 46680.813555
|
| 412 |
+
},
|
| 413 |
+
{
|
| 414 |
+
"periodo": "2029-12-01",
|
| 415 |
+
"fluxo": 40802.289293
|
| 416 |
+
},
|
| 417 |
+
{
|
| 418 |
+
"periodo": "2030-01-01",
|
| 419 |
+
"fluxo": 45820.242381
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"periodo": "2030-02-01",
|
| 423 |
+
"fluxo": 46081.313866
|
| 424 |
+
},
|
| 425 |
+
{
|
| 426 |
+
"periodo": "2030-03-01",
|
| 427 |
+
"fluxo": 44637.236299
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"periodo": "2030-04-01",
|
| 431 |
+
"fluxo": 38100.997039
|
| 432 |
+
},
|
| 433 |
+
{
|
| 434 |
+
"periodo": "2030-05-01",
|
| 435 |
+
"fluxo": 47917.662965
|
| 436 |
+
},
|
| 437 |
+
{
|
| 438 |
+
"periodo": "2030-06-01",
|
| 439 |
+
"fluxo": 48156.62759
|
| 440 |
+
},
|
| 441 |
+
{
|
| 442 |
+
"periodo": "2030-07-01",
|
| 443 |
+
"fluxo": 48396.751508
|
| 444 |
+
},
|
| 445 |
+
{
|
| 446 |
+
"periodo": "2030-08-01",
|
| 447 |
+
"fluxo": 48638.040343
|
| 448 |
+
},
|
| 449 |
+
{
|
| 450 |
+
"periodo": "2030-09-01",
|
| 451 |
+
"fluxo": 48883.754858
|
| 452 |
+
},
|
| 453 |
+
{
|
| 454 |
+
"periodo": "2030-10-01",
|
| 455 |
+
"fluxo": 49130.658876
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"periodo": "2030-11-01",
|
| 459 |
+
"fluxo": 49375.476465
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"periodo": "2030-12-01",
|
| 463 |
+
"fluxo": 49621.481732
|
| 464 |
+
},
|
| 465 |
+
{
|
| 466 |
+
"periodo": "2031-01-01",
|
| 467 |
+
"fluxo": 49276.059627
|
| 468 |
+
},
|
| 469 |
+
{
|
| 470 |
+
"periodo": "2031-02-01",
|
| 471 |
+
"fluxo": 49524.457559
|
| 472 |
+
},
|
| 473 |
+
{
|
| 474 |
+
"periodo": "2031-03-01",
|
| 475 |
+
"fluxo": 49774.060533
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"periodo": "2031-04-01",
|
| 479 |
+
"fluxo": 50024.874392
|
| 480 |
+
},
|
| 481 |
+
{
|
| 482 |
+
"periodo": "2031-05-01",
|
| 483 |
+
"fluxo": 50276.90501
|
| 484 |
+
},
|
| 485 |
+
{
|
| 486 |
+
"periodo": "2031-06-01",
|
| 487 |
+
"fluxo": 50530.158287
|
| 488 |
+
},
|
| 489 |
+
{
|
| 490 |
+
"periodo": "2031-07-01",
|
| 491 |
+
"fluxo": 50784.640153
|
| 492 |
+
},
|
| 493 |
+
{
|
| 494 |
+
"periodo": "2031-08-01",
|
| 495 |
+
"fluxo": 51040.356565
|
| 496 |
+
},
|
| 497 |
+
{
|
| 498 |
+
"periodo": "2031-09-01",
|
| 499 |
+
"fluxo": 51300.731381
|
| 500 |
+
},
|
| 501 |
+
{
|
| 502 |
+
"periodo": "2031-10-01",
|
| 503 |
+
"fluxo": 51562.366671
|
| 504 |
+
},
|
| 505 |
+
{
|
| 506 |
+
"periodo": "2031-11-01",
|
| 507 |
+
"fluxo": 51821.822762
|
| 508 |
+
},
|
| 509 |
+
{
|
| 510 |
+
"periodo": "2031-12-01",
|
| 511 |
+
"fluxo": 52082.537521
|
| 512 |
+
},
|
| 513 |
+
{
|
| 514 |
+
"periodo": "2032-01-01",
|
| 515 |
+
"fluxo": 52344.517054
|
| 516 |
+
},
|
| 517 |
+
{
|
| 518 |
+
"periodo": "2032-02-01",
|
| 519 |
+
"fluxo": 52607.767494
|
| 520 |
+
},
|
| 521 |
+
{
|
| 522 |
+
"periodo": "2032-03-01",
|
| 523 |
+
"fluxo": 52872.295004
|
| 524 |
+
},
|
| 525 |
+
{
|
| 526 |
+
"periodo": "2032-04-01",
|
| 527 |
+
"fluxo": 53138.105778
|
| 528 |
+
},
|
| 529 |
+
{
|
| 530 |
+
"periodo": "2032-05-01",
|
| 531 |
+
"fluxo": 53405.206038
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"periodo": "2032-06-01",
|
| 535 |
+
"fluxo": 53673.602039
|
| 536 |
+
},
|
| 537 |
+
{
|
| 538 |
+
"periodo": "2032-07-01",
|
| 539 |
+
"fluxo": 53943.300063
|
| 540 |
+
},
|
| 541 |
+
{
|
| 542 |
+
"periodo": "2032-08-01",
|
| 543 |
+
"fluxo": 54933.3961
|
| 544 |
+
},
|
| 545 |
+
{
|
| 546 |
+
"periodo": "2032-09-01",
|
| 547 |
+
"fluxo": 55209.305909
|
| 548 |
+
},
|
| 549 |
+
{
|
| 550 |
+
"periodo": "2032-10-01",
|
| 551 |
+
"fluxo": 55486.551398
|
| 552 |
+
},
|
| 553 |
+
{
|
| 554 |
+
"periodo": "2032-11-01",
|
| 555 |
+
"fluxo": 55765.139032
|
| 556 |
+
},
|
| 557 |
+
{
|
| 558 |
+
"periodo": "2032-12-01",
|
| 559 |
+
"fluxo": 56045.075308
|
| 560 |
+
},
|
| 561 |
+
{
|
| 562 |
+
"periodo": "2033-01-01",
|
| 563 |
+
"fluxo": 56918.987568
|
| 564 |
+
},
|
| 565 |
+
{
|
| 566 |
+
"periodo": "2033-02-01",
|
| 567 |
+
"fluxo": 57201.640748
|
| 568 |
+
},
|
| 569 |
+
{
|
| 570 |
+
"periodo": "2033-03-01",
|
| 571 |
+
"fluxo": 57485.662252
|
| 572 |
+
},
|
| 573 |
+
{
|
| 574 |
+
"periodo": "2033-04-01",
|
| 575 |
+
"fluxo": 59082.100043
|
| 576 |
+
},
|
| 577 |
+
{
|
| 578 |
+
"periodo": "2033-05-01",
|
| 579 |
+
"fluxo": 59526.20306
|
| 580 |
+
},
|
| 581 |
+
{
|
| 582 |
+
"periodo": "2033-06-01",
|
| 583 |
+
"fluxo": 59814.369409
|
| 584 |
+
},
|
| 585 |
+
{
|
| 586 |
+
"periodo": "2033-07-01",
|
| 587 |
+
"fluxo": 60103.930771
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"periodo": "2033-08-01",
|
| 591 |
+
"fluxo": 60394.8939
|
| 592 |
+
},
|
| 593 |
+
{
|
| 594 |
+
"periodo": "2033-09-01",
|
| 595 |
+
"fluxo": 60687.265581
|
| 596 |
+
},
|
| 597 |
+
{
|
| 598 |
+
"periodo": "2033-10-01",
|
| 599 |
+
"fluxo": 60981.052634
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"periodo": "2033-11-01",
|
| 603 |
+
"fluxo": 61276.26191
|
| 604 |
+
},
|
| 605 |
+
{
|
| 606 |
+
"periodo": "2033-12-01",
|
| 607 |
+
"fluxo": 61572.900294
|
| 608 |
+
},
|
| 609 |
+
{
|
| 610 |
+
"periodo": "2034-01-01",
|
| 611 |
+
"fluxo": 11858703.094597
|
| 612 |
+
}
|
| 613 |
+
],
|
| 614 |
+
};
|
| 615 |
+
|
| 616 |
+
export const SCP_FLOW_PATTERNS_DEFAULT = [
|
| 617 |
+
EMPTY_FLOW_PATTERN,
|
| 618 |
+
EXTRATO_6_FLOW_PATTERN,
|
| 619 |
+
];
|
| 620 |
+
|
| 621 |
+
export const SCP_CLIENTS_DEFAULT = [
|
| 622 |
+
{
|
| 623 |
+
client_id: "extrato_investidor_1",
|
| 624 |
+
client_name: "Extrato Investidor 1",
|
| 625 |
+
default_pattern_id: "scp_sem_fluxo",
|
| 626 |
+
status: "sem_fluxo_historico",
|
| 627 |
+
},
|
| 628 |
+
{
|
| 629 |
+
client_id: "extrato_investidor_2",
|
| 630 |
+
client_name: "Extrato Investidor 2",
|
| 631 |
+
default_pattern_id: "scp_sem_fluxo",
|
| 632 |
+
status: "sem_fluxo_historico",
|
| 633 |
+
},
|
| 634 |
+
{
|
| 635 |
+
client_id: "extrato_investidor_3",
|
| 636 |
+
client_name: "Extrato Investidor 3",
|
| 637 |
+
default_pattern_id: "scp_sem_fluxo",
|
| 638 |
+
status: "sem_fluxo_historico",
|
| 639 |
+
},
|
| 640 |
+
{
|
| 641 |
+
client_id: "extrato_investidor_4",
|
| 642 |
+
client_name: "Extrato Investidor 4",
|
| 643 |
+
default_pattern_id: "scp_sem_fluxo",
|
| 644 |
+
status: "sem_fluxo_historico",
|
| 645 |
+
},
|
| 646 |
+
{
|
| 647 |
+
client_id: "extrato_investidor_5",
|
| 648 |
+
client_name: "Extrato Investidor 5",
|
| 649 |
+
default_pattern_id: "scp_sem_fluxo",
|
| 650 |
+
status: "sem_fluxo_historico",
|
| 651 |
+
},
|
| 652 |
+
{
|
| 653 |
+
client_id: "extrato_investidor_6",
|
| 654 |
+
client_name: "Extrato Investidor 6",
|
| 655 |
+
default_pattern_id: "scp_extrato_6_consolidado",
|
| 656 |
+
status: "ativo",
|
| 657 |
+
},
|
| 658 |
+
];
|
packages/simulation-engine/tests/simulation-engine.test.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import test from "node:test";
|
| 2 |
+
import assert from "node:assert/strict";
|
| 3 |
+
import { readFileSync } from "node:fs";
|
| 4 |
+
import {
|
| 5 |
+
calcularParidadeRodobens,
|
| 6 |
+
calcularIndicadoresSecundario,
|
| 7 |
+
calcularSimulacao,
|
| 8 |
+
listarPerfisFluxoScpDefault,
|
| 9 |
+
montarFluxoClienteScp,
|
| 10 |
+
simularModoConversaoScpConsorcio,
|
| 11 |
+
simularSecundarioComPerfilScp,
|
| 12 |
+
} from "../src/index.js";
|
| 13 |
+
|
| 14 |
+
const rodobensParityFixtures = JSON.parse(
|
| 15 |
+
readFileSync(
|
| 16 |
+
new URL("../../../tests/fixtures/rodobens_btg5_parity_fixtures_v1.json", import.meta.url),
|
| 17 |
+
"utf8",
|
| 18 |
+
),
|
| 19 |
+
);
|
| 20 |
+
|
| 21 |
+
const rodobensPontualImovelSanitized = JSON.parse(
|
| 22 |
+
readFileSync(
|
| 23 |
+
new URL(
|
| 24 |
+
"../../../tests/fixtures/rodobens_btg5_pontual_imovel_sanitized_v1.json",
|
| 25 |
+
import.meta.url,
|
| 26 |
+
),
|
| 27 |
+
"utf8",
|
| 28 |
+
),
|
| 29 |
+
);
|
| 30 |
+
|
| 31 |
+
const baseInput = {
|
| 32 |
+
credito_desejado: 300000,
|
| 33 |
+
parametros: {
|
| 34 |
+
id_plano: "PADRAO",
|
| 35 |
+
nome_plano: "Plano Padrao Imovel",
|
| 36 |
+
prazo_meses: 180,
|
| 37 |
+
taxa_administracao_total: 0.15,
|
| 38 |
+
fundo_reserva_mensal: 0.0005,
|
| 39 |
+
seguro_prestamista: 0.035,
|
| 40 |
+
},
|
| 41 |
+
tem_lance: false,
|
| 42 |
+
};
|
| 43 |
+
|
| 44 |
+
function mapFields(entries) {
|
| 45 |
+
return Object.fromEntries(entries.map((entry) => [entry.field_id, entry.value]));
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
function buildRodobensParityInputFromScenario(scenario) {
|
| 49 |
+
const inputs = mapFields(scenario.inputs);
|
| 50 |
+
|
| 51 |
+
return {
|
| 52 |
+
plan: scenario.plan,
|
| 53 |
+
partner_name: inputs.partner_name,
|
| 54 |
+
grupo: inputs.grupo,
|
| 55 |
+
numero_cotas: inputs.numero_cotas,
|
| 56 |
+
modalidade_lance: inputs.modalidade_lance,
|
| 57 |
+
tipo_produto: inputs.tipo_produto,
|
| 58 |
+
produto_base_controle: inputs.produto_base_controle,
|
| 59 |
+
furo: inputs.furo,
|
| 60 |
+
credito: inputs.credito,
|
| 61 |
+
prazo_cliente: inputs.prazo_cliente,
|
| 62 |
+
taxa_administrativa_total: inputs.taxa_administrativa_total,
|
| 63 |
+
seguro_prestamista_habilitado: inputs.seguro_prestamista_habilitado,
|
| 64 |
+
plano: inputs.plano,
|
| 65 |
+
tipo_lance: inputs.tipo_lance,
|
| 66 |
+
embutido_percentual: inputs.embutido_percentual,
|
| 67 |
+
parcelas_lance: inputs.parcelas_lance,
|
| 68 |
+
mes_previsto_lance: inputs.mes_previsto_lance,
|
| 69 |
+
};
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
function assertParityOutputs(actual, expectedEntries) {
|
| 73 |
+
for (const expected of expectedEntries) {
|
| 74 |
+
assert.ok(
|
| 75 |
+
Object.hasOwn(actual, expected.field_id),
|
| 76 |
+
`campo ausente no resultado de paridade: ${expected.field_id}`,
|
| 77 |
+
);
|
| 78 |
+
|
| 79 |
+
const actualValue = actual[expected.field_id];
|
| 80 |
+
const expectedValue = expected.value;
|
| 81 |
+
|
| 82 |
+
if (typeof expectedValue === "number") {
|
| 83 |
+
assert.ok(
|
| 84 |
+
Math.abs(actualValue - expectedValue) < 1e-6,
|
| 85 |
+
`campo ${expected.field_id}: esperado ${expectedValue}, recebido ${actualValue}`,
|
| 86 |
+
);
|
| 87 |
+
continue;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
assert.equal(
|
| 91 |
+
actualValue,
|
| 92 |
+
expectedValue,
|
| 93 |
+
`campo ${expected.field_id}: esperado ${expectedValue}, recebido ${actualValue}`,
|
| 94 |
+
);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
test("calcula parcela integral com valores esperados", () => {
|
| 99 |
+
const result = calcularSimulacao(baseInput);
|
| 100 |
+
|
| 101 |
+
assert.equal(result.prazo, 180);
|
| 102 |
+
assert.equal(result.credito, 300000);
|
| 103 |
+
assert.ok(result.parcela_integral.total > 0);
|
| 104 |
+
assert.equal(Number(result.parcela_integral.fundo_comum.toFixed(2)), 1666.67);
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
test("calcula cenario com lance e reduz prazo", () => {
|
| 108 |
+
const result = calcularSimulacao({
|
| 109 |
+
...baseInput,
|
| 110 |
+
tem_lance: true,
|
| 111 |
+
lance_percentual: 0.3,
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
assert.ok(result.valor_lance);
|
| 115 |
+
assert.equal(result.valor_lance, 90000);
|
| 116 |
+
assert.ok(result.prazo_apos_lance);
|
| 117 |
+
assert.ok(result.prazo_apos_lance < result.prazo);
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
test("falha quando prazo_meses for invalido", () => {
|
| 121 |
+
assert.throws(() => {
|
| 122 |
+
calcularSimulacao({
|
| 123 |
+
...baseInput,
|
| 124 |
+
parametros: {
|
| 125 |
+
...baseInput.parametros,
|
| 126 |
+
prazo_meses: 0,
|
| 127 |
+
},
|
| 128 |
+
});
|
| 129 |
+
}, /prazo_meses/);
|
| 130 |
+
});
|
| 131 |
+
|
| 132 |
+
test("calcula payback do secundario com offset apos ultima contemplacao", () => {
|
| 133 |
+
const fluxos = [];
|
| 134 |
+
for (let i = 0; i < 10; i += 1) {
|
| 135 |
+
fluxos.push(-100);
|
| 136 |
+
}
|
| 137 |
+
for (let i = 0; i < 50; i += 1) {
|
| 138 |
+
fluxos.push(20);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
const result = calcularIndicadoresSecundario({
|
| 142 |
+
fluxos_mensais: fluxos,
|
| 143 |
+
meses_pos_ultima_contemplacao: 3,
|
| 144 |
+
});
|
| 145 |
+
|
| 146 |
+
assert.equal(result.caixa_maximo_negativo, -1000);
|
| 147 |
+
assert.equal(result.payback_meses, 63);
|
| 148 |
+
assert.equal(result.regras_aplicadas.fee_sobre_volume_reduzido_70, true);
|
| 149 |
+
});
|
| 150 |
+
|
| 151 |
+
test("deduz portabilidade e fee com parcela reduzida em 70% do volume", () => {
|
| 152 |
+
const result = calcularIndicadoresSecundario({
|
| 153 |
+
fluxos_mensais: [-1000, 500, 700],
|
| 154 |
+
transacoes_portabilidade: [
|
| 155 |
+
{ valor: 100000, contemplada: true },
|
| 156 |
+
{ valor: 100000, contemplada: false },
|
| 157 |
+
],
|
| 158 |
+
volume_contratado: 300000,
|
| 159 |
+
fee_percentual: 0.02,
|
| 160 |
+
percentual_cartas_parcela_reduzida: 1,
|
| 161 |
+
});
|
| 162 |
+
|
| 163 |
+
assert.equal(result.custo_portabilidade, 1500);
|
| 164 |
+
assert.equal(result.custo_fee_parcela_reduzida, 4200);
|
| 165 |
+
assert.equal(result.custo_total_deducoes, 5700);
|
| 166 |
+
assert.equal(result.fluxo_ajustado[0], -6700);
|
| 167 |
+
});
|
| 168 |
+
|
| 169 |
+
test("valida entradas invalidas no secundario", () => {
|
| 170 |
+
assert.throws(() => {
|
| 171 |
+
calcularIndicadoresSecundario({
|
| 172 |
+
fluxos_mensais: [-100, 100],
|
| 173 |
+
percentual_cartas_parcela_reduzida: 1.2,
|
| 174 |
+
});
|
| 175 |
+
}, /percentual_cartas_parcela_reduzida/);
|
| 176 |
+
});
|
| 177 |
+
|
| 178 |
+
test("calcula tir mensal e anual no secundario", () => {
|
| 179 |
+
const result = calcularIndicadoresSecundario({
|
| 180 |
+
fluxos_mensais: [-1000, 1100],
|
| 181 |
+
});
|
| 182 |
+
|
| 183 |
+
assert.ok(result.tir_mensal !== null);
|
| 184 |
+
assert.ok(result.tir_anual !== null);
|
| 185 |
+
assert.ok(Math.abs(result.tir_mensal - 0.1) < 1e-6);
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
test("lista perfis SCP default com clientes entregues", () => {
|
| 189 |
+
const catalogo = listarPerfisFluxoScpDefault();
|
| 190 |
+
assert.ok(catalogo.clients.length >= 6);
|
| 191 |
+
assert.ok(catalogo.patterns.length >= 2);
|
| 192 |
+
assert.ok(catalogo.clients.find((c) => c.client_id === "extrato_investidor_6"));
|
| 193 |
+
});
|
| 194 |
+
|
| 195 |
+
test("monta fluxo SCP parametrizado por escala e deslocamento", () => {
|
| 196 |
+
const fluxo = montarFluxoClienteScp({
|
| 197 |
+
client_id: "extrato_investidor_6",
|
| 198 |
+
escala_fluxo: 0.5,
|
| 199 |
+
deslocamento_meses: 2,
|
| 200 |
+
novo_client_id: "cliente_novo",
|
| 201 |
+
novo_client_name: "Cliente Novo",
|
| 202 |
+
});
|
| 203 |
+
|
| 204 |
+
assert.equal(fluxo.client_id, "cliente_novo");
|
| 205 |
+
assert.equal(fluxo.client_name, "Cliente Novo");
|
| 206 |
+
assert.equal(fluxo.source_client_id, "extrato_investidor_6");
|
| 207 |
+
assert.ok(fluxo.pontos.length > 10);
|
| 208 |
+
assert.equal(fluxo.pontos[0].periodo, "2021-04-01");
|
| 209 |
+
});
|
| 210 |
+
|
| 211 |
+
test("simula secundario com perfil SCP default", () => {
|
| 212 |
+
const result = simularSecundarioComPerfilScp({
|
| 213 |
+
client_id: "extrato_investidor_6",
|
| 214 |
+
fee_percentual: 0.02,
|
| 215 |
+
volume_contratado: 100000,
|
| 216 |
+
percentual_cartas_parcela_reduzida: 0.5,
|
| 217 |
+
});
|
| 218 |
+
|
| 219 |
+
assert.equal(result.fluxo_cliente.client_id, "extrato_investidor_6");
|
| 220 |
+
assert.ok(result.fluxo_ajustado.length > 10);
|
| 221 |
+
assert.ok(typeof result.caixa_maximo_negativo === "number");
|
| 222 |
+
});
|
| 223 |
+
|
| 224 |
+
test("simula modo conversao fechado SCP -> imovel + secundario", () => {
|
| 225 |
+
const result = simularModoConversaoScpConsorcio({
|
| 226 |
+
client_id: "extrato_investidor_6",
|
| 227 |
+
valor_imovel_base: 600000,
|
| 228 |
+
desconto_imovel_percentual: 0.15,
|
| 229 |
+
percentual_entrada_imovel: 0.25,
|
| 230 |
+
agio_consorcio_percentual: 0.1,
|
| 231 |
+
taxa_custos_transacao_percentual: 0.02,
|
| 232 |
+
fee_percentual: 0.02,
|
| 233 |
+
volume_contratado: 120000,
|
| 234 |
+
percentual_cartas_parcela_reduzida: 0.5,
|
| 235 |
+
});
|
| 236 |
+
|
| 237 |
+
assert.ok(result.modo_conversao);
|
| 238 |
+
assert.equal(result.modo_conversao.valor_imovel_com_desconto, 510000);
|
| 239 |
+
assert.equal(result.modo_conversao.valor_entrada_imovel, 127500);
|
| 240 |
+
assert.ok(typeof result.modo_conversao.lucro_projetado_conversao === "number");
|
| 241 |
+
});
|
| 242 |
+
|
| 243 |
+
test("carrega fixtures de paridade Rodobens no pacote do motor", () => {
|
| 244 |
+
assert.equal(rodobensParityFixtures.fixture_version, "1.0");
|
| 245 |
+
assert.equal(rodobensParityFixtures.generated_from, "SIMULADOR RODOBENS - BTG 5.xlsm");
|
| 246 |
+
assert.equal(rodobensParityFixtures.scenarios.length, 3);
|
| 247 |
+
|
| 248 |
+
const plans = rodobensParityFixtures.scenarios.map((scenario) => scenario.plan).sort();
|
| 249 |
+
assert.deepEqual(plans, ["degrau", "linear", "pontual"]);
|
| 250 |
+
});
|
| 251 |
+
|
| 252 |
+
test("compara outputs Rodobens contra o oracle do workbook por campo", () => {
|
| 253 |
+
for (const scenario of rodobensParityFixtures.scenarios) {
|
| 254 |
+
const result = calcularParidadeRodobens(buildRodobensParityInputFromScenario(scenario));
|
| 255 |
+
assertParityOutputs(result, scenario.expected_outputs);
|
| 256 |
+
}
|
| 257 |
+
});
|
| 258 |
+
|
| 259 |
+
test("expõe warnings e saneamento do Pontual para a suite de paridade", () => {
|
| 260 |
+
const pontualScenario = rodobensParityFixtures.scenarios.find(
|
| 261 |
+
(scenario) => scenario.plan === "pontual",
|
| 262 |
+
);
|
| 263 |
+
|
| 264 |
+
assert.ok(pontualScenario);
|
| 265 |
+
assert.ok(
|
| 266 |
+
pontualScenario.warnings.some((warning) =>
|
| 267 |
+
/Automóvel|Automovel|Imóvel|Imovel/.test(warning),
|
| 268 |
+
),
|
| 269 |
+
);
|
| 270 |
+
|
| 271 |
+
assert.equal(rodobensPontualImovelSanitized.plan, "pontual_imovel_sanitized");
|
| 272 |
+
assert.ok(rodobensPontualImovelSanitized.sanitized_fields.length >= 5);
|
| 273 |
+
assert.ok(rodobensPontualImovelSanitized.legacy_excluded.length >= 3);
|
| 274 |
+
});
|