invoicehn 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +59 -0
- data/LICENSE.txt +21 -0
- data/README.md +430 -0
- data/config/locales/en.yml +89 -0
- data/config/locales/es.yml +87 -0
- data/exe/invoicehn +7 -0
- data/lib/invoicehn/authorization.rb +144 -0
- data/lib/invoicehn/cli/builder.rb +88 -0
- data/lib/invoicehn/cli/main.rb +233 -0
- data/lib/invoicehn/cli/reporter.rb +122 -0
- data/lib/invoicehn/cli/wizard.rb +234 -0
- data/lib/invoicehn/cli.rb +16 -0
- data/lib/invoicehn/compliance/validator.rb +222 -0
- data/lib/invoicehn/compliance/violation.rb +42 -0
- data/lib/invoicehn/config.rb +50 -0
- data/lib/invoicehn/correlative.rb +144 -0
- data/lib/invoicehn/customer.rb +188 -0
- data/lib/invoicehn/errors.rb +39 -0
- data/lib/invoicehn/exchange_rate.rb +108 -0
- data/lib/invoicehn/invoice.rb +165 -0
- data/lib/invoicehn/issuance.rb +124 -0
- data/lib/invoicehn/issuer.rb +95 -0
- data/lib/invoicehn/ledger.rb +107 -0
- data/lib/invoicehn/line_item.rb +120 -0
- data/lib/invoicehn/locale.rb +77 -0
- data/lib/invoicehn/money.rb +177 -0
- data/lib/invoicehn/renderers/json.rb +77 -0
- data/lib/invoicehn/renderers/pdf.rb +226 -0
- data/lib/invoicehn/renderers/text.rb +251 -0
- data/lib/invoicehn/rtn.rb +70 -0
- data/lib/invoicehn/sequence.rb +126 -0
- data/lib/invoicehn/spanish_numerals.rb +123 -0
- data/lib/invoicehn/storage/json_store.rb +154 -0
- data/lib/invoicehn/tax_summary.rb +129 -0
- data/lib/invoicehn/tax_treatment.rb +109 -0
- data/lib/invoicehn/version.rb +5 -0
- data/lib/invoicehn.rb +40 -0
- metadata +161 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 633195e813e47b48266e5c5dd70026b7fb22f3a3c839d53a9c79e37cb4861cd9
|
|
4
|
+
data.tar.gz: 4970c4b593fb55f38ee90819b063444664bb07e0a00bc8b039f0e1b4d6c340c3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fbcecd6f82d560df0dd182e1cd0d57439a3c2c4f4de2cf5ac2980e838ad2746831d5775d54c894a2e474113f67ed7096610cb1f1a184aa2a0512851deab6fb40
|
|
7
|
+
data.tar.gz: 52a36d88796c7699fd29d193df20a16fb85651da782963bdd243e38557b6a73221478ce25b711083fdea4fa19d3535cfbfbe4d53090670d387dba70435f20a9d
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/).
|
|
4
|
+
Este proyecto sigue [Versionado Semántico](https://semver.org/lang/es/).
|
|
5
|
+
|
|
6
|
+
## [Sin publicar]
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-08-28
|
|
9
|
+
|
|
10
|
+
Primera versión. Emisión de Facturas (Comprobante Fiscal tipo 01) conforme al
|
|
11
|
+
Reglamento del Régimen de Facturación, Acuerdo 481-2017 y sus reformas
|
|
12
|
+
(609-2017, 725-2018 y 817-2018).
|
|
13
|
+
|
|
14
|
+
### Añadido
|
|
15
|
+
|
|
16
|
+
- **Requisitos del formato (Art. 10)**: datos de identificación del emisor,
|
|
17
|
+
denominación "Factura", CAI, fecha límite de emisión, rango autorizado,
|
|
18
|
+
destino de los ejemplares, correlativo de 16 dígitos
|
|
19
|
+
`NNN-NNN-NN-NNNNNNNN`, datos del adquirente exonerado, y la línea de
|
|
20
|
+
descuentos y rebajas del numeral 10.
|
|
21
|
+
- **Requisitos al momento de la emisión (Art. 11)**: identificación del cliente
|
|
22
|
+
según su tipo, discriminación de valores exentos, exonerados y gravados,
|
|
23
|
+
subtotales e impuestos por tarifa, descuentos, denominación de la moneda,
|
|
24
|
+
importe total en números y letras, umbral de L 10,000.00 para consumidores
|
|
25
|
+
finales, y tasa de cambio vigente en facturas en moneda extranjera.
|
|
26
|
+
- **Redondeo estatutario** del Artículo 9 de la Ley del Impuesto Sobre Ventas:
|
|
27
|
+
medio hacia arriba en 0.005 de Lempira. `Float` está prohibido en todo el
|
|
28
|
+
camino del dinero, con una prueba que recorre el código fuente y falla si
|
|
29
|
+
aparece.
|
|
30
|
+
- **Los descuentos reducen la base gravable**, conforme al Artículo 3 de la Ley
|
|
31
|
+
del ISV.
|
|
32
|
+
- **Tasas del ISV** del Decreto 278-2013: 15% general y 18% especial.
|
|
33
|
+
- **Control de autorizaciones**: un identificador acumula autorizaciones; el
|
|
34
|
+
asignador elige la vigente que cubra el correlativo siguiente. La emisión se
|
|
35
|
+
bloquea si la autorización venció (Art. 62) o el número queda fuera del rango.
|
|
36
|
+
- **Numeración sin huecos ni reutilización**, bajo candado exclusivo de archivo;
|
|
37
|
+
la asignación y el guardado ocurren en un solo paso atómico.
|
|
38
|
+
- **Anulación (Art. 41)** que conserva el correlativo consumido.
|
|
39
|
+
- **Libro contable (Art. 53 num. 1)** con interfaz `Invoicehn::Ledger` e
|
|
40
|
+
implementación JSONL incluida, y exportación en texto (Art. 53 num. 5).
|
|
41
|
+
- **Interfaz de terminal** con subcomandos y asistente interactivo, en español o
|
|
42
|
+
inglés. El documento fiscal se emite siempre en español.
|
|
43
|
+
- **Salidas** en texto, PDF y JSON/CSV.
|
|
44
|
+
|
|
45
|
+
### Notas
|
|
46
|
+
|
|
47
|
+
- La gema cubre el contenido del documento. La inscripción al Régimen de
|
|
48
|
+
Facturación (Art. 45), la inscripción como autoimpresor (Art. 47), la
|
|
49
|
+
Declaración Jurada del sistema computarizado (Art. 53) y la obtención del CAI,
|
|
50
|
+
el rango y la fecha límite (Arts. 59-61) siguen siendo obligación del obligado
|
|
51
|
+
tributario.
|
|
52
|
+
- El RTN se valida por longitud y dígitos únicamente: no existe un dígito
|
|
53
|
+
verificador documentado públicamente. El CAI se almacena e imprime tal cual,
|
|
54
|
+
porque ninguna norma fija su formato.
|
|
55
|
+
- La leyenda "La factura es beneficio de todos, exíjala" no está exigida por
|
|
56
|
+
ninguna norma; se admite como texto opcional.
|
|
57
|
+
|
|
58
|
+
[Sin publicar]: https://github.com/JorgePadilla/invoicehn/compare/v0.1.0...HEAD
|
|
59
|
+
[0.1.0]: https://github.com/JorgePadilla/invoicehn/releases/tag/v0.1.0
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jorge Padilla
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# invoicehn
|
|
2
|
+
|
|
3
|
+
[](https://github.com/JorgePadilla/invoicehn/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/invoicehn)
|
|
5
|
+
|
|
6
|
+
Facturación para Honduras conforme al **Reglamento del Régimen de Facturación,
|
|
7
|
+
Otros Documentos Fiscales y Registro Fiscal de Imprentas** (Acuerdo No. 481-2017
|
|
8
|
+
y sus reformas). Biblioteca Ruby más una interfaz de terminal.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
invoicehn setup # datos del emisor (Art. 10 num. 1)
|
|
12
|
+
invoicehn auth add # el CAI, el rango y la fecha límite que otorga el SAR
|
|
13
|
+
invoicehn new # emitir una factura (asistente interactivo)
|
|
14
|
+
invoicehn check # ¿se puede emitir ahora mismo?
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Lo que esta gema hace y lo que no
|
|
20
|
+
|
|
21
|
+
Esto es lo primero que debe quedar claro, porque el software sólo cubre una mitad
|
|
22
|
+
de lo que la ley exige.
|
|
23
|
+
|
|
24
|
+
**Cubre el contenido del documento.** Todos los campos que exigen los Artículos
|
|
25
|
+
10 (requisitos del formato) y 11 (requisitos al momento de la emisión): el
|
|
26
|
+
correlativo de 16 dígitos, el control del CAI, del rango autorizado y de la fecha
|
|
27
|
+
límite de emisión, la discriminación del ISV por tarifa, los descuentos, el total
|
|
28
|
+
en números y letras, y el redondeo estatutario.
|
|
29
|
+
|
|
30
|
+
**No otorga autorización.** Siguen siendo obligación del obligado tributario:
|
|
31
|
+
|
|
32
|
+
| Obligación | Artículo |
|
|
33
|
+
|---|---|
|
|
34
|
+
| Inscribirse en el Régimen de Facturación | Art. 45 |
|
|
35
|
+
| Inscribirse como autoimpresor | Art. 47 |
|
|
36
|
+
| Presentar la Declaración Jurada del sistema computarizado | Art. 53 |
|
|
37
|
+
| Obtener el CAI, el rango autorizado y la fecha límite | Arts. 59-61 |
|
|
38
|
+
| Comunicar los documentos no utilizados | Art. 42 |
|
|
39
|
+
| Custodiar los documentos por el plazo de prescripción | Art. 43 |
|
|
40
|
+
|
|
41
|
+
La gema **consume** el CAI, el rango y la fecha límite; nunca los inventa.
|
|
42
|
+
Una factura generada aquí sin una autorización real del SAR no es un documento
|
|
43
|
+
fiscal válido.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Instalación
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Gemfile
|
|
51
|
+
gem "invoicehn"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
o `gem install invoicehn`. Requiere Ruby >= 3.1.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Uso desde la terminal
|
|
59
|
+
|
|
60
|
+
### 1. Configurar el emisor
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
invoicehn setup
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Pide los siete datos del Art. 10 num. 1: RTN, nombre o razón social, nombre
|
|
67
|
+
comercial, dirección de la casa matriz, dirección del establecimiento del punto
|
|
68
|
+
de emisión, teléfono y correo electrónico. Todos son obligatorios; la emisión se
|
|
69
|
+
bloquea si falta alguno.
|
|
70
|
+
|
|
71
|
+
También acepta banderas, para instalaciones desatendidas:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
invoicehn setup --rtn 08011990123456 \
|
|
75
|
+
--legal-name "Comercial Ejemplo, S. de R.L." \
|
|
76
|
+
--trade-name "Ferretería Ejemplo" \
|
|
77
|
+
--address "Col. Palmira, Tegucigalpa M.D.C." \
|
|
78
|
+
--phone "2222-3333" --email "facturacion@ejemplo.hn"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. Registrar la autorización del SAR
|
|
82
|
+
|
|
83
|
+
Copie los valores del documento de autorización:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
invoicehn auth add --cai "ABCD12-345678-9ABCDE-F01234-567890-AB" \
|
|
87
|
+
--from "000-001-01-00000001" --to "000-001-01-00000500" \
|
|
88
|
+
--limit "2027-06-30"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Un mismo identificador acumula autorizaciones con el tiempo: cuando un rango se
|
|
92
|
+
agota y el SAR concede otro que continúa la numeración, se registra el nuevo sin
|
|
93
|
+
borrar el anterior. El asignador elige la autorización vigente que cubra el
|
|
94
|
+
correlativo que sigue.
|
|
95
|
+
|
|
96
|
+
### 3. Emitir
|
|
97
|
+
|
|
98
|
+
Interactivo:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
invoicehn new
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
O desde un archivo, para integrarlo con otro sistema:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"customer": {
|
|
109
|
+
"kind": "taxpayer",
|
|
110
|
+
"name": "Distribuidora del Norte, S.A.",
|
|
111
|
+
"rtn": "05019005123456"
|
|
112
|
+
},
|
|
113
|
+
"items": [
|
|
114
|
+
{ "description": "Cemento gris bolsa 42.5 kg", "quantity": 20,
|
|
115
|
+
"unit_price": "235.00", "discount": "200.00", "treatment": "gravado_15" },
|
|
116
|
+
{ "description": "Cerveza nacional caja 24 unidades", "quantity": 3,
|
|
117
|
+
"unit_price": "480.00", "treatment": "gravado_18" },
|
|
118
|
+
{ "description": "Medicamento esencial", "quantity": 10,
|
|
119
|
+
"unit_price": "12.50", "treatment": "exento" }
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
invoicehn issue -f venta.json
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 4. Consultar, anular, exportar
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
invoicehn show 000-001-01-00000001 # texto
|
|
132
|
+
invoicehn show 000-001-01-00000001 -f json # JSON
|
|
133
|
+
invoicehn pdf 000-001-01-00000001 -o f.pdf
|
|
134
|
+
invoicehn list --from 2026-01-01 --to 2026-12-31
|
|
135
|
+
invoicehn annul 000-001-01-00000002 -r "Error en la cantidad"
|
|
136
|
+
invoicehn export --format csv -o ventas.csv # Art. 53 num. 5
|
|
137
|
+
invoicehn check
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Idioma
|
|
141
|
+
|
|
142
|
+
La interfaz está en español por defecto y en inglés con `--lang en` o
|
|
143
|
+
`INVOICEHN_LANG=en`. **El documento fiscal siempre se emite en español**: sus
|
|
144
|
+
leyendas ("FACTURA", "Original: Cliente", "CONSUMIDOR FINAL", "ANULADA") son
|
|
145
|
+
texto legal fijado por el Reglamento, no texto de interfaz, y viven como
|
|
146
|
+
constantes fuera del catálogo de traducciones.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Uso como biblioteca
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
require "invoicehn"
|
|
154
|
+
|
|
155
|
+
emisor = Invoicehn::Issuer.new(
|
|
156
|
+
rtn: "08011990123456",
|
|
157
|
+
legal_name: "Comercial Ejemplo, S. de R.L.",
|
|
158
|
+
trade_name: "Ferretería Ejemplo",
|
|
159
|
+
headquarters_address: "Col. Palmira, Tegucigalpa M.D.C.",
|
|
160
|
+
phone: "2222-3333",
|
|
161
|
+
email: "facturacion@ejemplo.hn"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
autorizacion = Invoicehn::Authorization.new(
|
|
165
|
+
cai: "ABCD12-345678-9ABCDE-F01234-567890-AB",
|
|
166
|
+
range_start: "000-001-01-00000001",
|
|
167
|
+
range_end: "000-001-01-00000500",
|
|
168
|
+
limit_date: Date.new(2027, 6, 30)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
factura = Invoicehn::Invoice.new(
|
|
172
|
+
correlative: "000-001-01-00000042",
|
|
173
|
+
issuer: emisor,
|
|
174
|
+
customer: Invoicehn::Customer::ConsumidorFinal.new,
|
|
175
|
+
authorization: autorizacion,
|
|
176
|
+
line_items: [
|
|
177
|
+
Invoicehn::LineItem.new(
|
|
178
|
+
description: "Servicio de consultoría",
|
|
179
|
+
quantity: 2,
|
|
180
|
+
unit_price: Invoicehn::Money.new("1500.00"),
|
|
181
|
+
treatment: :gravado_15
|
|
182
|
+
)
|
|
183
|
+
]
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
factura.validate! # lanza ComplianceError con la lista
|
|
187
|
+
puts factura.total # => L 3,450.00
|
|
188
|
+
puts factura.total_in_words # => TRES MIL CUATROCIENTOS ... CON 00/100
|
|
189
|
+
puts Invoicehn::Renderers::Text.new(factura).render
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Para emitir con numeración y persistencia, use el servicio:
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
issuance = Invoicehn::Issuance.new
|
|
196
|
+
factura = issuance.issue(customer: cliente, line_items: lineas)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Decisiones que conviene conocer
|
|
202
|
+
|
|
203
|
+
### El redondeo es estatutario, y equivocarse es delito
|
|
204
|
+
|
|
205
|
+
**Ley del Impuesto Sobre Ventas (Decreto-Ley 24), Artículo 9**, redactado por el
|
|
206
|
+
Decreto 135-94:
|
|
207
|
+
|
|
208
|
+
> Cuando al calcular dicho gravamen, resulte una fracción menor de 0.005 de
|
|
209
|
+
> Lempira, deberá reducirse el recargo hasta la cifra de centavos próxima
|
|
210
|
+
> inferior, en cambio, si la fracción citada es igual o mayor de 0.005 de
|
|
211
|
+
> Lempira, entonces podrá subirse el cómputo hasta la cifra de centavos próxima
|
|
212
|
+
> superior. **El recargo del impuesto al consumidor fuera de la regla establecida
|
|
213
|
+
> en el párrafo anterior, se considerará como hurto.**
|
|
214
|
+
|
|
215
|
+
Por eso todo importe es `BigDecimal` y **`Float` está prohibido** en el camino
|
|
216
|
+
del dinero: `Money.new(10.5)` lanza una excepción. Una prueba recorre el código
|
|
217
|
+
fuente y falla si aparece `Float` o `to_f` fuera de su propio rechazo.
|
|
218
|
+
|
|
219
|
+
### El ISV se calcula sobre el subtotal de cada tarifa
|
|
220
|
+
|
|
221
|
+
El Art. 11 num. 1 literales h) e i) exigen mostrar *subtotales por tarifa* y
|
|
222
|
+
*impuestos por tarifa*. Redondear línea por línea y sumar puede producir un total
|
|
223
|
+
de ISV que no coincida con la tarifa aplicada al subtotal impreso — justo la
|
|
224
|
+
discrepancia que un auditor cuestionaría. La norma no resuelve el punto (el
|
|
225
|
+
Art. 9 de la Ley del ISV habla del recargo "sobre el precio del artículo vendido
|
|
226
|
+
o servicio prestado", que se lee por ítem), así que la lectura elegida está
|
|
227
|
+
documentada en el código y cubierta por pruebas.
|
|
228
|
+
|
|
229
|
+
### Los descuentos reducen la base gravable
|
|
230
|
+
|
|
231
|
+
**Ley del ISV, Artículo 3**: *"No forman parte de la base gravable los descuentos
|
|
232
|
+
efectivos que consten en la factura o documento equivalente, siempre que resulten
|
|
233
|
+
normales según la costumbre comercial."* Mostrar el descuento pero gravar el valor
|
|
234
|
+
bruto le cobraría de más al cliente.
|
|
235
|
+
|
|
236
|
+
La línea de descuentos aparece siempre, con o sin descuento: el Acuerdo 725-2018
|
|
237
|
+
la agregó como **Art. 10 num. 10** ("Descuentos y rebajas otorgados"), es decir
|
|
238
|
+
como requisito **del formato**, además de los literales l) y j) del Art. 11.
|
|
239
|
+
|
|
240
|
+
### El CAI y el RTN se validan con prudencia
|
|
241
|
+
|
|
242
|
+
El Art. 4 num. 7 define el CAI únicamente como *"una serie alfanumérica generada
|
|
243
|
+
electrónicamente"*. Ninguna norma fija su longitud ni su agrupación, y el SAR no
|
|
244
|
+
publica un formato. Se almacena e imprime **tal cual**.
|
|
245
|
+
|
|
246
|
+
El RTN son 14 dígitos, para personas naturales y jurídicas. **No existe un dígito
|
|
247
|
+
verificador documentado públicamente**: la palabra "dígito" no aparece en el
|
|
248
|
+
Código Tributario, el Acuerdo 481-2017 exige el RTN sin especificar su
|
|
249
|
+
estructura, y el SAR no publica algoritmo alguno. Validar contra un esquema
|
|
250
|
+
adivinado — un módulo 11 tomado del RUT chileno o del RFC mexicano — rechazaría
|
|
251
|
+
RTN reales. Se valida longitud y dígitos, nada más.
|
|
252
|
+
|
|
253
|
+
### La numeración no se reutiliza ni se salta
|
|
254
|
+
|
|
255
|
+
El asignador se lleva por la terna (establecimiento, punto de emisión, tipo de
|
|
256
|
+
documento) — el *identificador del documento* del Art. 10 num. 7 — y trabaja bajo
|
|
257
|
+
un candado exclusivo de archivo, de modo que dos procesos simultáneos no pueden
|
|
258
|
+
repetir ni omitir un número. La asignación y el guardado ocurren dentro del mismo
|
|
259
|
+
candado: si el guardado falla, el correlativo no se consume.
|
|
260
|
+
|
|
261
|
+
Un documento emitido es inmutable. La única corrección es la anulación
|
|
262
|
+
(Art. 41), que marca el registro con la leyenda **ANULADA** y **conserva el
|
|
263
|
+
correlativo consumido**.
|
|
264
|
+
|
|
265
|
+
### La fecha de emisión es la del sistema
|
|
266
|
+
|
|
267
|
+
No hay bandera para retrofechar. El Art. 43 obliga a custodiar los documentos en
|
|
268
|
+
orden cronológico, y un libro cuya numeración no concuerde con sus fechas es lo
|
|
269
|
+
primero que una auditoría cuestiona.
|
|
270
|
+
|
|
271
|
+
### La tasa del 18% está redactada de forma ambigua
|
|
272
|
+
|
|
273
|
+
El Decreto 278-2013 Art. 16 fija la tasa general en 15% y en 18% la de *"las
|
|
274
|
+
bebidas alcohólicas, cerveza y cigarrillos al igual que los boletos aéreos de
|
|
275
|
+
clase ejecutiva"*. La enumeración del Art. 6 que reforma agrega *"otros productos
|
|
276
|
+
elaborados de tabaco"*, y las guías tributarias incluyen además primera clase.
|
|
277
|
+
Decidir si un producto concreto califica es del obligado tributario; la gema
|
|
278
|
+
calcula una vez tomada esa decisión.
|
|
279
|
+
|
|
280
|
+
### Lo que NO es obligatorio, pese a lo que se repite
|
|
281
|
+
|
|
282
|
+
La leyenda **"La factura es beneficio de todos, exíjala"** **no** está exigida por
|
|
283
|
+
ninguna norma. Se verificó su ausencia en el texto completo del Acuerdo 481-2017,
|
|
284
|
+
en su texto consolidado y en su antecesor el Acuerdo 189-2014. Se admite como
|
|
285
|
+
texto opcional, nunca como regla de cumplimiento.
|
|
286
|
+
|
|
287
|
+
Las leyendas que sí exige el Reglamento son `CONSUMIDOR FINAL` (Art. 11 num. 2
|
|
288
|
+
lit. a), `ANULADA` (Art. 41) y el destino de los ejemplares (Art. 10 num. 6).
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Matriz de cumplimiento
|
|
293
|
+
|
|
294
|
+
Cada fila corresponde a pruebas automatizadas.
|
|
295
|
+
|
|
296
|
+
| Artículo | Requisito | Implementado en |
|
|
297
|
+
|---|---|---|
|
|
298
|
+
| 10 num. 1 | Datos de identificación del emisor | `Issuer` |
|
|
299
|
+
| 10 num. 2 | Denominación "Factura" | `Renderers::Text` |
|
|
300
|
+
| 10 num. 3-5 | CAI, fecha límite y rango vigentes | `Authorization` |
|
|
301
|
+
| 10 num. 6 | Destino de los ejemplares | `Renderers::Text` |
|
|
302
|
+
| 10 num. 7 | Correlativo de 16 dígitos | `Correlative` |
|
|
303
|
+
| 10 num. 8 | Datos del adquirente exonerado | `Customer::Exonerado` |
|
|
304
|
+
| 10 num. 10 | Descuentos y rebajas (formato) | `Renderers::Text` |
|
|
305
|
+
| 11 num. 1 | Requisitos para crédito fiscal | `Compliance::Validator` |
|
|
306
|
+
| 11 num. 1 g-i | Discriminación por tratamiento y tarifa | `TaxSummary` |
|
|
307
|
+
| 11 num. 1 k | Importe total en números y letras | `SpanishNumerals` |
|
|
308
|
+
| 11 num. 1 l / 2 j | Discriminación de descuentos | `LineItem`, `TaxSummary` |
|
|
309
|
+
| 11 num. 2 | CONSUMIDOR FINAL; datos sobre L 10,000.00 | `Customer::ConsumidorFinal` |
|
|
310
|
+
| 11 num. 3 | Crédito fiscal sólo por ventas gravadas | `TaxSummary#credito_fiscal_base` |
|
|
311
|
+
| 11 párrafo final | Tasa de cambio a la fecha de emisión | `ExchangeRate` |
|
|
312
|
+
| 12 | Exportaciones con tasa cero | `TaxTreatment::GRAVADO_0` |
|
|
313
|
+
| 41 | Leyenda ANULADA; correlativo conservado | `Invoice#annul` |
|
|
314
|
+
| 42 | Aviso de autorizaciones vencidas sin usar | `invoicehn check` |
|
|
315
|
+
| 43 | Custodia cronológica | `Storage::JsonStore` |
|
|
316
|
+
| 53 num. 1 | Integración contable o de inventarios | `Ledger` |
|
|
317
|
+
| 53 num. 5 | Exportación en archivos de texto | `invoicehn export` |
|
|
318
|
+
| 62 | La autorización vencida bloquea la emisión | `Authorization#expired?` |
|
|
319
|
+
| ISV Art. 3 | Los descuentos no forman base gravable | `LineItem#taxable_base` |
|
|
320
|
+
| ISV Art. 9 | Redondeo a centavos, medio hacia arriba | `Money#round_statutory` |
|
|
321
|
+
|
|
322
|
+
### Art. 9 del Reglamento: por qué no hay consolidación de fin de día
|
|
323
|
+
|
|
324
|
+
El Art. 9 permite no extender comprobante en el acto para ventas a consumidores
|
|
325
|
+
finales que no excedan L 50.00. Ese alivio **no aplica** a los autoimpresores por
|
|
326
|
+
máquina registradora ni por sistema computarizado — y esta gema es un sistema
|
|
327
|
+
computarizado. Por eso emite siempre un documento por venta: la ausencia de esa
|
|
328
|
+
función es deliberada.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Integración contable (Art. 53 num. 1)
|
|
333
|
+
|
|
334
|
+
El Art. 53 num. 1 condiciona la autorización como autoimpresor a que el sistema
|
|
335
|
+
de facturación esté integrado al menos a un sistema contable o de inventarios.
|
|
336
|
+
`Invoicehn::Ledger` es esa interfaz; la gema incluye una implementación en JSONL
|
|
337
|
+
que cumple el requisito por sí sola.
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
class MiContabilidad < Invoicehn::Ledger
|
|
341
|
+
def record(invoice, event: :emision)
|
|
342
|
+
MiERP::Asiento.crear!(invoice.to_h)
|
|
343
|
+
invoice
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def entries(from: nil, to: nil) = MiERP::Asiento.entre(from, to)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
Invoicehn::Issuance.new(
|
|
350
|
+
ledger: Invoicehn::Ledger::Multi.new(
|
|
351
|
+
Invoicehn::Ledger::JsonlLedger.new,
|
|
352
|
+
MiContabilidad.new
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Almacenamiento
|
|
360
|
+
|
|
361
|
+
Por defecto en `~/.invoicehn` (configurable con `INVOICEHN_HOME` o `--data-dir`):
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
~/.invoicehn/
|
|
365
|
+
├── issuer.json # datos del emisor
|
|
366
|
+
├── authorizations.json # los CAI registrados
|
|
367
|
+
├── sequences.json # contadores por identificador
|
|
368
|
+
├── ledger.jsonl # libro append-only
|
|
369
|
+
└── documentos/AAAA/MM/000-001-01-00000001.json
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Archivos JSON planos, legibles sin esta gema — algo que importa en registros que
|
|
373
|
+
deben sobrevivir al software que los escribió.
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Fuentes
|
|
378
|
+
|
|
379
|
+
Textos primarios, por número de *La Gaceta*:
|
|
380
|
+
|
|
381
|
+
| Gaceta | Norma |
|
|
382
|
+
|---|---|
|
|
383
|
+
| 34,413 | Acuerdo 481-2017 (original) |
|
|
384
|
+
| 34,457 | Acuerdo 609-2017 (primera reforma) |
|
|
385
|
+
| 34,792 | Acuerdo 725-2018 (segunda reforma — agrega los descuentos) |
|
|
386
|
+
| 34,811 | Acuerdo 817-2018 (tercera reforma) |
|
|
387
|
+
| 33,316 | Decreto 278-2013 (tasas del ISV) |
|
|
388
|
+
|
|
389
|
+
Más la Ley del Impuesto Sobre Ventas (Decreto-Ley 24, Arts. 3 y 9) y el Código
|
|
390
|
+
Tributario (Decreto 170-2016, Art. 66). Los PDF y las extracciones están en
|
|
391
|
+
`doc/fuentes/`.
|
|
392
|
+
|
|
393
|
+
> Dos trampas encontradas al investigar, por si le ahorran tiempo: la copia de la
|
|
394
|
+
> Ley del ISV que publica el TSC es una consolidación que se detiene en el
|
|
395
|
+
> Decreto 171-98 y todavía muestra 12%/15% — no sirve para citar tasas, aunque sí
|
|
396
|
+
> es buena fuente del Art. 9. Y el texto de 2017 del Acuerdo 481-2017 fue
|
|
397
|
+
> reformado tres veces: hay que trabajar sobre el *texto consolidado* del SAR.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Desarrollo
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
bundle install
|
|
405
|
+
bundle exec rake # pruebas y linter
|
|
406
|
+
bundle exec rake test
|
|
407
|
+
bundle exec rake rubocop
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Publicar una versión
|
|
411
|
+
|
|
412
|
+
Un número de versión publicado en RubyGems **no se puede reutilizar**: `gem yank`
|
|
413
|
+
retira el paquete pero deja el número quemado para siempre. Por eso `rake build`
|
|
414
|
+
y `rake release` ejecutan las pruebas y el linter antes de hacer nada.
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
gem signin # una sola vez por máquina; requiere MFA
|
|
418
|
+
# subir la versión en lib/invoicehn/version.rb
|
|
419
|
+
# anotar los cambios en CHANGELOG.md
|
|
420
|
+
bundle exec rake release # etiqueta vX.Y.Z, la sube y publica la gema
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
`rake release` se niega a continuar si el árbol de trabajo tiene cambios sin
|
|
424
|
+
confirmar o si la etiqueta ya existe.
|
|
425
|
+
|
|
426
|
+
## Licencia
|
|
427
|
+
|
|
428
|
+
MIT. El texto de la licencia incluye la exención de garantías habitual: esta
|
|
429
|
+
gema ayuda a cumplir los requisitos de contenido del Reglamento, pero la
|
|
430
|
+
responsabilidad tributaria sigue siendo del obligado tributario.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Command-line interface strings.
|
|
2
|
+
#
|
|
3
|
+
# IMPORTANT: this catalog does NOT contain the fiscal document's text. The
|
|
4
|
+
# invoice legends ("FACTURA", "Original: Cliente", "CONSUMIDOR FINAL",
|
|
5
|
+
# "ANULADA") are legal text fixed by the Reglamento and live as constants in
|
|
6
|
+
# Invoicehn::Renderers::Text. Translating them would produce an invalid
|
|
7
|
+
# document — an English CLI still issues a Spanish invoice.
|
|
8
|
+
en:
|
|
9
|
+
setup:
|
|
10
|
+
title: "Issuer configuration (Art. 10 num. 1)"
|
|
11
|
+
intro: "These details are printed on every invoice. All are mandatory."
|
|
12
|
+
rtn: "Issuer RTN (14 digits)"
|
|
13
|
+
legal_name: "Legal name or razón social"
|
|
14
|
+
trade_name: "Trade name"
|
|
15
|
+
headquarters: "Head office address"
|
|
16
|
+
branch: "Establishment address (leave blank if head office)"
|
|
17
|
+
phone: "Phone number"
|
|
18
|
+
email: "Email address"
|
|
19
|
+
saved: "Issuer saved to %{path}"
|
|
20
|
+
|
|
21
|
+
auth:
|
|
22
|
+
title: "Register a SAR authorization"
|
|
23
|
+
intro: |
|
|
24
|
+
The CAI, authorized range and issue deadline are granted by SAR
|
|
25
|
+
(Arts. 59-61). Copy them from the authorization document; this program
|
|
26
|
+
does not generate them.
|
|
27
|
+
cai: "CAI (Clave de Autorización de Impresión)"
|
|
28
|
+
establishment: "Establishment (3 digits; head office = 000)"
|
|
29
|
+
emission_point: "Emission point (3 digits)"
|
|
30
|
+
range_start: "First authorized number (8 digits)"
|
|
31
|
+
range_end: "Last authorized number (8 digits)"
|
|
32
|
+
limit_date: "Issue deadline (YYYY-MM-DD)"
|
|
33
|
+
saved: "Authorization registered: %{range}"
|
|
34
|
+
none: "No authorizations on file. Use `invoicehn auth add`."
|
|
35
|
+
list_header: "Registered authorizations"
|
|
36
|
+
|
|
37
|
+
invoice:
|
|
38
|
+
customer_kind: "Customer type"
|
|
39
|
+
kind_consumidor_final: "Final consumer"
|
|
40
|
+
kind_taxpayer: "Taxpayer (claims crédito fiscal)"
|
|
41
|
+
kind_exonerado: "Exonerated purchaser"
|
|
42
|
+
customer_name: "Customer name or legal name"
|
|
43
|
+
customer_rtn: "Customer RTN"
|
|
44
|
+
id_type: "Identification document type"
|
|
45
|
+
id_number: "Identification document number"
|
|
46
|
+
purchase_order: "Orden de Compra Exenta number"
|
|
47
|
+
exoneration_registry: "Constancia del Registro de Exonerados number"
|
|
48
|
+
sag_registry: "SAG registry number"
|
|
49
|
+
add_line: "Add a line item?"
|
|
50
|
+
description: "Detailed description of the good or service"
|
|
51
|
+
quantity: "Quantity"
|
|
52
|
+
unit_price: "Unit price"
|
|
53
|
+
discount: "Discount granted (0 if none)"
|
|
54
|
+
treatment: "ISV treatment"
|
|
55
|
+
currency: "Currency"
|
|
56
|
+
exchange_rate: "Exchange rate in force on the issue date"
|
|
57
|
+
exchange_source: "Exchange rate source"
|
|
58
|
+
notes: "Additional notes (optional)"
|
|
59
|
+
confirm: "Issue this invoice?"
|
|
60
|
+
cancelled: "Issuance cancelled. No number was consumed."
|
|
61
|
+
issued: "Invoice issued: %{correlative}"
|
|
62
|
+
|
|
63
|
+
check:
|
|
64
|
+
title: "Billing system status"
|
|
65
|
+
issuer_ok: "Issuer configured and complete"
|
|
66
|
+
issuer_missing: "Issuer details missing: %{fields}"
|
|
67
|
+
issuer_absent: "No issuer configured. Run `invoicehn setup`."
|
|
68
|
+
auth_active: "Current authorization: %{cai}"
|
|
69
|
+
auth_absent: "No current authorization for %{identifier}"
|
|
70
|
+
next: "Next number: %{correlative}"
|
|
71
|
+
remaining: "Documents available: %{count}"
|
|
72
|
+
days: "Days until the deadline: %{days}"
|
|
73
|
+
lapsed: |
|
|
74
|
+
Warning: %{count} authorization(s) expired holding unused documents.
|
|
75
|
+
Art. 42 requires reporting this to SAR within the first 10 business days
|
|
76
|
+
of the following month.
|
|
77
|
+
ready: "Ready to issue."
|
|
78
|
+
not_ready: "Not ready to issue."
|
|
79
|
+
|
|
80
|
+
errors:
|
|
81
|
+
not_initialized: "No issuer configured. Run `invoicehn setup` first."
|
|
82
|
+
not_found: "Invoice %{correlative} not found"
|
|
83
|
+
compliance: "The invoice does not meet the legal requirements:"
|
|
84
|
+
|
|
85
|
+
common:
|
|
86
|
+
yes: "Yes"
|
|
87
|
+
no: "No"
|
|
88
|
+
total: "Total"
|
|
89
|
+
cancel: "Cancel"
|