Taxes
Fiscal categories and per-jurisdiction tax rules, plus the engine that resolves the rate at sale time.
Modules are installed from your Hub's marketplace. Sign up for a Hub to access this module.
Módulo taxes — categorías fiscales, reglas por jurisdicción y motor de cálculo
Capa fiscal transversal del Hub (ADR-0085). Tres cosas: las categorías canónicas
(tax_category_key, la entidad enlazable), las reglas que ponen el % por país+región+categoría
con su calificación fiscal (ADR-0186), y el motor que resuelve y calcula al vender. El resto
de módulos delega aquí el «cuánto y por qué» en vez de duplicar la lógica de IVA.
Module id:
taxes. Depende de: nada. Es dependencia deinventory,servicesysales(instalar cualquiera lo auto-instala; tier free, sin implicación de billing). Módulo híbrido: SQL + handler WASM (calculate_tax,bulk_create_rules).
Documentación de usuario — docs/
Viaja dentro del módulo y se versiona con él: el asistente del hub (ADR-0282) la indexa por versión instalada y cita la de TU versión, no la de la última publicada. En inglés (idioma fuente).
| Fichero | Para qué |
|---|---|
docs/overview.md |
Qué hace y qué NO hace; qué siembra el install (6 categorías, 14 alias, IVA ES) |
docs/screens.md |
Categories / Tax Rules / Aliases: crear categoría, regla, componente multi-impuesto y alias |
docs/concepts.md |
Categoría vs regla, raíz vs componente, calificación fiscal, igic/ipsi son FAMILIA, snapshot inmutable, bruto→base por diferencia |
docs/limits.md |
no_rate no es 0 %, caps, permisos por acción y diagnóstico de «no encuentra la regla» |
Qué expone hoy
| Tipo | Nombre | Permiso |
|---|---|---|
| query | taxes.categories.list / .get |
taxes.view_tax |
| query | taxes.rules.list / .by_country / .status |
taxes.view_tax |
| query | taxes.aliases.list / .resolve |
taxes.view_tax |
| command | taxes.categories.create |
taxes.manage_tax |
| command | taxes.rules.create / .bulk_create (WASM) / .deactivate |
taxes.manage_tax |
| command | taxes.aliases.create |
taxes.manage_tax |
| command | taxes.calculate (WASM, cálculo puro) |
taxes.calculate_tax |
| emite | taxes.category.created / .rule.created / .rule.deactivated / .alias.created |
— |
| escucha | — | — |
Navegación: erp-taxes-categories, erp-taxes-rules, erp-taxes-aliases. Sin bloque settings
(la identidad fiscal del hub vive en hub_settings, ADR-0061).
🧩 ADR-0223: la resolución de la regla (
resolve_root, componentes, calificación) vive UNA vez enerplora_guest_sdk::tax, no en este módulo. Si diverge, se cobra una cosa y se declara otra.
Layout
module.json # manifest (contrato técnico)
migrations/postgres/ # esquema §2.5 (hub_id + soft-delete + auditoría)
seed/install.postgres.sql # DML idempotente: categorías canónicas + alias + IVA ES
queries/*.sql # lecturas declarativas (:hub_id inyectado)
commands/*.sql # escrituras declarativas (las `_` son intenciones del WASM)
schemas/*.json # JSON Schemas de input (draft 2020-12)
handler/ # WASM Tier 2 → dist/handler.wasm
ui/ # Web Components (Lit/Ionic/OutfitKit)
docs/ # documentación de usuario + corpus del asistente
Estado y trabajo abierto
El estado vive en las Issues de este repo, no aquí.
Doc de arquitectura: architecture/modules/taxes.md (cargarlo antes de tocar el módulo).
Sign in to leave a review
Share your experience with this module
User Reviews
No reviews yet
Be the first to review this module
chore(release): v2.3.12
chore(release): v2.3.11
chore(release): v2.3.10
chore(release): v2.3.9
chore(release): v2.3.8
chore(release): v2.3.7
chore(release): v2.3.6
chore(release): v2.3.5
chore(release): v2.3.4
chore(release): v2.3.3
chore(release): v2.3.2
chore(release): v2.3.1
feat(migrations): índice único por clave natural en taxes_rule + dedupe previo (hub#576) — v2.3.0 (#31) * docs: documentación completa del módulo (pm#91) * feat(migrations): índice ÚNICO por clave natural en taxes_rule (raíces, NULLS NOT DISTINCT) + dedupe (hub#576) La semilla y el backfill siempre asumieron la clave (hub, país, categoría, región, valid_from) sobre reglas RAÍZ, pero nada la imponía — y por eso el export del hub excluía taxes_rule del blueprint (el guard por id no reconoce la misma regla venida de otro hub; duplicación silenciosa del lookup de IVA). - 004: primero DEDUPE (sobrevive la fila más antigua por created_at,id; el resto se soft-borra, nunca se pierde) y después el índice parcial (parent_id IS NULL AND is_deleted = 0) con NULLS NOT DISTINCT — región NULL = todo el país y valid_from NULL = desde siempre SON la misma jurisdicción. - Los COMPONENTES quedan fuera a propósito: varios bajo una raíz comparten jurisdicción y categoría por diseño (taxes#9). - Un cambio de tipo programado (valid_from más nuevo) sigue siendo legal: el resolutor elige el más reciente. - tests/natural-key.postgres.test.sh fija el contrato entero (TDD, rojo antes de 004): dedupe, rechazo de duplicados con NULLs, componentes, soft-delete, idempotencia. - Bump 2.2.6 → 2.3.0 (la publicación la hace operación).
chore(release): v2.2.6
chore(release): v2.2.5
chore(release): v2.2.4
chore(release): v2.2.3
chore(release): v2.2.2
chore(release): v2.2.1
feat(taxes): una regla fiscal sabe decir si la operación está sujeta, exenta o no sujeta (#17) Una regla solo sabía decir CUÁNTO se repercute (`rate_pct`) y de qué familia es el impuesto (`tax_type`). Le faltaba la otra mitad, la que un registro fiscal tiene que declarar: si la operación está **sujeta**, **exenta** o **no sujeta**, bajo qué **régimen** y —cuando es exenta— por qué **causa**. Sin ese dato, el módulo de compliance no tenía de dónde sacarlo y lo declaraba todo como venta nacional sujeta y no exenta: un tratamiento sanitario salía como «sujeto al 0 %» (que no es lo mismo que exento) y una venta intracomunitaria repercutía IVA español. Tres columnas nuevas en `taxes_rule`: - `operation_class` — subject | subject_reverse | exempt | not_subject | not_subject_location. Lista **cerrada**: `bulk_create` RECHAZA la línea en vez de caer al default en silencio, porque una calificación mal escrita que se guardara como `subject` declararía a Hacienda lo contrario de lo que el usuario quiso decir. - `exempt_reason` — la causa, en el vocabulario de la jurisdicción. - `regime_key` — el régimen, ídem. Los dos últimos viajan **opacos**: este módulo los guarda y los devuelve, no los interpreta. Quien los traduce a XML es el módulo de compliance del país — `taxes` es multi-país y no puede llenarse de códigos de la AEAT. Vive en la REGLA y no en la categoría porque la regla ya está indexada por `(país, región, categoría, vigencia)`, que es exactamente la tupla en la que este dato cambia: un tratamiento sanitario está exento en España (art. 20.Uno.3º de la Ley 37/1992) y no tiene por qué estarlo en otra jurisdicción. La categoría sigue siendo la clave abstracta enlazable de ADR-0085. Los DEFAULT (`subject`, régimen general) hacen que las reglas ya creadas sigan significando exactamente lo que significaban. Ninguna factura ya emitida cambia de interpretación — están encadenadas en la huella fiscal. `calculate_tax` devuelve la calificación en el snapshot (`tax_kind`, `tax_operation_class`, `tax_regime_key`, `tax_exempt_reason`), siempre de la regla RAÍZ: el recargo de equivalencia es un componente que aporta cuota sobre la misma base, no una operación distinta. El seed añade `service.health` y `service.education` como categorías exentas de IVA en España (E1). Son categorías APARTE, no un cambio de las que ya existen: la exención es de la prestación, no del negocio — un corte de pelo sigue al 21 %. NO se siembran tipos de IGIC ni de IPSI: el módulo ya sabe expresarlos, pero el tipo concreto por categoría depende del negocio y sembrar un número inventado es peor que no sembrar ninguno. Contrato consumido por `invoice` (productor del desglose) y por el crate `verifactu` del hub (que lo traduce a `Impuesto`/`ClaveRegimen`/`CalificacionOperacion`). Refs ERPlora/hub#292