Visió general de l’arquitectura¶
Cadrumo és una aplicació local-first que prepara presentacions d’impostos espanyols mitjançant la interfície de línia d’ordres aeat. Modela el registre normatiu de l’autoritat fiscal, ingereix i classifica els teus registres financers i calcula les caselles numerades de cada formulari fiscal. Després comprova l’esborrany i exporta un fitxer que presentes tu mateix. L’AEAT és l’Agencia Estatal de Administración Tributaria, l’agència tributària d’Espanya.
Utilitza aquesta pàgina com a punt d’entrada quan llegeixis el codi base per primera vegada. És un mapa, no un directori: anomena les capes, els conceptes fonamentals i els tipus canònics als quals navegaràs, i s’atura aquí. Per a signatures i ordres exactes, segueix els enllaços de Entrar al codi.
Per què el sistema té aquesta forma¶
Preparar una declaració d’impostos espanyola a mà és propens a errors i difícil d’auditar. Una xifra d’un formulari ha de poder-se rastrejar fins a un moviment bancari, una norma i una regla publicada. Un formulari anual posterior ha de mantenir-se coherent amb els trimestrals que l’han alimentat. Tres decisions de disseny responen a aquest problema i donen forma a tota la resta:
Local-first. Els teus registres financers no surten mai de la teva màquina. L’eina funciona fora de línia i desa càrregues útils xifrades en una base de dades local al disc.
El registre és l’autoritat. Les regles fiscals (tipus, trams, terminis i la base legal de cada casella) no estan codificades de manera fixa. S’escriuen com a dades, es compilen, es validen i es llegeixen a través d’una única autoritat. Una xifra sempre es pot rastrejar fins a la regla que la va produir.
Qui presenta és la persona, no l’eina. El pipeline acaba en un fitxer d’exportació. Enviar-lo a l’agència és un pas humà deliberat. L’eina no té cap via de presentació, de manera que la presentació en directe és absent en comptes d’estar protegida.
L’estructura que segueix és hexagonal: les regles de negoci se situen al centre, i els analitzadors, els navegadors i l’emmagatzematge que toquen el món exterior se situen a la vora. La majoria de les dependències apunten cap endins. La composició de l’aplicació té una excepció explícita i controlada que arriba als adaptadors. Això manté la lògica fiscal comprovable per si mateixa i permet canviar un adaptador sense tocar cap regla.
Com llegir aquesta pàgina¶
Quatre vistes cobreixen el sistema al nivell més alt. Cadascuna combina un diagrama amb una explicació breu:
Les capes - les cinc capes hexagonals i què posseeix cadascuna.
El pipeline de l’autoritat del registre - com les dades de les regles fiscals es converteixen en els snapshots que llegeix el codi en temps d’execució.
El cicle de vida del modelo - el trajecte des dels registres fins a un fitxer d’exportació, i com es fonamenta cada xifra.
La persistència i el límit de seguretat - on resideix l’estat, i per què no en surt res.
Les capes¶
Cadrumo separa les responsabilitats en cinc capes dins de src/cadrumo/. Normalment, les capes només depenen de les capes que tenen a dins; la composició de l’aplicació té l’excepció d’adaptador documentada.
flowchart TD
EP["entrypoints — CLI (config · app)"]
AD["adapters — inbound · outbound · persistence"]
APP["application — use-case services"]
DOM["domain — tax rules and entities"]
CORE["core — enums · JavaScript Object Notation (JSON) contract · config · primitives"]
EP --> APP
EP --> CORE
APP --> DOM
APP --> AD
APP --> CORE
AD --> DOM
AD --> CORE
DOM --> CORE
DOM -.->|"secure-repo seam"| AD
Alternativa textual. Els entrypoints criden les capes application i core. L’application crida les capes domain, adapters i core; els adapters criden domain i core; i domain crida core. El domain arriba als adapters només a través de la costura anotada del repositori segur.
core` és la capa més interna. Posseeix les primitives transversals que comparteixen totes les altres capes. Els seus enums tipats defineixen conjunts tancats de valors com ara els codis de període, els dominis fiscals, els identificadors de modelo i els tipus de font de binding. core també posseeix el contracte d’embolcall JSON, la configuració, els tipus de diners i de temps, i la taxonomia d’errors.
**domain
** conté regles i registres fiscals purs: el registre de modelos, les definicions de casella (una casella és un requadre numerat d'un formulari), les observacions de presentació i els càlculs que s'hi fan. Només depèn decore`.application` orquestra els casos d’ús. Uneix les regles de domain amb els adapters per construir esborranys de presentació, agregar el ledger, executar diagnòstics i projectar l’estat. No fa cap entrada ni sortida per si mateixa.
**adapters
** connecta l'aplicació amb el món exterior en tres parts.inboundanalitza els extractes entrants en Portable Document Format (PDF), valors separats per comes (CSV), Open Financial Exchange (OFX) i Microsoft Excel Open XML (XLSX).outboundarriba a serveis externs (sessions de navegador de l'AEAT, oracles de càlcul i proveïdors d'autenticació).persistence` desa càrregues útils xifrades a la base de dades local.**entrypoints
** exposa l'aplicació als operadors. La línia d'ordres és aquí, i la seva superfície arrel es limita a dues famílies d'ordres,configiapp`.
Dues regles de límit mantenen honestes les capes. Les dades de límit creuen com a models pydantic v2 validats, mai com a diccionaris solts. Els conjunts tancats de valors es declaren com a enums tipats a core i circulen com a membres d’enum, de manera que un valor no vàlid es rebutja al límit.
Les fletxes de dependència apunten cap endins amb una excepció anotada. Tres repositoris de domain (filing, justificante i submission) se situen directament sobre la classe base d’emmagatzematge xifrat a adapters.persistence. El diagrama dibuixa aquesta costura com una aresta discontínua en lloc d’amagar-la.
Dins de domain i application, els subpaquets s’agrupen en cinc grups conceptuals. Alguns subpaquets s’ometen per claredat; la referència de l’API els enumera tots.
Grup |
Subpaquets de |
Subpaquets de |
|---|---|---|
Registre i càlculs |
|
|
Ledger i transaccions |
|
|
Presentació i exportació |
|
|
Live, portals i auth |
|
|
Perfil i emmagatzematge |
|
|
El cicle de vida del modelo¶
Un modelo és un formulari fiscal numerat de l’AEAT. Les seves dades flueixen en un sol sentit, dels teus registres a un fitxer que puges tu mateix.
flowchart LR
REC["Financial records — statements · invoices · evidence"]
ING["Ingest and classify — transactions · invoices · evidence"]
LED["Ledger / aggregation substrate — encrypted bucket · resolver mesh"]
CRE["work create"]
CAL["calculate — produces CalculationRevision (casillas)"]
VER["verify — completeness gate"]
FIL["file — local final marker"]
EXP["export — fichero-BOE file on disk"]
HUM{{"Human upload at AEAT sede — outside the tool"}}
AEAT["AEAT"]
REC --> ING --> LED
CRE --> CAL
LED --> CAL
CAL --> VER
VER --> FIL
VER --> EXP
FIL --> EXP
EXP --> HUM
HUM -.->|"manual submission"| AEAT
Alternativa textual. Els registres financers s’ingereixen i es classifiquen dins del ledger xifrat. Un operador crea la feina, la calcula, la verifica, la marca com a presentada localment i exporta un fitxer. Després, una persona puja aquest fitxer a l’AEAT fora de Cadrumo.
L’eina ingereix i classifica els registres financers (extractes bancaris, factures i les seves proves) dins del ledger xifrat. A partir d’aquí, el trajecte segueix una seqüència fixa de verbs de línia d’ordres:
aeat app modelo work createfixa una presentació a un modelo, any i període.calculateresol cada casella i desa unaCalculationRevision.verifyexecuta una comprovació d’integritat i coherència sobre l’esborrany.filemarca una revisió verificada com a presentada internament.aeat app modelo exportescriu el fitxer de pujada oficial (un artefacte fichero-BOE de format fix) al disc.
El límit és estructural. file és un marcador local, no una presentació, i export escriu un fitxer al teu disc. Cap ordre no transmet res a l’agència: la pujada és una acció humana al portal de l’AEAT, modelada com un pas més enllà del límit de l’eina. Les lectures en directe de l’operador requereixen la seva autenticació habitual i les proteccions de perfil actiu; continuen sent només de lectura. L’opció separada CADRUMO_LIVE_TESTS_ENABLED controla les proves de lectura en directe dels desenvolupadors.
Com es fonamenta una xifra¶
El valor d’una casella no és un simple nombre. Porta la seva procedència (les referències legals, les referències de font i l’identificador de fórmula que el van produir) des de la definició del registre fins a la sortida orientada a l’operador.
flowchart LR
DEF["Registry casilla definition — legal_refs · source_refs · formula_id"]
subgraph mesh["source-resolver mesh — merge_source_resolutions"]
direction TB
R1["Ledger aggregation resolvers"]
R2["Invoice catalogue resolver"]
R3["Previous-filing resolver"]
R4["Relation-prefill resolver"]
end
ENG["Registry formula engine — evaluates formula_id"]
OBS["CasillaObservation — value + legal_refs + source_refs + formula_id"]
REV["CalculationRevision.observations (+ flat casilla_values)"]
PAY["CLI payloads"]
OPS["Operator surface — JSON envelope + text"]
DEF --> ENG
DEF --> mesh
R1 --> ENG
R2 --> ENG
R3 --> ENG
R4 --> ENG
ENG --> OBS --> REV --> PAY --> OPS
Alternativa textual. Una definició de casella del registre i les entrades resoltes de ledger, factura, presentació anterior i relació alimenten el motor de fórmules. Aquest emet una observació que porta la procedència i que es converteix en una revisió de càlcul, una càrrega útil de la CLI i una sortida orientada a l’operador.
Cada binding d’una casella declara una font tipada. Una malla de resolutors (agregació del ledger, catàleg de factures, arrossegament de la presentació anterior i prefill de relació entre modelos) converteix aquestes fonts en valors de binding, fusionats mitjançant merge_source_resolutions. El motor de fórmules del registre avalua la fórmula de la casella sobre les entrades resoltes i segella el resultat com una CasillaObservation que porta els seus legal_refs, source_refs i formula_id. Aquesta observació viatja dins de la CalculationRevision persistida, després cap a les càrregues útils de la CLI i després cap a l’operador. La procedència no es perd mai en el camí de sortida.
Una vinculació és un contracte de projecció, no un espai per adjuntar dades. Identifica la família d’origen propietària, valida el selector i l’agregació i indica com aquest origen es converteix en una entrada de la presentació. Un contracte escalar produeix un valor. Les famílies de registres repetits declaren una vinculació tipada per camp de fila. Uneixen els valors mitjançant agrupació i un índex de fila que comença en u. La revisió de càlcul congela les vinculacions de fila resoltes. Les rutes que produeixen files de detall validades les emmagatzemen per separat. Mai no adjunta un bloc opac de registres a una casella.
El Model 720 demostra l’estructura repetida: el seu resolvedor d’actius estrangers està inscrit a la malla d’orígens i projecta camps d’actius tipats en vinculacions de files del registre. L’inventari d’existències és diferent. InventoryLedger és un agregat empresarial implementat i xifrat. La taxonomia d’orígens de vinculacions actualment no té cap tipus d’inventari ni cap resolvedor d’inventari inscrit. Per tant, roman fora de les revisions de càlcul dels models fins que s’afegeixi una projecció amb fonament legal.
La persistència i el límit de seguretat¶
Totes les dades financeres sensibles resideixen en un sol lloc: un magatzem xifrat per perfil. És l’estructura, no una convenció, la que les hi manté.
flowchart TD
OP(["Operator"])
subgraph boundary["Encrypted bucket-scoped store — financial data never leaves (no temp / scratch / plaintext)"]
direction TB
PROF["Active profile — resolve_active_bucket_id"]
SESS["BucketSession — KEK/DEK · idle-timed · one taxpayer at a time"]
FAC["Runtime factories — secure_object_repository_for_active_bucket"]
REPO["SecureObjectRepository — AEAD payloads · HMAC-SHA256 lookup digests"]
MK["MasterKeyProvider — Keyring / FileFallback · AES-256"]
DB[("SQLite with encrypted payload columns")]
ATT["AttachmentStore — put_bytes / read_bytes · content-addressed"]
EV["Evidence bytes — invoices · statements (FINANCIAL)"]
PROF --> SESS --> FAC --> REPO
REPO --> MK
REPO --> DB
ATT --> REPO
EV --> ATT
end
SEDE["adapters/outbound/aeat — read-only live checks"]
AEAT["AEAT"]
HUM{{"Human filing — outside the app"}}
OP --> PROF
SEDE -.->|"read-only operator live checks"| AEAT
OP --> HUM
HUM -.->|"manual submission"| AEAT
Alternativa textual. Un operador selecciona un perfil actiu. La sessió de bucket resultant obre les fàbriques d’execució i un repositori segur, que xifra les càrregues útils a la base de dades local i les proves adjuntes. Les comprovacions de l’AEAT de l’operador continuen sent només de lectura; només l’acció humana de presentació pot enviar res a l’AEAT.
Les claus de xifratge de claus (KEK) protegeixen les claus de xifratge de dades (DEK). El repositori utilitza càrregues útils de xifratge autenticat amb dades associades (AEAD) i resums de cerca HMAC-SHA256 derivats de material mestre. HMAC vol dir codi d’autenticació de missatges basat en resum (hash-based message authentication code). El xifratge és l’Advanced Encryption Standard amb claus de 256 bits (AES-256).
Treballes amb un contribuent alhora. En seleccionar un perfil s’obre una sessió de bucket, que limita un SecureObjectRepository al magatzem xifrat d’aquell contribuent. El repositori xifra cada càrrega útil al límit de la columna amb una clau del MasterKeyProvider: el clauer del sistema operatiu, o una alternativa derivada d’una contrasenya. Persisteix el text xifrat en una base de dades SQLite local amb columnes de càrrega útil xifrades. Els bytes de prova (factures, extractes i documents desxifrats) passen pel AttachmentStore adreçat per contingut, que desa els bytes mateixos, mai un enllaç.
Els bytes d’evidència (factures, extractes i documents desxifrats) passen per l”AttachmentStore adreçat per contingut, que emmagatzema els mateixos bytes, mai un enllaç.
Els registres empresarials analitzats no es converteixen en adjunts només perquè més endavant puguin donar suport a una presentació. Les transaccions, els catàlegs de factures i l’inventari d’existències continuen sent agregats tipats separats a l’emmagatzematge xifrat. El Model 720 rep, en canvi, observacions tipades d’actius estrangers del cridant. Un resolvedor d’orígens inscrit projecta en una revisió de càlcul només els camps sol·licitats pel registre. Al registre actual del Model 100, les caselles d’existències 0177, 0181 i 0182 continuen sent entrades manuals. El domini d’inventari ara divideix la variació d’obertura a tancament de 2025 entre 0177 i 0182, però aquesta projecció de domini no demostra que hi hagi una ruta activa d’inventari a Renda ni afirma encara que 0181 inclogui totes les compres.
Res d’aquest magatzem no creua cap enfora cap a l’agència com a escriptura. Les comprovacions en directe només de lectura arriben a l’agència únicament per llegir. L’única via que arriba a l’agència com a escriptura és la pujada humana, que se situa més enllà del límit de l’eina. Una prova estructural imposa l’absència de qualsevol verb d’escriptura a l’adaptador de la sede de l’AEAT, de manera que l’estat de seguretat no es pot erosionar per accident.
Com la documentació es manté fidel al codi¶
La disciplina que regeix les capes també regeix la documentació. Cadrumo manté tres superfícies de documentació en anglès a prop del codi que en proporciona els fets.
Els autors de documentació escriuen el markdown del repositori (aquesta guia i les altres dins de docs/) per a les persones que s’orienten al projecte. Els revisors tècnics comproven cada afirmació contra el codi abans que s’incorpori. Les docstrings dins del codi font proporcionen la referència de l’API, de manera que els autors no copien mai una signatura a la prosa. Els generadors basteixen les referències del codi font i de la línia d’ordres a partir del codi mateix.
Com que els generadors deriven les referències de l’API i de la línia d’ordres a partir del codi font, aquestes superfícies no poden desviar-se de manera silenciosa. Una construcció estricta tracta els avisos com a errors i falla si hi ha una referència creuada trencada, un stub que falta o una referència d’ordre que ja no coincideix amb les ordres.
Entrar al codi¶
Utilitza aquesta visió general per orientar-te, i després endinsa’t en el detall:
L’arbre de capes - navega per
src/cadrumo/core,src/cadrumo/domain,src/cadrumo/application,src/cadrumo/adaptersisrc/cadrumo/entrypointsper veure els subpaquets que anomena cada grup.La referència de la línia d’ordres - Referència de la CLI per als verbs, opcions i sortida exactes.
La referència de l’API - /api/cadrumo per a les signatures de mòdul i la llista completa de subpaquets.
La guia d’autoria del registre - Redacció i revisió de la documentació per saber com escriure el TOML que compila el pipeline.
L’explicació del pipeline - Com Cadrumo converteix els teus registres en un fitxer tributari per al mateix trajecte explicat per al contribuent.
El glossari - Glossari per a qualsevol terme (casella, modelo, fichero-BOE o justificante) que vulguis definir.
Posar-ho en marxa - Guia ràpida: produeix un fitxer de modelo per instal·lar i preparar una primera presentació.
Contribuir - el codi font del projecte i el seu gestor d’incidències són a GitHub.