Every algorithm in docs/RULES.md, implemented exactly as written: the RUC check digit, the calendario perpetuo with weekend and holiday roll forward, CDC and KUDE QR parsing, keyword classification, computeF120, computeF515 and the dashboard projections, plus the two form definitions. Both worked examples reproduce verbatim on the first implementation: F120 at Gs. 277.273 to pay with the Gs. 350.000 flip case, F515 at Gs. 11.290.000 on a 5,65% effective rate. 162 tests over the package, coverage enforced at 100% statements, branches, functions and lines; the only exclusions are three bounded-loop guards marked v8 ignore with a comment saying why. No tax rule was invented. All six TODO-TAX-VERIFY items from RULES.md have a test pinning today's behaviour and a row in the DECISIONS.md register, so verification later is a red/green diff. Where RULES.md was silent the choice is marked SPEC-GAP in the code and listed too, the notable one being that taxpayer.hasIrp gates the deduction amount. No floats anywhere in a money path: money is a branded Pyg of whole guaranies and percentages go through integer arithmetic rounded half up. Also: contracts now takes IRP_CATEGORIES from rules rather than declaring the eight strings twice; classification returns reason codes with catalog strings in both locales, so the detail sheet localizes; apps/api/.env.example now names the same web port as apps/web/.env.example, without which a fresh checkout fails sign in on the origin check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
124 lines
3.8 KiB
JavaScript
124 lines
3.8 KiB
JavaScript
import js from '@eslint/js';
|
|
import react from 'eslint-plugin-react';
|
|
import reactHooks from 'eslint-plugin-react-hooks';
|
|
import globals from 'globals';
|
|
import tseslint from 'typescript-eslint';
|
|
|
|
export default tseslint.config(
|
|
{
|
|
ignores: ['**/dist/**', '**/.next/**', '**/node_modules/**', '**/coverage/**', '**/*.d.ts'],
|
|
},
|
|
|
|
js.configs.recommended,
|
|
...tseslint.configs.recommended,
|
|
|
|
{
|
|
rules: {
|
|
// `any` is allowed only with a written reason, per the build constraints.
|
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
'@typescript-eslint/no-unused-vars': [
|
|
'error',
|
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
|
],
|
|
'@typescript-eslint/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports' }],
|
|
eqeqeq: ['error', 'always'],
|
|
'no-console': 'off',
|
|
},
|
|
},
|
|
|
|
// Module boundaries, SPEC.md section 4.
|
|
{
|
|
files: ['packages/**/*.ts'],
|
|
rules: {
|
|
'no-restricted-imports': [
|
|
'error',
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ['**/apps/**', '@impuestos/api', '@impuestos/web'],
|
|
message:
|
|
'packages/* are pure and must not import from apps. Move the shared piece into a package.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
{
|
|
files: ['apps/api/src/**/*.ts'],
|
|
languageOptions: { globals: globals.node },
|
|
rules: {
|
|
'no-restricted-imports': [
|
|
'error',
|
|
{
|
|
patterns: [
|
|
{
|
|
// Only modules/pii may reach the pii tables. Everything else goes through
|
|
// the functions that module exports, so PII access stays auditable.
|
|
group: ['**/modules/pii/*', '!**/modules/pii/index'],
|
|
message:
|
|
'Import from modules/pii (its index) instead of reaching into the pii module.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
files: ['apps/api/src/modules/pii/**/*.ts', 'apps/api/src/db/**/*.ts'],
|
|
rules: { 'no-restricted-imports': 'off' },
|
|
},
|
|
|
|
// The web app has no database access at all: it holds no DB dependency and may not
|
|
// import one even transitively through a shared package.
|
|
{
|
|
files: ['apps/web/**/*.{ts,tsx}'],
|
|
languageOptions: {
|
|
globals: { ...globals.browser, ...globals.node },
|
|
parserOptions: { ecmaFeatures: { jsx: true } },
|
|
},
|
|
plugins: { react, 'react-hooks': reactHooks },
|
|
settings: { react: { version: 'detect' } },
|
|
rules: {
|
|
...reactHooks.configs.recommended.rules,
|
|
'no-restricted-imports': [
|
|
'error',
|
|
{
|
|
paths: [
|
|
{ name: 'kysely', message: 'The web app never touches the database.' },
|
|
{ name: 'better-sqlite3', message: 'The web app never touches the database.' },
|
|
{ name: 'pg', message: 'The web app never touches the database.' },
|
|
{
|
|
name: 'better-auth',
|
|
message: 'Auth server code lives in apps/api. Use better-auth/react here.',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
// Constraint 12: user facing copy comes from the catalogs, never inline.
|
|
// Punctuation and separators are allowed so layout markup stays readable.
|
|
'react/jsx-no-literals': [
|
|
'error',
|
|
{
|
|
noStrings: true,
|
|
allowedStrings: ['·', '/', '|', ':', ',', '.', '-', '+', '(', ')', '%', '✓'],
|
|
ignoreProps: true,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// Plain Node scripts: no JSX, no browser globals.
|
|
{
|
|
files: ['**/scripts/**/*.mjs', '*.config.{js,mjs,ts}', '**/*.config.{js,mjs,ts}'],
|
|
languageOptions: { globals: globals.node },
|
|
},
|
|
|
|
// Tests assert on real copy, so literals are the point there.
|
|
{
|
|
files: ['**/*.test.ts', '**/*.test.tsx', 'e2e/**/*.ts'],
|
|
rules: { 'react/jsx-no-literals': 'off' },
|
|
},
|
|
);
|