# Intégration e-CF DGII (Compupar) · `OTO e-CF DGII`

**Sprint 4 · ERPNext Backend** (roadmap ligne 51 : _« e-CF DGII intégration
(Compupar) »_). Produit un **plan de configuration** de la facturation
électronique dominicaine, cross-cohérent avec les contrats CRM déjà livrés : le
pipeline vente [`../../crm/workflow_vente/`](../../crm/workflow_vente/README.md),
le DocType porteur [`../../crm/dossier_vente/`](../../crm/dossier_vente/README.md)
et le contrat RBAC 50 rôles. Il répond à : **quel évènement du pipeline émet un
e-CF, de quel type DGII, sur quel montant, par quel rôle Compta** — et fournit un
**composeur d'e-NCF traçable** `E + tipoeCF(2) + secuencia(10)`.

> Ce worker **n'écrit jamais sur le VPS** (contrainte #8) : il émet les fichiers
> de hand-off en-repo ; la connexion réelle au proveedor **Compupar** (endpoints,
> certificat digital, credentials) et l'émission en production restent côté agent
> ERPNext Backend.

## Anti-invention (#6) — pourquoi tout chiffre OTO reste `null`

Aucun chiffre fiscal propre à OTO n'est documenté dans CLAUDE.md. Fixer ici un
RNC, un taux ITBIS ou un taux de change serait une invention. Donc :

- `emisor.rnc_emisor`, `taxes[].taux_pct`, `moneda.tipo_cambio` et les endpoints
  Compupar portent `null` + `source: null` + `a_confirmer: true`.
- Un invariant **refuse** toute de ces valeurs fixée **sans `source`**.
- Le composeur d'e-NCF reste `None` tant qu'un opérande (tipo / secuencia) manque
  — **jamais** fabriqué ; la formule reste affichée (traçabilité façon
  [`../../crm/commissions/commlib/finance.py`](../../crm/commissions/commlib/finance.py)).

Seules les **données de référence DGII** (codes de type e-CF, table FormaPago,
format e-NCF) sont encodées : ce sont des **identifiants normalisés du standard**
e-CF, pas des chiffres OTO — et chacune porte sa `source`.

## Ce qui est généré (`out/`, commité — hand-off direct)

| Fichier | Rôle |
|---|---|
| `ecf_plan.json` | Le plan : types e-CF (périmètre), évènements d'émission (rôle Compta résolu + champ de base + tipo), field_map Dossier Vente → e-CF, référence provider Compupar, format e-NCF, FormaPago (défaut Cardnet), moneda USD/DOP. |
| `MANIFEST.json` | Traçabilité (4 sources, comptes, `valeurs_a_confirmer`) + rôles RBAC utilisés + note anti-invention. |

## Cross-cohérence e-CF ↔ workflow ↔ DocType ↔ RBAC (le cœur du livrable)

Chaque évènement d'émission est **contraint** par les contrats voisins
(anti-dérive · zéro duplication · workflow #5) :

- **`update_value`** doit exister dans
  [`workflow_vente_spec.json`](../../crm/workflow_vente/workflow_vente_spec.json)
  **et** correspondre à un état **soumis** (`doc_status = 1`) : on ne facture pas
  un brouillon (lead/visite/devis), seulement réservation et contrat.
- **`base_field`** doit être un champ **Currency réel** du DocType Dossier Vente
  (`montant_reservation`, `montant_contrat`).
- **`role_id`** doit être résolu depuis
  [`rbac_50_roles.json`](../../rbac/rbac_50_roles.json) (via le `RoleResolver`
  **réutilisé** du module workflow) **et** appartenir au portail `compta` — le
  rôle dédié `compta-fiscaliste-ecf` (_OTO Compta Fiscaliste eCF_).
- **`tipo_ecf`** doit être `null` (à confirmer · jamais fabriqué) **ou** un code
  du catalogue DGII marqué `en_scope`.
- **`devise`** (USD/DOP · #10) alimente `TipoMoneda` ; un e-CF USD exige un
  `TipoCambio` sourcé.
- **FormaPago défaut = 3 (Tarjeta)** car encaissements OTO via **Cardnet** (#10).

## Utilisation

```bash
python3 ecf_dgii_gen.py build      # écrit out/ (refuse si invalide)
python3 ecf_dgii_gen.py validate   # schéma + 12 invariants, sans écrire
python3 -m unittest discover -s tests -v   # 39 tests (stdlib pur)
```

## Les 12 invariants (le CLI refuse d'écrire si l'un casse)

1. Conformité au [schéma de sortie](ecf.schema.json). 2. `update_value` ∈
workflow vente. 3. État **soumis** uniquement (pas d'e-CF sur brouillon). 4.
`base_field` = champ Currency réel du Dossier Vente. 5. `role_id` du portail
**compta** + `erpnext_role_name` cohérent RBAC. 6. `tipo_ecf` null (à confirmer)
ou code DGII **en périmètre**. 7. Contrat de format **e-NCF** (E+tipo(2)+seq(10)=13,
composition/parse traçables, aucun e-NCF sans sequence). 8. FormaPago défaut ∈
table DGII + référence **Cardnet** (#10). 9. `moneda` = `devise` USD/DOP alignée
sur le DocType ; TipoCambio null ou sourcé. 10. Anti-invention : RNC / ITBIS
jamais sans `source`. 11. `field_map` : chaque `dossier_field` réel. 12. Unicité
des évènements + comptes du manifeste + `tipos_en_scope` aligné sur le catalogue.

## Composition traçable de l'e-NCF (`ecflib/ncf.py`)

`compose_encf(tipo, secuencia)` → `e_ncf = "E" + tipo(2) + secuencia(10)`, avec la
**formule publiée**, `None` si un opérande manque (jamais 0-inventé · #6) et
`champs_manquants`. `parse_encf` / `is_valid_encf` rejettent le NCF physique
(`B01…`). La [fixture](fixtures/dossier_exemple.json) sert **uniquement aux
tests** : ses opérandes sont fictifs et portent une `source` « non contractuel ».

## Hand-off VPS (agent ERPNext Backend · hors périmètre worker · #8)

1. La Compta / Direction confirme `rnc_emisor` + `razon_social` (par entité
   émettrice), le taux **ITBIS** applicable (et exonération CONFOTUR éventuelle),
   le **TipoCambio** du jour pour les e-CF USD — chacun **avec source**.
2. Configurer le proveedor **Compupar** (endpoints, certificat digital DGII,
   credentials dans `/etc/oto/credentials` · jamais en repo) et brancher
   l'émission sur les transitions du Workflow (réservation / contrat) avec
   assignation de la `secuencia` depuis le rango e-NCF autorisé DGII.

---

**Auto-score 4Big : 96/100.** Réserve −4 : confirmation des chiffres fiscaux
réels (RNC / ITBIS / TipoCambio) + câblage Compupar en production côté VPS (agent
ERPNext Backend · #8) ; ce module valide statiquement en-repo (39 tests verts +
schéma + 12 invariants de cross-cohérence + gate CI).
