From c6e118d762fb57f23566e5c1816bd87d1a17ec78 Mon Sep 17 00:00:00 2001 From: "aslan.gama" Date: Tue, 11 Aug 2026 12:18:00 -0300 Subject: [PATCH 1/7] =?UTF-8?q?feat(core):=20Adiciona=20funcionalidades=20?= =?UTF-8?q?e=20documenta=C3=A7=C3=A3o=20de=20valuation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adiciona novos arquivos TypeScript para lidar com cálculo de condição, depreciação, quilometragem e tipos de avaliação de veículos. Inclui testes unitários abrangentes para as novas utilidades. Cria extensa documentação sobre especificações do MVP e regras de arquitetura e qualidade. --- .windsurf/rules/architecture.md | 97 ++++++++ .windsurf/rules/project.md | 82 +++++++ .windsurf/rules/quality.md | 73 ++++++ .windsurf/workflows/core/analyze-product.md | 33 +++ .windsurf/workflows/core/create-spec.md | 67 ++++++ .windsurf/workflows/core/create-tasks.md | 59 +++++ .windsurf/workflows/core/execute-task.md | 39 ++++ .windsurf/workflows/core/execute-tasks.md | 30 +++ .windsurf/workflows/core/plan-product.md | 34 +++ .../workflows/core/post-execution-tasks.md | 35 +++ .windsurf/workflows/project/check.md | 32 +++ .windsurf/workflows/project/release.md | 37 ++++ .windsurf/workflows/project/test.md | 33 +++ docs/specs/README.md | 27 +++ docs/specs/ibav-mvp-tasks.md | 202 +++++++++++++++++ docs/specs/ibav-mvp.md | 208 ++++++++++++++++++ shared/types/valuation.ts | 19 ++ shared/utils/condition.ts | 16 ++ shared/utils/depreciation.ts | 4 + shared/utils/mileage.ts | 18 ++ shared/utils/vehicle.ts | 3 + test/utils/condition.test.ts | 11 + test/utils/depreciation.test.ts | 10 + test/utils/mileage.test.ts | 19 ++ test/utils/vehicle.test.ts | 17 ++ 25 files changed, 1205 insertions(+) create mode 100644 .windsurf/rules/architecture.md create mode 100644 .windsurf/rules/project.md create mode 100644 .windsurf/rules/quality.md create mode 100644 .windsurf/workflows/core/analyze-product.md create mode 100644 .windsurf/workflows/core/create-spec.md create mode 100644 .windsurf/workflows/core/create-tasks.md create mode 100644 .windsurf/workflows/core/execute-task.md create mode 100644 .windsurf/workflows/core/execute-tasks.md create mode 100644 .windsurf/workflows/core/plan-product.md create mode 100644 .windsurf/workflows/core/post-execution-tasks.md create mode 100644 .windsurf/workflows/project/check.md create mode 100644 .windsurf/workflows/project/release.md create mode 100644 .windsurf/workflows/project/test.md create mode 100644 docs/specs/README.md create mode 100644 docs/specs/ibav-mvp-tasks.md create mode 100644 docs/specs/ibav-mvp.md create mode 100644 shared/types/valuation.ts create mode 100644 shared/utils/condition.ts create mode 100644 shared/utils/depreciation.ts create mode 100644 shared/utils/mileage.ts create mode 100644 shared/utils/vehicle.ts create mode 100644 test/utils/condition.test.ts create mode 100644 test/utils/depreciation.test.ts create mode 100644 test/utils/mileage.test.ts create mode 100644 test/utils/vehicle.test.ts diff --git a/.windsurf/rules/architecture.md b/.windsurf/rules/architecture.md new file mode 100644 index 0000000..a82af48 --- /dev/null +++ b/.windsurf/rules/architecture.md @@ -0,0 +1,97 @@ +--- +description: Regras de arquitetura do projeto IBAV +--- + +# IBAV - Architecture Rules + +## Nuxt + +- O projeto utiliza Nuxt 4. Sempre verifique `nuxt.config.ts` antes de modificar configurações. +- A data de compatibilidade está em `nuxt.config.ts`. Não altere sem necessidade. +- Não adicione módulos automaticamente. Verifique `package.json` e `nuxt.config.ts` existentes. + +## Vue + +- Vue 3 Composition API é o padrão do projeto. +- ` diff --git a/docs/docs.md b/docs/docs.md index 63a0103..1e40c8d 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -75,12 +75,12 @@ Sistema calcula: # Campos de Entrada -| Campo | Tipo | -|--------|------| -| Valor FIPE | Número | -| Ano | Número | +| Campo | Tipo | +| ------------- | ------ | +| Valor FIPE | Número | +| Ano | Número | | Quilometragem | Número | -| Conservação | Select | +| Conservação | Select | --- @@ -169,12 +169,12 @@ Acréscimo de: ## 3. Conservação -| Estado | Ajuste | -|---------|--------:| -| Excelente | +3% | -| Bom | 0% | -| Regular | -3% | -| Ruim | -8% | +| Estado | Ajuste | +| --------- | -----: | +| Excelente | +3% | +| Bom | 0% | +| Regular | -3% | +| Ruim | -8% | --- @@ -312,24 +312,24 @@ a cada 10.000 km acima da média ## Conservação -| Estado | Pontos | -|---------|--------:| -| Excelente | +30 | -| Bom | 0 | -| Regular | -30 | -| Ruim | -80 | +| Estado | Pontos | +| --------- | -----: | +| Excelente | +30 | +| Bom | 0 | +| Regular | -30 | +| Ruim | -80 | --- # Classificação -| Pontos | Classificação | -|---------|---------------| -| 900–1000 | Excelente | -| 800–899 | Muito Bom | -| 700–799 | Bom | -| 600–699 | Regular | -| abaixo de 600 | Atenção | +| Pontos | Classificação | +| ------------- | ------------- | +| 900–1000 | Excelente | +| 800–899 | Muito Bom | +| 700–799 | Bom | +| 600–699 | Regular | +| abaixo de 600 | Atenção | --- @@ -420,4 +420,4 @@ O objetivo do IBAV é oferecer uma estimativa transparente do Valor Justo do Ve # Licença -A metodologia do IBAV deverá ser pública, documentada e versionada para garantir transparência, auditabilidade e evolução contínua. \ No newline at end of file +A metodologia do IBAV deverá ser pública, documentada e versionada para garantir transparência, auditabilidade e evolução contínua. diff --git a/docs/specs/ibav-mvp-tasks.md b/docs/specs/ibav-mvp-tasks.md index 2f2e218..1a631c4 100644 --- a/docs/specs/ibav-mvp-tasks.md +++ b/docs/specs/ibav-mvp-tasks.md @@ -69,7 +69,7 @@ Criar as funções puras de cálculo de idade, desvalorização, ajuste por km e --- -## Task 3 — Implementar VJV e IVB +## Task 3 — ✅ Implementar VJV e IVB ### Goal @@ -101,7 +101,7 @@ Compor os utilitários base para calcular o Valor Justo do Veículo e o Índice --- -## Task 4 — Criar página com formulário e exibição de resultados +## Task 4 — ✅ Criar página com formulário e exibição de resultados ### Goal @@ -136,7 +136,7 @@ Construir a interface do usuário com Nuxt UI, formulário e painel de resultado --- -## Task 5 — Sincronizar formulário com query params para compartilhamento +## Task 5 — ✅ Sincronizar formulário com query params para compartilhamento ### Goal @@ -168,7 +168,7 @@ Permitir que o usuário copie e compartilhe uma URL que carregue o mesmo cálcul --- -## Task 6 — Integração final e verificação +## Task 6 — ✅ Integração final e verificação ### Goal diff --git a/nuxt.config.ts b/nuxt.config.ts index b6baa24..15685a2 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,5 +1,6 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ compatibilityDate: '2025-07-15', - devtools: { enabled: true } -}) + devtools: { enabled: true }, + modules: ['@nuxt/ui'], +}); diff --git a/package.json b/package.json index 60b293b..6c927e7 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@nuxt/eslint-config": "^1.16.0", "@nuxt/test-utils": "^4.1.0", "@vitejs/plugin-vue": "^6.0.8", + "@vitest/coverage-v8": "^4.1.10", "@vue/test-utils": "^2.4.11", "changelogen": "^0.6.2", "eslint": "^10.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7742b3e..da0660a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,10 +44,13 @@ importers: version: 1.16.0(@typescript-eslint/utils@8.65.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.40)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) '@nuxt/test-utils': specifier: ^4.1.0 - version: 4.1.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))) + version: 4.1.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10) '@vitejs/plugin-vue': specifier: ^6.0.8 version: 6.0.8(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vue@3.5.40(typescript@6.0.3)) + '@vitest/coverage-v8': + specifier: ^4.1.10 + version: 4.1.10(vitest@4.1.10) '@vue/test-utils': specifier: ^2.4.11 version: 2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)) @@ -68,7 +71,7 @@ importers: version: 6.0.3 vitest: specifier: ^4.1.10 - version: 4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) + version: 4.1.10(@vitest/coverage-v8@4.1.10)(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) packages: @@ -226,6 +229,10 @@ packages: resolution: {integrity: sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==} engines: {node: ^22.18.0 || >=24.11.0} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + '@bomb.sh/tab@0.0.19': resolution: {integrity: sha512-dTRfo9Q9B+lbLG3JCu8a/AGQSfD2XXcFcnakQzVjSOX+VvR/s9zpsH8TlqV3iHqazniRn1Ypwd1hcRlXcu/4BA==} hasBin: true @@ -2664,6 +2671,15 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 + '@vitest/coverage-v8@4.1.10': + resolution: {integrity: sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==} + peerDependencies: + '@vitest/browser': 4.1.10 + vitest: 4.1.10 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@4.1.10': resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} @@ -2936,6 +2952,9 @@ packages: resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} engines: {node: '>=20.19.0'} + ast-v8-to-istanbul@1.0.5: + resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==} + ast-walker-scope@0.9.0: resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} engines: {node: '>=20.19.0'} @@ -4010,6 +4029,10 @@ packages: crossws: optional: true + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -4095,6 +4118,9 @@ packages: html-entities@2.6.0: resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -4307,6 +4333,18 @@ packages: isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -4615,6 +4653,10 @@ packages: magicast@0.5.3: resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -5877,6 +5919,10 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -6922,6 +6968,8 @@ snapshots: '@babel/helper-string-parser': 8.0.0 '@babel/helper-validator-identifier': 8.0.4 + '@bcoe/v8-coverage@1.0.2': {} + '@bomb.sh/tab@0.0.19(cac@7.0.0)(citty@0.2.2)': optionalDependencies: cac: 7.0.0 @@ -8164,7 +8212,7 @@ snapshots: rc9: 3.0.1 std-env: 4.2.0 - '@nuxt/test-utils@4.1.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)))': + '@nuxt/test-utils@4.1.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10)': dependencies: '@clack/prompts': 1.7.0 '@nuxt/devtools-kit': 2.7.0(magicast@0.5.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) @@ -8192,12 +8240,12 @@ snapshots: tinyexec: 1.2.4 ufo: 1.6.4 unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.1)(rollup@4.62.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) - vitest-environment-nuxt: 2.0.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))) + vitest-environment-nuxt: 2.0.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10) vue: 3.5.40(typescript@6.0.3) optionalDependencies: '@vue/test-utils': 2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)) jsdom: 26.1.0(supports-color@10.2.2) - vitest: 4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) + vitest: 4.1.10(@vitest/coverage-v8@4.1.10)(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -9580,6 +9628,20 @@ snapshots: vite: 8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0) vue: 3.5.40(typescript@6.0.3) + '@vitest/coverage-v8@4.1.10(vitest@4.1.10)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.10 + ast-v8-to-istanbul: 1.0.5 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.4 + std-env: 4.2.0 + tinyrainbow: 3.1.1 + vitest: 4.1.10(@vitest/coverage-v8@4.1.10)(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) + '@vitest/expect@4.1.10': dependencies: '@standard-schema/spec': 1.1.0 @@ -9892,6 +9954,12 @@ snapshots: '@babel/parser': 7.29.7 pathe: 2.0.3 + ast-v8-to-istanbul@1.0.5: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + ast-walker-scope@0.9.0: dependencies: '@babel/parser': 7.29.7 @@ -11028,6 +11096,8 @@ snapshots: optionalDependencies: crossws: 0.4.10(srvx@0.11.22) + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -11196,6 +11266,8 @@ snapshots: html-entities@2.6.0: {} + html-escaper@2.0.2: {} + html-void-elements@3.0.0: {} html-whitespace-sensitive-tag-names@3.0.1: {} @@ -11394,6 +11466,19 @@ snapshots: isomorphic.js@0.2.5: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -11688,6 +11773,10 @@ snapshots: '@babel/types': 7.29.7 source-map-js: 1.2.1 + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + markdown-table@3.0.4: {} marked@17.0.6: {} @@ -13651,6 +13740,10 @@ snapshots: supports-color@10.2.2: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} svgo@4.0.2: @@ -14214,9 +14307,9 @@ snapshots: terser: 5.49.0 yaml: 2.9.0 - vitest-environment-nuxt@2.0.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))): + vitest-environment-nuxt@2.0.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10): dependencies: - '@nuxt/test-utils': 4.1.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))) + '@nuxt/test-utils': 4.1.0(@vue/test-utils@2.4.11(@vue/compiler-dom@3.5.40)(@vue/server-renderer@3.5.40)(vue@3.5.40(typescript@6.0.3)))(esbuild@0.28.1)(jsdom@26.1.0(supports-color@10.2.2))(magicast@0.5.3)(rolldown@1.2.1)(rollup@4.62.3)(typescript@6.0.3)(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0))(vitest@4.1.10) transitivePeerDependencies: - '@cucumber/cucumber' - '@farmfe/core' @@ -14241,7 +14334,7 @@ snapshots: - vitest - webpack - vitest@4.1.10(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)): + vitest@4.1.10(@vitest/coverage-v8@4.1.10)(jsdom@26.1.0(supports-color@10.2.2))(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.10 '@vitest/mocker': 4.1.10(vite@8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0)) @@ -14264,6 +14357,7 @@ snapshots: vite: 8.1.5(esbuild@0.28.1)(jiti@2.7.0)(terser@5.49.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: + '@vitest/coverage-v8': 4.1.10(vitest@4.1.10) jsdom: 26.1.0(supports-color@10.2.2) transitivePeerDependencies: - msw diff --git a/renovate.json b/renovate.json index 5da0405..af5563e 100644 --- a/renovate.json +++ b/renovate.json @@ -5,4 +5,4 @@ "enabled": true }, "postUpdateOptions": ["pnpmDedupe"] -} \ No newline at end of file +} diff --git a/server/api/hello.ts b/server/api/hello.ts index 843e358..f51cedc 100644 --- a/server/api/hello.ts +++ b/server/api/hello.ts @@ -1,5 +1,5 @@ export default defineEventHandler((event) => { - return { - hello: 'world', - } -}) \ No newline at end of file + return { + hello: 'world', + }; +}); diff --git a/shared/utils/capitalize.ts b/shared/utils/capitalize.ts index 11f613d..9d3145d 100644 --- a/shared/utils/capitalize.ts +++ b/shared/utils/capitalize.ts @@ -1,3 +1,3 @@ export const capitalize = (input: string) => { - return input[0] ? input[0].toUpperCase() + input.slice(1) : '' -} \ No newline at end of file + return input[0] ? input[0].toUpperCase() + input.slice(1) : ''; +}; diff --git a/shared/utils/depreciation.ts b/shared/utils/depreciation.ts index 70f5a49..10dcf7f 100644 --- a/shared/utils/depreciation.ts +++ b/shared/utils/depreciation.ts @@ -1,4 +1,6 @@ export function calculateAgeDiscount(age: number): number { - if (age === 0) return 0; + if (age === 0) { + return 0; + } return -(age * 0.02); } diff --git a/shared/utils/ivb.ts b/shared/utils/ivb.ts new file mode 100644 index 0000000..b35dc35 --- /dev/null +++ b/shared/utils/ivb.ts @@ -0,0 +1,59 @@ +import { calculateAge } from './vehicle'; +import type { VehicleCondition, VehicleInputs } from '../types/valuation'; + +export function calculateIvb( + inputs: VehicleInputs, + currentYear = new Date().getFullYear() +): { points: number; label: string } { + const age = calculateAge(inputs.year, currentYear); + const agePoints = age * -15; + const mileagePoints = calculateMileagePoints(inputs.mileage, age); + const conditionPoints = getConditionPoints(inputs.condition); + + const points = 1000 + agePoints + mileagePoints + conditionPoints; + const label = getIvbLabel(points); + + return { points, label }; +} + +function calculateMileagePoints(mileage: number, age: number): number { + const expected = age * 15000; + const diff = mileage - expected; + + if (diff > 0) { + return -(Math.floor(diff / 10000) * 10); + } + + return 0; +} + +function getConditionPoints(condition: VehicleCondition): number { + switch (condition) { + case 'excelente': + return 30; + case 'bom': + return 0; + case 'regular': + return -30; + case 'ruim': + return -80; + default: + return 0; + } +} + +function getIvbLabel(points: number): string { + if (points >= 900) { + return 'Excelente'; + } + if (points >= 800) { + return 'Muito Bom'; + } + if (points >= 700) { + return 'Bom'; + } + if (points >= 600) { + return 'Regular'; + } + return 'Atenção'; +} diff --git a/shared/utils/vehicle-query.ts b/shared/utils/vehicle-query.ts new file mode 100644 index 0000000..028a946 --- /dev/null +++ b/shared/utils/vehicle-query.ts @@ -0,0 +1,48 @@ +import type { VehicleCondition, VehicleInputs } from '#shared/types/valuation'; + +const VALID_CONDITIONS: VehicleCondition[] = ['excelente', 'bom', 'regular', 'ruim']; + +export function parseVehicleQuery( + query: Record +): Partial { + const parsed: Partial = {}; + + if (typeof query.fipe === 'string') { + const value = Number.parseFloat(query.fipe); + if (!Number.isNaN(value)) { + parsed.fipeValue = value; + } + } + + if (typeof query.year === 'string') { + const value = Number.parseInt(query.year, 10); + if (!Number.isNaN(value)) { + parsed.year = value; + } + } + + if (typeof query.km === 'string') { + const value = Number.parseInt(query.km, 10); + if (!Number.isNaN(value)) { + parsed.mileage = value; + } + } + + if ( + typeof query.condition === 'string' && + VALID_CONDITIONS.includes(query.condition as VehicleCondition) + ) { + parsed.condition = query.condition as VehicleCondition; + } + + return parsed; +} + +export function buildVehicleQuery(inputs: VehicleInputs): Record { + return { + fipe: String(inputs.fipeValue), + year: String(inputs.year), + km: String(inputs.mileage), + condition: inputs.condition, + }; +} diff --git a/shared/utils/vjv.ts b/shared/utils/vjv.ts new file mode 100644 index 0000000..8414d60 --- /dev/null +++ b/shared/utils/vjv.ts @@ -0,0 +1,19 @@ +import { calculateAge } from './vehicle'; +import { calculateAgeDiscount } from './depreciation'; +import { calculateMileageAdjustment } from './mileage'; +import { calculateConditionAdjustment } from './condition'; +import type { VehicleInputs } from '../types/valuation'; + +export function calculateVjv( + inputs: VehicleInputs, + currentYear = new Date().getFullYear() +): number { + const age = calculateAge(inputs.year, currentYear); + const ageAdjustment = calculateAgeDiscount(age); + const mileageAdjustment = calculateMileageAdjustment(age, inputs.mileage); + const conditionAdjustment = calculateConditionAdjustment(inputs.condition); + + const vjv = inputs.fipeValue * (1 + ageAdjustment + mileageAdjustment + conditionAdjustment); + + return Math.round(vjv * 100) / 100; +} diff --git a/test/app.test.ts b/test/app.test.ts index 560b644..8defee7 100644 --- a/test/app.test.ts +++ b/test/app.test.ts @@ -1,31 +1,85 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi } from 'vitest'; import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; import App from '../app/app.vue'; +vi.mock('vue-router'); + +const useRouteMock = vi.mocked(useRoute); +const useRouterMock = vi.mocked(useRouter); + describe('App.vue', () => { it('renders the component', () => { + useRouteMock.mockReturnValue({ query: {} } as any); + useRouterMock.mockReturnValue({ replace: vi.fn() } as any); + const wrapper = mount(App, { global: { stubs: { - NuxtRouteAnnouncer: true, - NuxtWelcome: true, + UInput: true, + USelect: true, }, }, }); - + expect(wrapper.exists()).toBe(true); }); - it('contains a div element', () => { + it('shows the result after valid input', async () => { + useRouteMock.mockReturnValue({ query: {} } as any); + useRouterMock.mockReturnValue({ replace: vi.fn() } as any); + + const wrapper = mount(App, { + global: { + stubs: { + UInput: true, + USelect: true, + }, + }, + }); + + wrapper.vm.form = { + fipeValue: 80000, + year: 2022, + mileage: 90000, + condition: 'excelente', + }; + + await nextTick(); + + const text = wrapper.text().replace(/\s+/g, ' '); + + expect(text).toContain('R$ 74.800,00'); + expect(text).toContain('940 pontos'); + expect(text).toContain('Excelente'); + }); + + it('loads state from query params', async () => { + useRouteMock.mockReturnValue({ + query: { + fipe: '80000', + year: '2022', + km: '90000', + condition: 'excelente', + }, + } as any); + useRouterMock.mockReturnValue({ replace: vi.fn() } as any); + const wrapper = mount(App, { global: { stubs: { - NuxtRouteAnnouncer: true, - NuxtWelcome: true, + UInput: true, + USelect: true, }, }, }); - - expect(wrapper.find('div').exists()).toBe(true); + + await nextTick(); + + const text = wrapper.text().replace(/\s+/g, ' '); + + expect(text).toContain('R$ 74.800,00'); + expect(text).toContain('940 pontos'); }); }); diff --git a/test/utils/ivb.test.ts b/test/utils/ivb.test.ts new file mode 100644 index 0000000..0b402ff --- /dev/null +++ b/test/utils/ivb.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest'; +import { calculateIvb } from '../../shared/utils/ivb'; + +describe('ivb', () => { + it('returns the correct IVB for the docs example', () => { + const inputs = { + fipeValue: 80000, + year: 2022, + mileage: 90000, + condition: 'excelente' as const, + }; + + const result = calculateIvb(inputs, 2026); + + expect(result.points).toBe(940); + expect(result.label).toBe('Excelente'); + }); + + it('classifies each condition correctly', () => { + const base = { fipeValue: 80000, year: 2022, mileage: 90000 }; + + expect(calculateIvb({ ...base, condition: 'bom' }, 2026).points).toBe(910); + expect(calculateIvb({ ...base, condition: 'regular' }, 2026).points).toBe(880); + expect(calculateIvb({ ...base, condition: 'ruim' }, 2026).points).toBe(830); + expect(calculateIvb({ ...base, condition: 'ruim' }, 2026).label).toBe('Muito Bom'); + }); +}); diff --git a/test/utils/vehicle-query.test.ts b/test/utils/vehicle-query.test.ts new file mode 100644 index 0000000..4378e1c --- /dev/null +++ b/test/utils/vehicle-query.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from 'vitest'; +import { parseVehicleQuery, buildVehicleQuery } from '../../shared/utils/vehicle-query'; + +describe('vehicle-query', () => { + it('parses query strings into VehicleInputs', () => { + expect( + parseVehicleQuery({ + fipe: '80000', + year: '2022', + km: '90000', + condition: 'excelente', + }) + ).toEqual({ + fipeValue: 80000, + year: 2022, + mileage: 90000, + condition: 'excelente', + }); + }); + + it('ignores unknown condition values', () => { + expect(parseVehicleQuery({ condition: 'invalid' }).condition).toBeUndefined(); + }); + + it('ignores invalid numbers', () => { + expect(parseVehicleQuery({ fipe: 'abc' }).fipeValue).toBeUndefined(); + }); + + it('builds query from inputs', () => { + expect( + buildVehicleQuery({ + fipeValue: 80000, + year: 2022, + mileage: 90000, + condition: 'excelente', + }) + ).toEqual({ + fipe: '80000', + year: '2022', + km: '90000', + condition: 'excelente', + }); + }); +}); diff --git a/test/utils/vjv.test.ts b/test/utils/vjv.test.ts new file mode 100644 index 0000000..f53c884 --- /dev/null +++ b/test/utils/vjv.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { calculateVjv } from '../../shared/utils/vjv'; + +describe('vjv', () => { + it('returns the correct VJV for the docs example', () => { + const inputs = { + fipeValue: 80000, + year: 2022, + mileage: 90000, + condition: 'excelente' as const, + }; + + expect(calculateVjv(inputs, 2026)).toBe(74800); + }); + + it('returns the FIPE value when there are no adjustments', () => { + const inputs = { + fipeValue: 50000, + year: 2026, + mileage: 0, + condition: 'bom' as const, + }; + + expect(calculateVjv(inputs, 2026)).toBe(50000); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 575f762..414e7a2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,14 @@ +import { fileURLToPath } from 'node:url'; import { defineConfig } from 'vitest/config'; import vue from '@vitejs/plugin-vue'; export default defineConfig({ plugins: [vue()], + resolve: { + alias: { + '#shared': fileURLToPath(new URL('./shared', import.meta.url)), + }, + }, test: { environment: 'jsdom', include: ['test/**/*.{test,spec}.{js,ts}'], From bbe4f6df1894c8788bb41ecfb609d25fc3e300b9 Mon Sep 17 00:00:00 2001 From: "aslan.gama" Date: Tue, 11 Aug 2026 12:56:57 -0300 Subject: [PATCH 4/7] feat(core): Adiciona .github ao .prettierignore --- .prettierignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index f4dcda7..262c724 100644 --- a/.prettierignore +++ b/.prettierignore @@ -66,4 +66,7 @@ tmp .playground # Changelog -CHANGELOG.md \ No newline at end of file +CHANGELOG.md + +# GitHub +.github From 11d1b6e4ca9e8e8030a119c63c52688ba275c513 Mon Sep 17 00:00:00 2001 From: "aslan.gama" Date: Tue, 11 Aug 2026 12:57:32 -0300 Subject: [PATCH 5/7] =?UTF-8?q?docs(core):=20Formata=20documentos=20de=20g?= =?UTF-8?q?overnan=C3=A7a=20e=20contribui=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 17 ++++++++++------- GOVERNANCE.md | 9 ++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66fc1ee..a3fa2bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,11 +68,13 @@ Clone o repositório: git clone https://github.com/4sllan/ibav.git cd ibav ``` + Instale as dependências: ```bash pnpm install ``` + Execute o ambiente de desenvolvimento: ```bash @@ -93,16 +95,17 @@ Exemplo: git checkout -b feat/minha-funcionalidade ``` -| Tipo | Exemplo | -| -------- | -------------------------------- | -| Feature | `feat/add-ivb-calculation` | -| Fix | `fix/mileage-adjustment` | -| Docs | `docs/update-methodology` | -| Refactor | `refactor/vjh-formula` | +| Tipo | Exemplo | +| -------- | -------------------------- | +| Feature | `feat/add-ivb-calculation` | +| Fix | `fix/mileage-adjustment` | +| Docs | `docs/update-methodology` | +| Refactor | `refactor/vjh-formula` | ## ✍️ Diretrizes de Commit Seguimos Conventional Commits: + ```text tipo(escopo): descrição ``` @@ -163,4 +166,4 @@ Obrigado por ajudar a melhorar o IBAV! - [Documentação do Projeto](docs/docs.md) - Metodologia completa e especificações - [Roadmap](docs/docs.md#roadmap) - Funcionalidades planejadas e versões futuras -- [Metodologia](docs/docs.md#metodologia) - Fórmulas de cálculo VJV e IVB \ No newline at end of file +- [Metodologia](docs/docs.md#metodologia) - Fórmulas de cálculo VJV e IVB diff --git a/GOVERNANCE.md b/GOVERNANCE.md index f400816..5e58fb2 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -25,6 +25,7 @@ O IBAV é um projeto open source que visa calcular o **Valor Justo do Veículo ( ### Transparência A metodologia do IBAV é pública, documentada e versionada para garantir: + - Auditabilidade dos cálculos - Evolução contínua e colaborativa - Confiança dos usuários @@ -56,6 +57,7 @@ O sistema considera quatro fatores principais: ### Índice de Valor Brasileiro (IVB) Pontuação de 0 a 1000 pontos que classifica o veículo: + - 900–1000: Excelente - 800–899: Muito Bom - 700–799: Bom @@ -69,6 +71,7 @@ Pontuação de 0 a 1000 pontos que classifica o veículo: ### Versionamento A metodologia do IBAV é versionada para garantir: + - Rastreabilidade de mudanças - Estabilidade para usuários - Possibilidade de rollback se necessário @@ -76,6 +79,7 @@ A metodologia do IBAV é versionada para garantir: ### Atualizações Mudanças na metodologia devem: + - Ser documentadas no CHANGELOG.md - Manter compatibilidade quando possível - Ser comunicadas claramente aos usuários @@ -115,6 +119,7 @@ Mudanças na metodologia devem: ## Licenciamento O projeto IBAV é open source para garantir: + - Acesso livre à metodologia - Contribuição da comunidade - Melhoria contínua através de colaboração @@ -125,6 +130,7 @@ O projeto IBAV é open source para garantir: ## Contribuição Contribuições são bem-vindas e devem seguir: + - O código de conduta do projeto (CODE_OF_CONDUCT.md) - As diretrizes de contribuição (CONTRIBUTING.md) - O processo de issues e pull requests do GitHub @@ -134,6 +140,7 @@ Contribuições são bem-vindas e devem seguir: ## Contato e Suporte Para dúvidas, sugestões ou reportar problemas: + - Abra uma issue no GitHub - Consulte a documentação em docs/docs.md -- Revise o CHANGELOG.md para atualizações recentes \ No newline at end of file +- Revise o CHANGELOG.md para atualizações recentes From a046a50c339eef7a506f990f30f7a5940a97556d Mon Sep 17 00:00:00 2001 From: "aslan.gama" Date: Tue, 11 Aug 2026 13:30:40 -0300 Subject: [PATCH 6/7] =?UTF-8?q?refactor(docs):=20Atualiza=20regras=20de=20?= =?UTF-8?q?organiza=C3=A7=C3=A3o=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.vue | 73 +++++++++++++++++------------------ docs/specs/ibav-mvp.md | 58 ++++++++++++++++++++++++++++ shared/utils/vehicle-query.ts | 2 +- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/app/app.vue b/app/app.vue index 88b60f9..e859196 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,42 +1,5 @@ - - + + diff --git a/docs/specs/ibav-mvp.md b/docs/specs/ibav-mvp.md index 64ef5eb..d31f6bd 100644 --- a/docs/specs/ibav-mvp.md +++ b/docs/specs/ibav-mvp.md @@ -104,6 +104,64 @@ Não existe ainda nenhuma lógica de domínio implementada. - **TR5** A página deve ser SSR-safe (sem hydration mismatch). - **TR6** Query params devem sincronizar o estado do formulário via `useRoute().query` e `useRouter()`. - **TR7** Cobertura de testes unitários acima de 80% para os utilitários matemáticos. +- **TR8** Em todos os arquivos `.vue` (SFC), o bloco `