@thecodecodex/tools 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -2
- package/package.json +46 -4
- package/src/base64.ts +36 -0
- package/src/br/cnpj.ts +55 -0
- package/src/br/cpf.ts +61 -0
- package/src/cli/main.ts +93 -0
- package/src/cli/tools.ts +643 -0
- package/src/compensation-comparator.ts +124 -0
- package/src/css-playground.ts +699 -0
- package/src/dev-ref.ts +295 -0
- package/src/diff.ts +301 -0
- package/src/finance/clt.ts +189 -0
- package/src/finance/compound.ts +78 -0
- package/src/finance/pj.ts +146 -0
- package/src/fov.ts +145 -0
- package/src/hash.ts +13 -0
- package/src/image-to-pdf.ts +299 -0
- package/src/invoice-pdf.ts +134 -0
- package/src/invoice.ts +94 -0
- package/src/jwt.ts +44 -0
- package/src/market.ts +49 -0
- package/src/password.ts +51 -0
- package/src/pdf-studio.ts +453 -0
- package/src/random/index.ts +19 -0
- package/src/regex.ts +426 -0
- package/src/skills.json +60 -0
- package/src/skills.ts +52 -0
- package/src/state-holidays.ts +94 -0
- package/src/table-convert/code.ts +99 -0
- package/src/table-convert/index.ts +42 -0
- package/src/table-convert/model.ts +72 -0
- package/src/table-convert/parse.ts +242 -0
- package/src/table-convert/serialize.ts +435 -0
- package/src/table-convert/transform.ts +133 -0
- package/src/table-convert/xlsx.ts +262 -0
- package/src/table-convert/zip.ts +127 -0
- package/src/timestamp.ts +50 -0
- package/src/url-codec.ts +25 -0
- package/src/uuid.ts +53 -0
- package/src/vacation.ts +171 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Encargos do empregado CLT em 2026.
|
|
2
|
+
// INSS: Portaria Interministerial MPS/MF nº 13/2026 (teto R$ 8.475,55).
|
|
3
|
+
// IRRF: tabela progressiva mensal + redução da Lei nº 15.270/2025, conforme
|
|
4
|
+
// https://www.gov.br/receitafederal/pt-br/assuntos/meu-imposto-de-renda/tabelas/2026
|
|
5
|
+
|
|
6
|
+
export interface Bracket {
|
|
7
|
+
upTo: number
|
|
8
|
+
rate: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const INSS_2026: Bracket[] = [
|
|
12
|
+
{ upTo: 1621.0, rate: 0.075 },
|
|
13
|
+
{ upTo: 2902.84, rate: 0.09 },
|
|
14
|
+
{ upTo: 4354.27, rate: 0.12 },
|
|
15
|
+
{ upTo: 8475.55, rate: 0.14 },
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
// Tabela progressiva mensal: alíquota e parcela a deduzir por faixa da BASE DE CÁLCULO
|
|
19
|
+
export const IRRF_2026 = [
|
|
20
|
+
{ upTo: 2428.8, rate: 0, deduction: 0 },
|
|
21
|
+
{ upTo: 2826.65, rate: 0.075, deduction: 182.16 },
|
|
22
|
+
{ upTo: 3751.05, rate: 0.15, deduction: 394.16 },
|
|
23
|
+
{ upTo: 4664.68, rate: 0.225, deduction: 675.49 },
|
|
24
|
+
{ upTo: Infinity, rate: 0.275, deduction: 908.73 },
|
|
25
|
+
] as const
|
|
26
|
+
|
|
27
|
+
export const IRRF_DEPENDENT_2026 = 189.59
|
|
28
|
+
// Desconto simplificado mensal: substitui as deduções legais quando for maior
|
|
29
|
+
export const IRRF_SIMPLIFIED_2026 = 607.2
|
|
30
|
+
|
|
31
|
+
// Redução (Lei 15.270/2025): calculada sobre os RENDIMENTOS TRIBUTÁVEIS (salário bruto), não sobre a base
|
|
32
|
+
export const REDUCTION_2026 = { exemptUpTo: 5000, max: 312.89, phaseOutUpTo: 7350, constant: 978.62, factor: 0.133145 }
|
|
33
|
+
|
|
34
|
+
// Arredonda centavos "para cima no meio" como a folha (121,575 → 121,58); toFixed(10) remove o ruído binário (121,57499…)
|
|
35
|
+
const round2 = (value: number) => Math.round(Number(value.toFixed(10)) * 100) / 100
|
|
36
|
+
|
|
37
|
+
// INSS progressivo: cada alíquota incide só sobre a parcela dentro da sua faixa
|
|
38
|
+
export function inss(gross: number, table: Bracket[] = INSS_2026): number {
|
|
39
|
+
let total = 0
|
|
40
|
+
let floor = 0
|
|
41
|
+
for (const { upTo, rate } of table) {
|
|
42
|
+
if (gross <= floor) break
|
|
43
|
+
total += (Math.min(gross, upTo) - floor) * rate
|
|
44
|
+
floor = upTo
|
|
45
|
+
}
|
|
46
|
+
return round2(total)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function irrfTable(base: number): number {
|
|
50
|
+
if (base <= 0) return 0
|
|
51
|
+
const bracket = IRRF_2026.find((b) => base <= b.upTo) ?? IRRF_2026[IRRF_2026.length - 1]
|
|
52
|
+
return round2(Math.max(0, base * (bracket?.rate ?? 0) - (bracket?.deduction ?? 0)))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function irrfReduction(taxableIncome: number, taxBeforeReduction: number): number {
|
|
56
|
+
const { exemptUpTo, max, phaseOutUpTo, constant, factor } = REDUCTION_2026
|
|
57
|
+
if (taxableIncome <= exemptUpTo) return Math.min(max, taxBeforeReduction)
|
|
58
|
+
if (taxableIncome <= phaseOutUpTo) return round2(Math.min(Math.max(0, constant - factor * taxableIncome), taxBeforeReduction))
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface IrrfInput {
|
|
63
|
+
// rendimentos tributáveis do mês (salário bruto)
|
|
64
|
+
taxableIncome: number
|
|
65
|
+
// INSS descontado no mês
|
|
66
|
+
socialSecurity: number
|
|
67
|
+
dependents?: number
|
|
68
|
+
alimony?: number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface IrrfResult {
|
|
72
|
+
base: number
|
|
73
|
+
deduction: "legal" | "simplified"
|
|
74
|
+
deductionAmount: number
|
|
75
|
+
taxBeforeReduction: number
|
|
76
|
+
reduction: number
|
|
77
|
+
tax: number
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* IRRF mensal 2026:
|
|
82
|
+
* 1. deduções legais (INSS + dependentes + pensão) ou desconto simplificado, o que for maior;
|
|
83
|
+
* 2. tabela progressiva sobre a base;
|
|
84
|
+
* 3. redução da Lei 15.270 calculada sobre o rendimento bruto, limitada ao imposto apurado.
|
|
85
|
+
* Também vale para o 13º (tributação exclusiva): a redução se aplica a ele por força do art. 3º-A, §3º, da Lei 9.250/1995,
|
|
86
|
+
* incluído pela Lei 15.270/2025. As férias são rendimento do mês e seguem a regra mensal normal.
|
|
87
|
+
*/
|
|
88
|
+
export function irrf({ taxableIncome, socialSecurity, dependents = 0, alimony = 0 }: IrrfInput): IrrfResult {
|
|
89
|
+
const legal = socialSecurity + dependents * IRRF_DEPENDENT_2026 + alimony
|
|
90
|
+
const simplified = legal < IRRF_SIMPLIFIED_2026
|
|
91
|
+
const deductionAmount = simplified ? IRRF_SIMPLIFIED_2026 : legal
|
|
92
|
+
const base = round2(Math.max(0, taxableIncome - deductionAmount))
|
|
93
|
+
const taxBeforeReduction = irrfTable(base)
|
|
94
|
+
const reduction = irrfReduction(taxableIncome, taxBeforeReduction)
|
|
95
|
+
return { base, deduction: simplified ? "simplified" : "legal", deductionAmount: round2(deductionAmount), taxBeforeReduction, reduction, tax: round2(Math.max(0, taxBeforeReduction - reduction)) }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Atalho para o caso comum: salário bruto → INSS e IRRF do mês
|
|
99
|
+
export function payrollTaxes(gross: number, options: { dependents?: number, alimony?: number } = {}) {
|
|
100
|
+
const socialSecurity = inss(gross)
|
|
101
|
+
const income = irrf({ taxableIncome: gross, socialSecurity, ...options })
|
|
102
|
+
return { inss: socialSecurity, irrf: income.tax, detail: income }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const FGTS_RATE = 0.08
|
|
106
|
+
// Desconto máximo do vale-transporte: 6% do salário-base (Lei 7.418/85), limitado ao custo do benefício
|
|
107
|
+
export const TRANSPORT_VOUCHER_RATE = 0.06
|
|
108
|
+
|
|
109
|
+
export interface CltOptions {
|
|
110
|
+
dependents?: number
|
|
111
|
+
// pensão alimentícia judicial: deduz do IR e sai do líquido
|
|
112
|
+
alimony?: number
|
|
113
|
+
// custo mensal do vale-transporte; o empregado paga até 6% do salário
|
|
114
|
+
transportVoucher?: number
|
|
115
|
+
// outros descontos em folha (coparticipação de plano, empréstimo…)
|
|
116
|
+
otherDiscounts?: number
|
|
117
|
+
// benefícios mensais pagos pela empresa e não descontados (VR/VA, parte do plano de saúde, auxílios)
|
|
118
|
+
monthlyBenefits?: number
|
|
119
|
+
// PLR, bônus ou RSUs líquidos no ano
|
|
120
|
+
annualBonus?: number
|
|
121
|
+
includeFgts?: boolean
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface CltMonth {
|
|
125
|
+
gross: number
|
|
126
|
+
inss: number
|
|
127
|
+
irrf: IrrfResult
|
|
128
|
+
transportDiscount: number
|
|
129
|
+
alimony: number
|
|
130
|
+
otherDiscounts: number
|
|
131
|
+
net: number
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function cltMonth(gross: number, options: CltOptions = {}): CltMonth {
|
|
135
|
+
const { dependents = 0, alimony = 0, transportVoucher = 0, otherDiscounts = 0 } = options
|
|
136
|
+
const socialSecurity = inss(gross)
|
|
137
|
+
const income = irrf({ taxableIncome: gross, socialSecurity, dependents, alimony })
|
|
138
|
+
const transportDiscount = round2(Math.min(transportVoucher, gross * TRANSPORT_VOUCHER_RATE))
|
|
139
|
+
const net = round2(gross - socialSecurity - income.tax - transportDiscount - alimony - otherDiscounts)
|
|
140
|
+
return { gross, inss: socialSecurity, irrf: income, transportDiscount, alimony, otherDiscounts, net }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface CltYear {
|
|
144
|
+
month: CltMonth
|
|
145
|
+
// 13º líquido (tributação exclusiva, mesma tabela e redução)
|
|
146
|
+
net13th: number
|
|
147
|
+
// ganho líquido de férias: (salário + 1/3) tributados juntos, menos o salário líquido do mês que ele substitui
|
|
148
|
+
vacationBonusNet: number
|
|
149
|
+
fgtsYear: number
|
|
150
|
+
// benefícios no ano: mensais × 12 + parte do VT paga pela empresa
|
|
151
|
+
benefitsYear: number
|
|
152
|
+
bonusYear: number
|
|
153
|
+
// tudo que entra no ano (salários líquidos, 13º, 1/3 de férias, FGTS, benefícios, bônus)
|
|
154
|
+
annualTotal: number
|
|
155
|
+
monthlyEquivalent: number
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function cltYear(gross: number, options: CltOptions = {}): CltYear {
|
|
159
|
+
const { dependents = 0, monthlyBenefits = 0, annualBonus = 0, includeFgts = true, transportVoucher = 0 } = options
|
|
160
|
+
const month = cltMonth(gross, options)
|
|
161
|
+
|
|
162
|
+
const tax13th = payrollTaxes(gross, { dependents })
|
|
163
|
+
const net13th = round2(gross - tax13th.inss - tax13th.irrf)
|
|
164
|
+
|
|
165
|
+
// no mês de férias o salário vem com o terço constitucional e é tributado junto
|
|
166
|
+
const vacation = cltMonth(gross + gross / 3, options)
|
|
167
|
+
const vacationBonusNet = round2(vacation.net - month.net)
|
|
168
|
+
|
|
169
|
+
const fgtsYear = includeFgts ? round2(gross * FGTS_RATE * (12 + 1 + 1 / 3)) : 0
|
|
170
|
+
const benefitsYear = round2(monthlyBenefits * 12 + (transportVoucher - month.transportDiscount) * 11)
|
|
171
|
+
const annualTotal = round2(month.net * 12 + net13th + vacationBonusNet + fgtsYear + benefitsYear + annualBonus)
|
|
172
|
+
return { month, net13th, vacationBonusNet, fgtsYear, benefitsYear, bonusYear: annualBonus, annualTotal, monthlyEquivalent: round2(annualTotal / 12) }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Menor salário bruto cujo pacote anual atinge a meta (busca binária, precisão de centavos)
|
|
176
|
+
export function grossForAnnualTotal(target: number, options: CltOptions = {}): number {
|
|
177
|
+
if (cltYear(0, options).annualTotal >= target) return 0
|
|
178
|
+
let low = 0
|
|
179
|
+
let high = 1_000_000
|
|
180
|
+
while (high - low > 0.01) {
|
|
181
|
+
const mid = (low + high) / 2
|
|
182
|
+
if (cltYear(mid, options).annualTotal >= target) high = mid
|
|
183
|
+
else low = mid
|
|
184
|
+
}
|
|
185
|
+
// menor valor em centavos que atinge a meta (evita 12.000,01 quando 12.000,00 já basta)
|
|
186
|
+
const cents = Math.floor(high * 100) / 100
|
|
187
|
+
return cltYear(cents, options).annualTotal >= target ? cents : Math.round((cents + 0.01) * 100) / 100
|
|
188
|
+
}
|
|
189
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Juros compostos com aportes mensais, IR regressivo da renda fixa e correção pela inflação.
|
|
2
|
+
// Taxas anuais são EFETIVAS (como CDI, poupança e Tesouro são cotados): mensal = (1 + anual)^(1/12) − 1.
|
|
3
|
+
|
|
4
|
+
export interface CompoundInput {
|
|
5
|
+
initial: number
|
|
6
|
+
monthly: number
|
|
7
|
+
months: number
|
|
8
|
+
// taxa anual efetiva, ex.: 0.1 = 10% a.a.
|
|
9
|
+
annualRate: number
|
|
10
|
+
// inflação anual esperada, para o valor real (poder de compra de hoje)
|
|
11
|
+
annualInflation?: number
|
|
12
|
+
// aplica o IR regressivo sobre o rendimento no resgate (CDB, Tesouro…); poupança é isenta
|
|
13
|
+
taxed?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface YearRow {
|
|
17
|
+
year: number
|
|
18
|
+
contributed: number
|
|
19
|
+
interest: number
|
|
20
|
+
balance: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CompoundResult {
|
|
24
|
+
rows: YearRow[]
|
|
25
|
+
contributed: number
|
|
26
|
+
interest: number
|
|
27
|
+
grossBalance: number
|
|
28
|
+
incomeTax: number
|
|
29
|
+
netBalance: number
|
|
30
|
+
// saldo líquido em reais de hoje
|
|
31
|
+
realBalance: number
|
|
32
|
+
// renda mensal sustentável pela regra dos 4% ao ano, em reais de hoje
|
|
33
|
+
monthlyIncome4: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const monthlyRate = (annual: number) => Math.pow(1 + annual, 1 / 12) - 1
|
|
37
|
+
|
|
38
|
+
// Tabela regressiva da renda fixa (Lei 11.033/2004), pelo prazo de cada aporte
|
|
39
|
+
export function regressiveRate(monthsHeld: number): number {
|
|
40
|
+
if (monthsHeld <= 6) return 0.225
|
|
41
|
+
if (monthsHeld <= 12) return 0.2
|
|
42
|
+
if (monthsHeld <= 24) return 0.175
|
|
43
|
+
return 0.15
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function simulate({ initial, monthly, months, annualRate, annualInflation = 0, taxed = false }: CompoundInput): CompoundResult {
|
|
47
|
+
const r = monthlyRate(annualRate)
|
|
48
|
+
const n = Math.max(0, Math.round(months))
|
|
49
|
+
let balance = initial
|
|
50
|
+
let contributed = initial
|
|
51
|
+
const rows: YearRow[] = []
|
|
52
|
+
// aporte no fim de cada mês, depois do rendimento do mês
|
|
53
|
+
for (let m = 1; m <= n; m++) {
|
|
54
|
+
balance = balance * (1 + r) + monthly
|
|
55
|
+
contributed += monthly
|
|
56
|
+
if (m % 12 === 0 || m === n) rows.push({ year: Math.ceil(m / 12), contributed, interest: balance - contributed, balance })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// IR: cada aporte tem o próprio prazo; o ganho de cada um paga a alíquota do seu prazo
|
|
60
|
+
let incomeTax = 0
|
|
61
|
+
if (taxed) {
|
|
62
|
+
const lots = [{ amount: initial, held: n }, ...Array.from({ length: n }, (_, k) => ({ amount: monthly, held: n - (k + 1) }))]
|
|
63
|
+
for (const { amount, held } of lots) incomeTax += amount * (Math.pow(1 + r, held) - 1) * regressiveRate(held)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const netBalance = balance - incomeTax
|
|
67
|
+
const realBalance = netBalance / Math.pow(1 + annualInflation, n / 12)
|
|
68
|
+
return {
|
|
69
|
+
rows,
|
|
70
|
+
contributed,
|
|
71
|
+
interest: balance - contributed,
|
|
72
|
+
grossBalance: balance,
|
|
73
|
+
incomeTax,
|
|
74
|
+
netBalance,
|
|
75
|
+
realBalance,
|
|
76
|
+
monthlyIncome4: (realBalance * 0.04) / 12,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { INSS_2026, irrf } from "./clt"
|
|
2
|
+
|
|
3
|
+
// Tributação de PJ de serviços (2026). Fontes: LC 123/2006 (Anexos III e V do Simples), Res. CGSN 140/2018,
|
|
4
|
+
// Lei 15.270/2025 (dividendos), DAS-MEI com salário mínimo de R$ 1.621.
|
|
5
|
+
|
|
6
|
+
export const MINIMUM_WAGE_2026 = 1621
|
|
7
|
+
export const MEI = { annualLimit: 81_000, monthlyDas: 86.05 } // 5% do salário mínimo + R$ 5 de ISS
|
|
8
|
+
// Faixas por receita bruta dos últimos 12 meses (RBT12): alíquota nominal e parcela a deduzir
|
|
9
|
+
// cppShare: parte do DAS que é contribuição previdenciária patronal (repartição do Anexo III)
|
|
10
|
+
export const SIMPLES_ANEXO_III = [
|
|
11
|
+
{ upTo: 180_000, rate: 0.06, deduction: 0, cppShare: 0.434 },
|
|
12
|
+
{ upTo: 360_000, rate: 0.112, deduction: 9_360, cppShare: 0.434 },
|
|
13
|
+
{ upTo: 720_000, rate: 0.135, deduction: 17_640, cppShare: 0.434 },
|
|
14
|
+
{ upTo: 1_800_000, rate: 0.16, deduction: 35_640, cppShare: 0.434 },
|
|
15
|
+
{ upTo: 3_600_000, rate: 0.21, deduction: 125_640, cppShare: 0.434 },
|
|
16
|
+
{ upTo: 4_800_000, rate: 0.33, deduction: 648_000, cppShare: 0.305 },
|
|
17
|
+
]
|
|
18
|
+
export const SIMPLES_ANEXO_V = [
|
|
19
|
+
{ upTo: 180_000, rate: 0.155, deduction: 0 },
|
|
20
|
+
{ upTo: 360_000, rate: 0.18, deduction: 4_500 },
|
|
21
|
+
{ upTo: 720_000, rate: 0.195, deduction: 9_900 },
|
|
22
|
+
{ upTo: 1_800_000, rate: 0.205, deduction: 17_100 },
|
|
23
|
+
{ upTo: 3_600_000, rate: 0.23, deduction: 62_100 },
|
|
24
|
+
{ upTo: 4_800_000, rate: 0.305, deduction: 540_000 },
|
|
25
|
+
]
|
|
26
|
+
export const SIMPLES_LIMIT = 4_800_000
|
|
27
|
+
// Fator R: folha de 12 meses ÷ RBT12 ≥ 28% leva atividades do Anexo V para o Anexo III.
|
|
28
|
+
// A folha inclui o pró-labore E a CPP efetivamente recolhida (LC 123, art. 18, §24) — no Simples, a CPP vem dentro do DAS.
|
|
29
|
+
export const FATOR_R = 0.28
|
|
30
|
+
// Lucro Presumido, serviços: presunção de 32% para IRPJ e CSLL
|
|
31
|
+
export const PRESUMIDO = { presumption: 0.32, irpj: 0.15, irpjSurcharge: 0.1, surchargeMonthlyThreshold: 20_000, csll: 0.09, pis: 0.0065, cofins: 0.03, cpp: 0.2 }
|
|
32
|
+
// Sócio: INSS de 11% sobre o pró-labore, limitado ao teto
|
|
33
|
+
export const PRO_LABORE_INSS_RATE = 0.11
|
|
34
|
+
// Dividendos (Lei 15.270/2025): acima de R$ 50 mil/mês da mesma empresa para a mesma pessoa, 10% sobre o total
|
|
35
|
+
export const DIVIDENDS = { monthlyExemption: 50_000, rate: 0.1 }
|
|
36
|
+
|
|
37
|
+
type Bracket = { upTo: number, rate: number, deduction: number }
|
|
38
|
+
|
|
39
|
+
export function simplesEffectiveRate(rbt12: number, table: Bracket[]): number {
|
|
40
|
+
if (rbt12 <= 0) return table[0]?.rate ?? 0
|
|
41
|
+
const bracket = table.find((b) => rbt12 <= b.upTo) ?? table[table.length - 1]
|
|
42
|
+
return bracket ? (rbt12 * bracket.rate - bracket.deduction) / rbt12 : 0
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function simplesCppShare(rbt12: number): number {
|
|
46
|
+
return (SIMPLES_ANEXO_III.find((b) => rbt12 <= b.upTo) ?? SIMPLES_ANEXO_III[SIMPLES_ANEXO_III.length - 1])?.cppShare ?? 0
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Cooperado de cooperativa de trabalho: contribuinte individual, 20% retidos pela cooperativa até o teto
|
|
50
|
+
// (a alíquota de 11% acabou com o RE 595.838/STF, que derrubou a contribuição de 15% do tomador)
|
|
51
|
+
export const COOPERATIVE_INSS_RATE = 0.2
|
|
52
|
+
export function cooperativeInss(remuneration: number): number {
|
|
53
|
+
const ceiling = INSS_2026[INSS_2026.length - 1]?.upTo ?? Infinity
|
|
54
|
+
return Math.min(remuneration, ceiling) * COOPERATIVE_INSS_RATE
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function proLaboreInss(proLabore: number): number {
|
|
58
|
+
const ceiling = INSS_2026[INSS_2026.length - 1]?.upTo ?? Infinity
|
|
59
|
+
return Math.min(proLabore, ceiling) * PRO_LABORE_INSS_RATE
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type Regime = "mei" | "simples-iii" | "simples-v" | "presumido"
|
|
63
|
+
|
|
64
|
+
export interface PjInput {
|
|
65
|
+
monthlyRevenue: number
|
|
66
|
+
// pró-labore mensal; no Anexo III ele é ajustado para atingir o Fator R
|
|
67
|
+
proLabore: number
|
|
68
|
+
// ISS do município (Lucro Presumido)
|
|
69
|
+
issRate: number
|
|
70
|
+
accountant: number
|
|
71
|
+
otherExpenses: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface RegimeResult {
|
|
75
|
+
regime: Regime
|
|
76
|
+
allowed: boolean
|
|
77
|
+
// tributos da empresa (DAS ou IRPJ+CSLL+PIS+COFINS+ISS+CPP)
|
|
78
|
+
companyTaxes: number
|
|
79
|
+
proLabore: number
|
|
80
|
+
proLaboreInss: number
|
|
81
|
+
proLaboreIrrf: number
|
|
82
|
+
dividendsTax: number
|
|
83
|
+
totalTaxes: number
|
|
84
|
+
effectiveRate: number
|
|
85
|
+
// o que sobra para o sócio no mês: faturamento − tributos − contador − despesas
|
|
86
|
+
net: number
|
|
87
|
+
// Simples: alíquota efetiva do DAS
|
|
88
|
+
simplesRate?: number
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function partner(proLabore: number) {
|
|
92
|
+
const inss = proLaboreInss(proLabore)
|
|
93
|
+
return { inss, irrf: irrf({ taxableIncome: proLabore, socialSecurity: inss }).tax }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function finish(regime: Regime, input: PjInput, companyTaxes: number, proLabore: number, allowed = true, simplesRate?: number): RegimeResult {
|
|
97
|
+
const { inss, irrf: irrfTax } = partner(proLabore)
|
|
98
|
+
const profit = input.monthlyRevenue - companyTaxes - proLabore - input.accountant - input.otherExpenses
|
|
99
|
+
const dividendsTax = profit > DIVIDENDS.monthlyExemption ? profit * DIVIDENDS.rate : 0
|
|
100
|
+
const totalTaxes = companyTaxes + inss + irrfTax + dividendsTax
|
|
101
|
+
const result: RegimeResult = {
|
|
102
|
+
regime,
|
|
103
|
+
allowed,
|
|
104
|
+
companyTaxes,
|
|
105
|
+
proLabore,
|
|
106
|
+
proLaboreInss: inss,
|
|
107
|
+
proLaboreIrrf: irrfTax,
|
|
108
|
+
dividendsTax,
|
|
109
|
+
totalTaxes,
|
|
110
|
+
effectiveRate: input.monthlyRevenue > 0 ? totalTaxes / input.monthlyRevenue : 0,
|
|
111
|
+
net: input.monthlyRevenue - totalTaxes - input.accountant - input.otherExpenses,
|
|
112
|
+
}
|
|
113
|
+
if (simplesRate !== undefined) result.simplesRate = simplesRate
|
|
114
|
+
return result
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function compareRegimes(input: PjInput): RegimeResult[] {
|
|
118
|
+
const revenue = input.monthlyRevenue
|
|
119
|
+
const rbt12 = revenue * 12
|
|
120
|
+
const minimumProLabore = Math.max(input.proLabore, MINIMUM_WAGE_2026)
|
|
121
|
+
|
|
122
|
+
// MEI: DAS fixo, sem pró-labore obrigatório; a maioria das atividades de TI não é permitida
|
|
123
|
+
const mei = finish("mei", { ...input, accountant: 0 }, MEI.monthlyDas, 0, rbt12 <= MEI.annualLimit)
|
|
124
|
+
|
|
125
|
+
// Anexo III via Fator R: pró-labore + CPP do DAS precisam somar 28% do faturamento
|
|
126
|
+
const rateIII = simplesEffectiveRate(rbt12, SIMPLES_ANEXO_III)
|
|
127
|
+
const fatorRProLabore = Math.max(minimumProLabore, revenue * FATOR_R - revenue * rateIII * simplesCppShare(rbt12))
|
|
128
|
+
const simplesIII = finish("simples-iii", input, revenue * rateIII, fatorRProLabore, rbt12 <= SIMPLES_LIMIT, rateIII)
|
|
129
|
+
|
|
130
|
+
// Anexo V: sem Fator R, pró-labore mínimo
|
|
131
|
+
const rateV = simplesEffectiveRate(rbt12, SIMPLES_ANEXO_V)
|
|
132
|
+
const simplesV = finish("simples-v", input, revenue * rateV, minimumProLabore, rbt12 <= SIMPLES_LIMIT, rateV)
|
|
133
|
+
|
|
134
|
+
// Lucro Presumido: tributos sobre o faturamento + CPP de 20% sobre o pró-labore (fora do Simples)
|
|
135
|
+
const p = PRESUMIDO
|
|
136
|
+
const presumedProfit = revenue * p.presumption
|
|
137
|
+
const irpj = presumedProfit * p.irpj + Math.max(0, presumedProfit - p.surchargeMonthlyThreshold) * p.irpjSurcharge
|
|
138
|
+
const presumidoTaxes = irpj + presumedProfit * p.csll + revenue * (p.pis + p.cofins + input.issRate) + minimumProLabore * p.cpp
|
|
139
|
+
const presumido = finish("presumido", input, presumidoTaxes, minimumProLabore)
|
|
140
|
+
|
|
141
|
+
return [mei, simplesIII, simplesV, presumido]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function bestRegime(results: RegimeResult[]): RegimeResult | undefined {
|
|
145
|
+
return results.filter((r) => r.allowed).reduce<RegimeResult | undefined>((best, r) => (!best || r.net > best.net ? r : best), undefined)
|
|
146
|
+
}
|
package/src/fov.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Calculadora de FOV para simuladores — porte da lógica de https://github.com/dinex86/FOV-Calculator (MIT)
|
|
2
|
+
// Ideia: o campo de visão "correto" é o ângulo que a tela realmente ocupa na sua visão,
|
|
3
|
+
// assim os objetos no jogo aparecem em escala real.
|
|
4
|
+
|
|
5
|
+
export interface Setup {
|
|
6
|
+
aspect: [number, number]
|
|
7
|
+
// diagonal de UMA tela, em polegadas
|
|
8
|
+
diagonalIn: number
|
|
9
|
+
// distância dos olhos até a tela, em cm
|
|
10
|
+
distanceCm: number
|
|
11
|
+
screens: 1 | 3
|
|
12
|
+
// espessura da moldura (bezel) de cada tela, em mm — só conta com 3 telas
|
|
13
|
+
bezelMm: number
|
|
14
|
+
curved: boolean
|
|
15
|
+
// raio da curvatura em mm (ex.: "1800R" = 1800)
|
|
16
|
+
radiusMm: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_SETUP: Setup = { aspect: [16, 9], diagonalIn: 27, distanceCm: 60, screens: 1, bezelMm: 0, curved: false, radiusMm: 1800 }
|
|
20
|
+
|
|
21
|
+
export const ASPECTS: [number, number][] = [[16, 9], [16, 10], [21, 9], [24, 10], [32, 9], [32, 10], [5, 4], [4, 3]]
|
|
22
|
+
|
|
23
|
+
const DEG = 180 / Math.PI
|
|
24
|
+
|
|
25
|
+
export function screenSizeCm(diagonalIn: number, [x, y]: [number, number]) {
|
|
26
|
+
const diagonalCm = diagonalIn * 2.54
|
|
27
|
+
const unit = Math.sqrt((diagonalCm * diagonalCm) / (x * x + y * y))
|
|
28
|
+
return { width: x * unit, height: y * unit }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function flatAngle(widthCm: number, distanceCm: number): number {
|
|
32
|
+
return Math.atan2(widthCm / 2, distanceCm) * 2
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Tela curva: usa a corda entre as bordas da tela e a "profundidade" da curva
|
|
36
|
+
function curvedAngle(widthCm: number, radiusMm: number, distanceCm: number): number {
|
|
37
|
+
const radius = radiusMm / 10
|
|
38
|
+
const arcAngle = widthCm / radius
|
|
39
|
+
const depth = radius * (1 - Math.cos(arcAngle / 2))
|
|
40
|
+
const halfChord = Math.sqrt(2 * radius * depth - depth * depth)
|
|
41
|
+
return 2 * Math.atan2(halfChord, distanceCm - depth)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Angles {
|
|
45
|
+
// FOV horizontal de UMA tela, em graus
|
|
46
|
+
hFovSingle: number
|
|
47
|
+
// FOV horizontal total (todas as telas), em graus
|
|
48
|
+
hFov: number
|
|
49
|
+
// FOV vertical, em graus
|
|
50
|
+
vFov: number
|
|
51
|
+
// ângulo para girar cada tela lateral (igual ao hFOV de uma tela)
|
|
52
|
+
sideScreenAngle: number
|
|
53
|
+
widthCm: number
|
|
54
|
+
heightCm: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function calculateAngles(setup: Setup): Angles {
|
|
58
|
+
const [x, y] = setup.aspect
|
|
59
|
+
const { width, height } = screenSizeCm(setup.diagonalIn, setup.aspect)
|
|
60
|
+
// a moldura entre telas "rouba" campo de visão: soma 2 bezels (em cm) à largura
|
|
61
|
+
const effectiveWidth = width + (setup.screens > 1 ? (setup.bezelMm / 10) * 2 : 0)
|
|
62
|
+
const h = setup.curved ? curvedAngle(effectiveWidth, setup.radiusMm, setup.distanceCm) : flatAngle(effectiveWidth, setup.distanceCm)
|
|
63
|
+
const v = 2 * Math.atan2(Math.tan(h / 2) * y, x)
|
|
64
|
+
return { hFovSingle: h * DEG, hFov: h * DEG * setup.screens, vFov: v * DEG, sideScreenAngle: h * DEG, widthCm: width, heightCm: height }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type Kind = "hfov" | "hfov-rad" | "hfov-step" | "vfov" | "vfov-multiplier" | "vfov-step"
|
|
68
|
+
|
|
69
|
+
interface GameConfig {
|
|
70
|
+
name: string
|
|
71
|
+
kind: Kind
|
|
72
|
+
min?: number
|
|
73
|
+
max?: number
|
|
74
|
+
decimals: number
|
|
75
|
+
factor?: number
|
|
76
|
+
// jogos com slider relativo: valor = round((fov − base) / incremento) × passo
|
|
77
|
+
base?: number
|
|
78
|
+
increment?: number
|
|
79
|
+
step?: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Valores e limites de cada jogo conforme o projeto original
|
|
83
|
+
export const GAMES: GameConfig[] = [
|
|
84
|
+
{ name: "Assetto Corsa / Assetto Corsa Competizione", kind: "vfov", min: 10, max: 120, decimals: 1 },
|
|
85
|
+
{ name: "rFactor 1 & 2, GSC, GSCE, SCE, AMS (ISI)", kind: "vfov", min: 10, max: 100, decimals: 0 },
|
|
86
|
+
{ name: "DiRT Rally 1/2, GRID Autosport", kind: "vfov", min: 10, max: 115, decimals: 0, factor: 2 },
|
|
87
|
+
{ name: "RaceRoom Racing Experience", kind: "hfov", min: 35, max: 180, decimals: 1 },
|
|
88
|
+
{ name: "Project CARS 1/2", kind: "hfov", min: 35, max: 180, decimals: 0 },
|
|
89
|
+
{ name: "Euro & American Truck Simulator", kind: "hfov", min: 35, max: 180, decimals: 0 },
|
|
90
|
+
{ name: "F1 2016–2018", kind: "hfov-step", min: -1, max: 1, decimals: 2, base: 77, increment: 2, step: 0.05 },
|
|
91
|
+
{ name: "F1 2019–2020", kind: "hfov-step", min: -10, max: 10, decimals: 1, base: 77, increment: 2, step: 0.1 },
|
|
92
|
+
{ name: "F1 2021+", kind: "hfov-step", min: -20, max: 20, decimals: 0, base: 77, increment: 2, step: 1 },
|
|
93
|
+
{ name: "EA Sports WRC", kind: "vfov-step", min: 0, max: 100, decimals: 0, base: 18, increment: 0.5, step: 1 },
|
|
94
|
+
{ name: "Richard Burns Rally", kind: "hfov-rad", min: 10, max: 180, decimals: 6 },
|
|
95
|
+
{ name: "GTR2", kind: "vfov-multiplier", min: 0.5, max: 1.5, decimals: 1, base: 58 },
|
|
96
|
+
{ name: "Race 07", kind: "vfov-multiplier", min: 0.4, max: 1.5, decimals: 1, base: 58 },
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
export type GameUnit = "°" | "rad" | "x" | ""
|
|
100
|
+
|
|
101
|
+
export interface GameResult {
|
|
102
|
+
name: string
|
|
103
|
+
kind: Kind
|
|
104
|
+
value: string
|
|
105
|
+
unit: GameUnit
|
|
106
|
+
// true quando o valor ideal está fora do que o jogo permite e foi limitado
|
|
107
|
+
clamped: boolean
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function gameSettings(setup: Setup, angles: Angles = calculateAngles(setup)): GameResult[] {
|
|
111
|
+
return GAMES.map((game) => {
|
|
112
|
+
let value: number
|
|
113
|
+
let unit: GameUnit = "°"
|
|
114
|
+
switch (game.kind) {
|
|
115
|
+
case "hfov":
|
|
116
|
+
case "hfov-step":
|
|
117
|
+
value = angles.hFov
|
|
118
|
+
break
|
|
119
|
+
case "hfov-rad": {
|
|
120
|
+
// RBR usa o FOV horizontal de uma tela 4:3 com a mesma altura
|
|
121
|
+
const [x, y] = setup.aspect
|
|
122
|
+
const width4by3 = ((angles.widthCm + (setup.screens > 1 ? (setup.bezelMm / 10) * 2 : 0)) / x) * y / 3 * 4
|
|
123
|
+
value = flatAngle(width4by3, setup.distanceCm) * DEG
|
|
124
|
+
unit = "rad"
|
|
125
|
+
break
|
|
126
|
+
}
|
|
127
|
+
default:
|
|
128
|
+
value = angles.vFov
|
|
129
|
+
}
|
|
130
|
+
value *= game.factor ?? 1
|
|
131
|
+
if (game.kind === "vfov-multiplier") {
|
|
132
|
+
value /= game.base ?? 1
|
|
133
|
+
unit = "x"
|
|
134
|
+
}
|
|
135
|
+
if (game.kind === "hfov-step" || game.kind === "vfov-step") {
|
|
136
|
+
value = Math.round((value - (game.base ?? 0)) / (game.increment ?? 1)) * (game.step ?? 1)
|
|
137
|
+
unit = ""
|
|
138
|
+
}
|
|
139
|
+
const limited = Math.min(Math.max(value, game.min ?? -Infinity), game.max ?? Infinity)
|
|
140
|
+
const clamped = Math.abs(limited - value) > 1e-9
|
|
141
|
+
const final = unit === "rad" ? limited * (Math.PI / 180) : limited
|
|
142
|
+
const scale = 10 ** game.decimals
|
|
143
|
+
return { name: game.name, kind: game.kind, value: (Math.round(final * scale) / scale).toFixed(game.decimals), unit, clamped }
|
|
144
|
+
})
|
|
145
|
+
}
|
package/src/hash.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Hashes via Web Crypto (nativo do navegador e do Node). MD5 não existe na Web Crypto por ser inseguro.
|
|
2
|
+
export const ALGORITHMS = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"] as const
|
|
3
|
+
export type Algorithm = (typeof ALGORITHMS)[number]
|
|
4
|
+
|
|
5
|
+
export async function hash(text: string, algorithm: Algorithm): Promise<string> {
|
|
6
|
+
const digest = await crypto.subtle.digest(algorithm, new TextEncoder().encode(text))
|
|
7
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function hashAll(text: string): Promise<Record<Algorithm, string>> {
|
|
11
|
+
const values = await Promise.all(ALGORITHMS.map((algorithm) => hash(text, algorithm)))
|
|
12
|
+
return Object.fromEntries(ALGORITHMS.map((algorithm, i) => [algorithm, values[i] ?? ""])) as Record<Algorithm, string>
|
|
13
|
+
}
|