libredte-lib-sdk 0.2.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (84) hide show
  1. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/PKG-INFO +5 -5
  2. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/README.rst +4 -4
  3. libredte_lib_sdk-0.3.0/libredte_lib_sdk/billing/document/builder.py +83 -0
  4. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/examples.py +1 -1
  5. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/models.py +3 -3
  6. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/renderer.py +16 -7
  7. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/sdk.py +2 -2
  8. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/pyproject.toml +1 -1
  9. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/conftest.py +5 -5
  10. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_book.py +1 -1
  11. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_examples.py +7 -7
  12. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_exchange.py +2 -2
  13. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_ownership_transfer.py +1 -1
  14. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/test_client.py +1 -1
  15. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/test_services.py +54 -6
  16. libredte_lib_sdk-0.2.0/libredte_lib_sdk/billing/document/builder.py +0 -71
  17. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/.github/workflows/ci.yml +0 -0
  18. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/.github/workflows/docs.yml +0 -0
  19. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/.gitignore +0 -0
  20. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/.readthedocs.yaml +0 -0
  21. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/CLAUDE.md +0 -0
  22. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/LICENSE +0 -0
  23. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/Makefile +0 -0
  24. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/docs/Makefile +0 -0
  25. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/docs/conf.py +0 -0
  26. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/docs/index.rst +0 -0
  27. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/__init__.py +0 -0
  28. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/__init__.py +0 -0
  29. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/book/__init__.py +0 -0
  30. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/book/builder.py +0 -0
  31. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/book/loader.py +0 -0
  32. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/book/models.py +0 -0
  33. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/book/validator.py +0 -0
  34. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/common.py +0 -0
  35. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/__init__.py +0 -0
  36. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/dispatcher.py +0 -0
  37. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/loader.py +0 -0
  38. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/document/validator.py +0 -0
  39. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/enums.py +0 -0
  40. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/exchange/__init__.py +0 -0
  41. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/exchange/document_response.py +0 -0
  42. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/exchange/models.py +0 -0
  43. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/identifier/__init__.py +0 -0
  44. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/identifier/caf_faker.py +0 -0
  45. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/identifier/caf_loader.py +0 -0
  46. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/identifier/caf_validator.py +0 -0
  47. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/identifier/models.py +0 -0
  48. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/integration/__init__.py +0 -0
  49. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/integration/models.py +0 -0
  50. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/integration/sii_dte.py +0 -0
  51. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/integration/sii_rcv.py +0 -0
  52. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/integration/sii_rtc.py +0 -0
  53. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/ownership_transfer/__init__.py +0 -0
  54. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/ownership_transfer/aec.py +0 -0
  55. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/ownership_transfer/models.py +0 -0
  56. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/trading_parties/__init__.py +0 -0
  57. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/trading_parties/mandatario_manager.py +0 -0
  58. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/billing/trading_parties/models.py +0 -0
  59. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/client.py +0 -0
  60. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/exceptions.py +0 -0
  61. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/human_resources/__init__.py +0 -0
  62. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/human_resources/integration/__init__.py +0 -0
  63. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/human_resources/integration/previred_provider.py +0 -0
  64. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/human_resources/payroll/__init__.py +0 -0
  65. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/human_resources/payroll/calculator.py +0 -0
  66. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/human_resources/payroll/renderer.py +0 -0
  67. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/py.typed +0 -0
  68. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/system/__init__.py +0 -0
  69. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/system/certificate/__init__.py +0 -0
  70. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/system/certificate/loader.py +0 -0
  71. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/system/repository/__init__.py +0 -0
  72. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/libredte_lib_sdk/system/repository/catalog.py +0 -0
  73. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/__init__.py +0 -0
  74. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/conftest.py +0 -0
  75. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/__init__.py +0 -0
  76. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_caf.py +0 -0
  77. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_certificate_loader.py +0 -0
  78. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_document_loader.py +0 -0
  79. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_document_rendering.py +0 -0
  80. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_document_validation.py +0 -0
  81. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_human_resources.py +0 -0
  82. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_operation_not_found.py +0 -0
  83. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/integration/test_repository.py +0 -0
  84. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.3.0}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: libredte-lib-sdk
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: SDK Python para la API de LibreDTE Lib (facturacion electronica de Chile).
5
5
  Project-URL: Homepage, https://www.libredte.cl
6
6
  Project-URL: Source, https://github.com/libredte/libredte-lib-sdk-python
@@ -80,7 +80,7 @@ Uso
80
80
  sdk = LibreDTE() # base_url y api_token también vía env vars, ver abajo
81
81
  b = sdk.billing
82
82
 
83
- parsed_data = {
83
+ input_data = {
84
84
  'Encabezado': {
85
85
  'IdDoc': {
86
86
  'TipoDTE': 33,
@@ -125,11 +125,11 @@ Uso
125
125
  # b.identifier.caf_validator.validate(caf.xml_base64)
126
126
 
127
127
  # 1. Borrador.
128
- borrador = b.document.builder.build_draft(parsed_data)
128
+ borrador = b.document.builder.build_draft(input_data)
129
129
 
130
130
  # 2. Documento real, timbrado y firmado.
131
131
  documento = b.document.builder.build_signed(
132
- parsed_data, caf_xml=caf.xml_base64, certificate=certificate,
132
+ input_data, caf_xml=caf.xml_base64, certificate=certificate,
133
133
  )
134
134
 
135
135
  # 3. Envío al SII (arma el sobre EnvioDTE y luego lo envía).
@@ -220,7 +220,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
220
220
  ejemplos = b.document.examples.list() # [ExampleSummary(id=..., category=..., case=...), ...]
221
221
  ejemplo = b.document.examples.get(ejemplos[0].id)
222
222
  documento = b.document.builder.build_signed(
223
- ejemplo.parsed_data, caf_xml=caf.xml_base64, certificate=certificate,
223
+ ejemplo.input_data, caf_xml=caf.xml_base64, certificate=certificate,
224
224
  )
225
225
 
226
226
  # Catálogos/repositorios reales (comunas, tipos de documento, etc.)
@@ -36,7 +36,7 @@ Uso
36
36
  sdk = LibreDTE() # base_url y api_token también vía env vars, ver abajo
37
37
  b = sdk.billing
38
38
 
39
- parsed_data = {
39
+ input_data = {
40
40
  'Encabezado': {
41
41
  'IdDoc': {
42
42
  'TipoDTE': 33,
@@ -81,11 +81,11 @@ Uso
81
81
  # b.identifier.caf_validator.validate(caf.xml_base64)
82
82
 
83
83
  # 1. Borrador.
84
- borrador = b.document.builder.build_draft(parsed_data)
84
+ borrador = b.document.builder.build_draft(input_data)
85
85
 
86
86
  # 2. Documento real, timbrado y firmado.
87
87
  documento = b.document.builder.build_signed(
88
- parsed_data, caf_xml=caf.xml_base64, certificate=certificate,
88
+ input_data, caf_xml=caf.xml_base64, certificate=certificate,
89
89
  )
90
90
 
91
91
  # 3. Envío al SII (arma el sobre EnvioDTE y luego lo envía).
@@ -176,7 +176,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
176
176
  ejemplos = b.document.examples.list() # [ExampleSummary(id=..., category=..., case=...), ...]
177
177
  ejemplo = b.document.examples.get(ejemplos[0].id)
178
178
  documento = b.document.builder.build_signed(
179
- ejemplo.parsed_data, caf_xml=caf.xml_base64, certificate=certificate,
179
+ ejemplo.input_data, caf_xml=caf.xml_base64, certificate=certificate,
180
180
  )
181
181
 
182
182
  # Catálogos/repositorios reales (comunas, tipos de documento, etc.)
@@ -0,0 +1,83 @@
1
+ # Copyright (C) 2026 LibreDTE <https://www.libredte.cl>
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """Servicio para `billing.document.builder`."""
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from ...client import ApiClient
11
+ from ..trading_parties.models import Certificate
12
+ from .models import Document
13
+
14
+
15
+ class DocumentBuilderService:
16
+ """
17
+ Construye documentos tributarios (`billing.document.builder`).
18
+
19
+ El mismo worker de la API sirve tanto para un borrador como para el
20
+ documento timbrado y firmado, según qué datos se le pasen — acá se
21
+ separa en dos métodos explícitos para que la intención de cada
22
+ llamada quede clara en el código que la usa.
23
+ """
24
+
25
+ _BUILD_OPERATION = 'billing.document.builder::build'
26
+
27
+ def __init__(self, client: ApiClient) -> None:
28
+ """Guarda el `ApiClient` compartido usado para llamar a la API."""
29
+ self._client = client
30
+
31
+ def build_draft(
32
+ self,
33
+ input_data: str | dict[str, Any],
34
+ *,
35
+ options: dict[str, Any] | None = None,
36
+ ) -> Document:
37
+ """
38
+ Emite el borrador de un DTE a partir de datos de entrada.
39
+
40
+ `input_data` es el `Encabezado`/`Detalle` (y demás nodos) del
41
+ formato DTE del SII como `dict`, incluyendo
42
+ `Encabezado.IdDoc.Folio` (el SDK no asigna folios: eso lo decide
43
+ quien llama, típicamente porque lleva el correlativo) — o datos
44
+ en otro formato (XML/YAML, un formulario, etc.) junto con
45
+ `options={'parser': {'strategy': '<estrategia>'}}` para indicar
46
+ cómo parsearlos. Sin `options`, la API asume que `input_data` ya
47
+ viene en el formato DTE del SII (estrategia `default.json`). Sin
48
+ CAF ni certificado, el resultado no queda timbrado
49
+ (`Document.is_timbrado` es `False`).
50
+ """
51
+ bag: dict[str, Any] = {'inputData': input_data}
52
+ if options is not None:
53
+ bag['options'] = options
54
+
55
+ data = self._client.call(self._BUILD_OPERATION, bag=bag)
56
+ return Document.from_api(data)
57
+
58
+ def build_signed(
59
+ self,
60
+ input_data: str | dict[str, Any],
61
+ *,
62
+ options: dict[str, Any] | None = None,
63
+ caf_xml: str,
64
+ certificate: Certificate,
65
+ ) -> Document:
66
+ """
67
+ Genera el DTE real, timbrado y firmado.
68
+
69
+ `input_data`/`options` funcionan igual que en `build_draft()`.
70
+ Requiere un CAF real (XML tal como lo entrega el SII, cubriendo
71
+ el folio indicado en `input_data`) y el certificado digital del
72
+ emisor. Para pruebas, ambos se pueden generar con
73
+ `IdentifierComponent.caf_faker` y
74
+ `TradingPartiesComponent.mandatario_manager`.
75
+ """
76
+ bag: dict[str, Any] = {'inputData': input_data}
77
+ if options is not None:
78
+ bag['options'] = options
79
+ bag['caf'] = caf_xml
80
+ bag['certificate'] = certificate.to_payload()
81
+
82
+ data = self._client.call(self._BUILD_OPERATION, bag=bag)
83
+ return Document.from_api(data)
@@ -17,7 +17,7 @@ class DocumentExamplesService:
17
17
  de `libredte-lib-core` (`tests/fixtures/yaml/documentos_ok/`) — uno
18
18
  por variante de negocio real (descuentos, impuesto adicional, pago a
19
19
  crédito, exportación, etc.), no datos inventados por el SDK.
20
- `Example.parsed_data` de `get()` se le pasa tal cual a
20
+ `Example.input_data` de `get()` se le pasa tal cual a
21
21
  `DocumentBuilderService.build_draft()`/`.build_signed()`, típicamente
22
22
  reemplazando `Encabezado.IdDoc.Folio` y `Encabezado.Receptor` por los
23
23
  propios de quien usa el SDK antes de construir el documento.
@@ -163,7 +163,7 @@ class Example:
163
163
  """
164
164
  Un ejemplo de documento, tal como lo devuelve `examples::get()`.
165
165
 
166
- `parsed_data` es directamente el `parsedData` que esperan
166
+ `input_data` se pasa directamente al parámetro homónimo de
167
167
  `DocumentBuilderService.build_draft()`/`.build_signed()` — mismo
168
168
  `Encabezado`/`Detalle` que usa el resto del SDK, sin transformación.
169
169
  `expected` son los valores esperados del caso (totales, etc.) que usa
@@ -172,7 +172,7 @@ class Example:
172
172
  """
173
173
 
174
174
  id: str
175
- parsed_data: dict[str, Any]
175
+ input_data: dict[str, Any]
176
176
  expected: dict[str, Any]
177
177
 
178
178
  @classmethod
@@ -180,7 +180,7 @@ class Example:
180
180
  """Construye un `Example` desde el `data` que devuelve la API."""
181
181
  return cls(
182
182
  id=data['id'],
183
- parsed_data=data['example'],
183
+ input_data=data['example'],
184
184
  expected=data.get('expected') or {},
185
185
  )
186
186
 
@@ -32,6 +32,7 @@ class DocumentRendererService:
32
32
  *,
33
33
  format: str = 'pdf', # noqa: A002
34
34
  renderings: dict[str, int] | None = None,
35
+ libredte_data: dict[str, Any] | None = None,
35
36
  ) -> RenderResult:
36
37
  """
37
38
  Genera el PDF (u otro formato soportado por la API) de un documento.
@@ -48,16 +49,24 @@ class DocumentRendererService:
48
49
  generarse (ej. pedir solo `'cedible'` para un tipo de documento
49
50
  sin acuse de recibo). Ver `RenderResult` para cómo se identifica
50
51
  cada copia en la respuesta.
52
+
53
+ `libredte_data` son datos adicionales que no están en el XML del
54
+ documento pero sí se usan al renderizar la plantilla — ej.
55
+ `{'extra': {'dte': {...}, 'historial': [...]}}`: lo que va en
56
+ `extra.dte` se combina con los datos del documento antes de
57
+ renderizar; el resto de `extra` (ej. `historial`) queda
58
+ disponible aparte para la plantilla.
51
59
  """
52
60
  renderer_options: dict[str, Any] = {'format': format}
53
61
  if renderings is not None:
54
62
  renderer_options['renderings'] = renderings
55
63
 
56
- data = self._client.call(
57
- self._RENDER_OPERATION,
58
- bag={
59
- 'xmlDocument': document_xml_base64,
60
- 'options': {'renderer': renderer_options},
61
- },
62
- )
64
+ bag: dict[str, Any] = {
65
+ 'xmlDocument': document_xml_base64,
66
+ 'options': {'renderer': renderer_options},
67
+ }
68
+ if libredte_data is not None:
69
+ bag['libredteData'] = libredte_data
70
+
71
+ data = self._client.call(self._RENDER_OPERATION, bag=bag)
63
72
  return RenderResult.from_api(data)
@@ -34,14 +34,14 @@ class LibreDTE:
34
34
  sdk.billing.integration,
35
35
  )
36
36
 
37
- borrador = doc.builder.build_draft(parsed_data)
37
+ borrador = doc.builder.build_draft(input_data)
38
38
 
39
39
  caf = ident.caf_faker.create(emisor, codigo_documento=33)
40
40
  certificate = tp.mandatario_manager.create_fake_certificate(
41
41
  mandatario,
42
42
  )
43
43
  documento = doc.builder.build_signed(
44
- parsed_data, caf_xml=caf.xml_base64, certificate=certificate,
44
+ input_data, caf_xml=caf.xml_base64, certificate=certificate,
45
45
  )
46
46
  sobre = doc.dispatcher.create(
47
47
  documento.xml_base64, certificate=certificate, emisor=emisor_dto,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "libredte-lib-sdk"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "SDK Python para la API de LibreDTE Lib (facturacion electronica de Chile)."
9
9
  readme = "README.rst"
10
10
  license = { file = "LICENSE" }
@@ -17,7 +17,7 @@ import pytest
17
17
  from libredte_lib_sdk import LibreDTE
18
18
  from libredte_lib_sdk.billing.trading_parties import Mandatario
19
19
 
20
- _PARSED_DATA = {
20
+ _INPUT_DATA = {
21
21
  'Encabezado': {
22
22
  'IdDoc': {'TipoDTE': 33, 'Folio': 1},
23
23
  'Emisor': {
@@ -38,7 +38,7 @@ _PARSED_DATA = {
38
38
  'Detalle': [{'NmbItem': 'Producto A', 'QtyItem': 1, 'PrcItem': 1000}],
39
39
  }
40
40
 
41
- _PARSED_DATA_BOLETA = {
41
+ _INPUT_DATA_BOLETA = {
42
42
  'Encabezado': {
43
43
  'IdDoc': {'TipoDTE': 39, 'Folio': 1},
44
44
  'Emisor': {
@@ -68,7 +68,7 @@ def real_sdk():
68
68
  @pytest.fixture
69
69
  def draft_document(real_sdk):
70
70
  """Un borrador real (factura afecta, tipo 33), construido en vivo."""
71
- return real_sdk.billing.document.builder.build_draft(_PARSED_DATA)
71
+ return real_sdk.billing.document.builder.build_draft(_INPUT_DATA)
72
72
 
73
73
 
74
74
  @pytest.fixture
@@ -80,7 +80,7 @@ def draft_boleta(real_sdk):
80
80
  API omite en silencio una presentación `'cedible'` pedida sobre un
81
81
  tipo de documento que no la soporta (ver `RenderResult`).
82
82
  """
83
- return real_sdk.billing.document.builder.build_draft(_PARSED_DATA_BOLETA)
83
+ return real_sdk.billing.document.builder.build_draft(_INPUT_DATA_BOLETA)
84
84
 
85
85
 
86
86
  @pytest.fixture
@@ -111,7 +111,7 @@ def signed_document(real_sdk, fake_certificate):
111
111
  folio_hasta=100,
112
112
  )
113
113
  return real_sdk.billing.document.builder.build_signed(
114
- _PARSED_DATA,
114
+ _INPUT_DATA,
115
115
  caf_xml=caf.xml_base64,
116
116
  certificate=fake_certificate,
117
117
  )
@@ -42,7 +42,7 @@ _BAG = {
42
42
  }
43
43
 
44
44
 
45
- def test_builder_build_returns_the_parsed_data_and_the_xml(
45
+ def test_builder_build_returns_the_datos_and_the_xml(
46
46
  fake_certificate,
47
47
  real_sdk,
48
48
  ):
@@ -6,7 +6,7 @@ Tests en vivo para `DocumentExamplesService`.
6
6
 
7
7
  Los ejemplos son los mismos casos de prueba, ya validados, de
8
8
  `libredte-lib-core` (`tests/fixtures/yaml/documentos_ok/`) — se prueba
9
- que `get()` entrega un `parsed_data` realmente utilizable por
9
+ que `get()` entrega un `input_data` realmente utilizable por
10
10
  `DocumentBuilderService`, no solo que la llamada HTTP responde.
11
11
  """
12
12
 
@@ -34,19 +34,19 @@ def test_list_returns_many_examples_across_document_types(real_sdk):
34
34
  assert len(categories) > 1
35
35
 
36
36
 
37
- def test_get_returns_parsed_data_and_expected_values(real_sdk):
37
+ def test_get_returns_input_data_and_expected_values(real_sdk):
38
38
  first = real_sdk.billing.document.examples.list()[0]
39
39
 
40
40
  example = real_sdk.billing.document.examples.get(first.id)
41
41
 
42
42
  assert example.id == first.id
43
- assert 'Encabezado' in example.parsed_data
44
- assert 'Detalle' in example.parsed_data
43
+ assert 'Encabezado' in example.input_data
44
+ assert 'Detalle' in example.input_data
45
45
  assert example.expected
46
46
 
47
47
 
48
48
  def test_an_example_builds_into_a_real_signed_document(real_sdk):
49
- """El `parsed_data` de un ejemplo debe servir tal cual a `build_signed`."""
49
+ """El `input_data` de un ejemplo debe servir tal cual a `build_signed`."""
50
50
  b = real_sdk.billing
51
51
  example = next(
52
52
  e
@@ -57,14 +57,14 @@ def test_an_example_builds_into_a_real_signed_document(real_sdk):
57
57
 
58
58
  caf = b.identifier.caf_faker.create(
59
59
  _EMISOR,
60
- codigo_documento=data.parsed_data['Encabezado']['IdDoc']['TipoDTE'],
60
+ codigo_documento=data.input_data['Encabezado']['IdDoc']['TipoDTE'],
61
61
  )
62
62
  certificate = b.trading_parties.mandatario_manager.create_fake_certificate(
63
63
  _MANDATARIO,
64
64
  )
65
65
 
66
66
  documento = b.document.builder.build_signed(
67
- data.parsed_data,
67
+ data.input_data,
68
68
  caf_xml=caf.xml_base64,
69
69
  certificate=certificate,
70
70
  )
@@ -50,7 +50,7 @@ _RESULTADO_DTE = {
50
50
  }
51
51
 
52
52
 
53
- def test_build_envio_recibos_returns_the_parsed_data_and_the_xml(
53
+ def test_build_envio_recibos_returns_the_datos_and_the_xml(
54
54
  fake_certificate,
55
55
  real_sdk,
56
56
  ):
@@ -82,7 +82,7 @@ def test_build_envio_recibos_result_chains_into_validator(
82
82
  assert len(results) >= 1
83
83
 
84
84
 
85
- def test_build_respuesta_envio_returns_the_parsed_data_and_the_xml(
85
+ def test_build_respuesta_envio_returns_the_datos_and_the_xml(
86
86
  fake_certificate,
87
87
  real_sdk,
88
88
  ):
@@ -36,7 +36,7 @@ def built_aec(signed_document, fake_certificate, real_sdk):
36
36
  )
37
37
 
38
38
 
39
- def test_build_returns_the_parsed_data_and_the_xml(built_aec):
39
+ def test_build_returns_the_datos_and_the_xml(built_aec):
40
40
  assert (
41
41
  built_aec.datos['DocumentoAEC']['Caratula']['RutCedente']
42
42
  == '76192083-9'
@@ -67,7 +67,7 @@ def test_call_returns_the_data_payload_on_success(api_client):
67
67
 
68
68
  result = api_client.call(
69
69
  'billing.document.builder::build',
70
- bag={'parsedData': {}},
70
+ bag={'inputData': {}},
71
71
  )
72
72
 
73
73
  assert result == {'id': 'doc-1'}
@@ -42,7 +42,7 @@ _REAL_SIGNED_DOCUMENT_RESPONSE = {
42
42
  },
43
43
  }
44
44
 
45
- _PARSED_DATA = {
45
+ _INPUT_DATA = {
46
46
  'Encabezado': {
47
47
  'IdDoc': {'TipoDTE': 33, 'Folio': 1},
48
48
  'Emisor': {'RUTEmisor': '76192083-9', 'RznSoc': 'SASCO SpA'},
@@ -64,14 +64,35 @@ def test_build_draft_omits_caf_and_certificate_from_the_payload(sdk):
64
64
  f'{TEST_BASE_URL}/billing/document/builder/build',
65
65
  ).mock(return_value=httpx.Response(200, json=draft_response))
66
66
 
67
- document = sdk.billing.document.builder.build_draft(_PARSED_DATA)
67
+ document = sdk.billing.document.builder.build_draft(_INPUT_DATA)
68
68
 
69
69
  sent = json.loads(route.calls.last.request.content)
70
- assert sent['parameters']['bag'].keys() == {'parsedData'}
70
+ assert sent['parameters']['bag'].keys() == {'inputData'}
71
71
  assert document.id == '76192083-9_T033F000000001'
72
72
  assert document.is_timbrado is False
73
73
 
74
74
 
75
+ @respx.mock
76
+ def test_build_draft_sends_options_when_given(sdk):
77
+ draft_response = {
78
+ 'meta': _REAL_SIGNED_DOCUMENT_RESPONSE['meta'],
79
+ 'data': {**_REAL_SIGNED_DOCUMENT_RESPONSE['data'], 'ted': None},
80
+ }
81
+ route = respx.post(
82
+ f'{TEST_BASE_URL}/billing/document/builder/build',
83
+ ).mock(return_value=httpx.Response(200, json=draft_response))
84
+
85
+ sdk.billing.document.builder.build_draft(
86
+ '<DTE>...</DTE>',
87
+ options={'parser': {'strategy': 'default.xml'}},
88
+ )
89
+
90
+ sent = json.loads(route.calls.last.request.content)
91
+ bag = sent['parameters']['bag']
92
+ assert bag['inputData'] == '<DTE>...</DTE>'
93
+ assert bag['options'] == {'parser': {'strategy': 'default.xml'}}
94
+
95
+
75
96
  @respx.mock
76
97
  def test_build_signed_includes_caf_and_certificate(sdk):
77
98
  route = respx.post(
@@ -81,7 +102,7 @@ def test_build_signed_includes_caf_and_certificate(sdk):
81
102
  )
82
103
 
83
104
  document = sdk.billing.document.builder.build_signed(
84
- _PARSED_DATA,
105
+ _INPUT_DATA,
85
106
  caf_xml='<AUTORIZACION/>',
86
107
  certificate=_CERTIFICATE,
87
108
  )
@@ -451,6 +472,33 @@ def test_renderer_render_omits_renderings_key_by_default(sdk):
451
472
 
452
473
  sent = json.loads(route.calls.last.request.content)['parameters']
453
474
  assert 'renderings' not in sent['bag']['options']['renderer']
475
+ assert 'libredteData' not in sent['bag']
476
+
477
+
478
+ @respx.mock
479
+ def test_renderer_render_sends_libredte_data_when_given(sdk):
480
+ """`libredte_data` viaja como `bag.libredteData` cuando se indica."""
481
+ route = respx.post(
482
+ f'{TEST_BASE_URL}/billing/document/renderer/render',
483
+ ).mock(
484
+ return_value=httpx.Response(
485
+ 200,
486
+ json={
487
+ 'meta': {},
488
+ 'data': {'renderings': [_rendering_response(b'ok')]},
489
+ },
490
+ ),
491
+ )
492
+
493
+ sdk.billing.document.renderer.render(
494
+ 'ZG9jdW1lbnRv',
495
+ libredte_data={'extra': {'historial': ['evento 1']}},
496
+ )
497
+
498
+ sent = json.loads(route.calls.last.request.content)['parameters']
499
+ assert sent['bag']['libredteData'] == {
500
+ 'extra': {'historial': ['evento 1']},
501
+ }
454
502
 
455
503
 
456
504
  @respx.mock
@@ -492,7 +540,7 @@ def test_document_examples_get_sends_id_and_splits_expected(sdk):
492
540
  'meta': {},
493
541
  'data': {
494
542
  'id': '033_factura_afecta/033_001_simple',
495
- 'example': _PARSED_DATA,
543
+ 'example': _INPUT_DATA,
496
544
  'expected': {
497
545
  'Encabezado': {'Totales': {'MntTotal': 1190}},
498
546
  },
@@ -507,7 +555,7 @@ def test_document_examples_get_sends_id_and_splits_expected(sdk):
507
555
 
508
556
  sent = json.loads(route.calls.last.request.content)['parameters']
509
557
  assert sent['id'] == '033_factura_afecta/033_001_simple'
510
- assert example.parsed_data == _PARSED_DATA
558
+ assert example.input_data == _INPUT_DATA
511
559
  assert example.expected['Encabezado']['Totales']['MntTotal'] == 1190
512
560
 
513
561
 
@@ -1,71 +0,0 @@
1
- # Copyright (C) 2026 LibreDTE <https://www.libredte.cl>
2
- # SPDX-License-Identifier: MIT
3
-
4
- """Servicio para `billing.document.builder`."""
5
-
6
- from __future__ import annotations
7
-
8
- from typing import Any
9
-
10
- from ...client import ApiClient
11
- from ..trading_parties.models import Certificate
12
- from .models import Document
13
-
14
-
15
- class DocumentBuilderService:
16
- """
17
- Construye documentos tributarios (`billing.document.builder`).
18
-
19
- El mismo worker de la API sirve tanto para un borrador como para el
20
- documento timbrado y firmado, según qué datos se le pasen — acá se
21
- separa en dos métodos explícitos para que la intención de cada
22
- llamada quede clara en el código que la usa.
23
- """
24
-
25
- _BUILD_OPERATION = 'billing.document.builder::build'
26
-
27
- def __init__(self, client: ApiClient) -> None:
28
- """Guarda el `ApiClient` compartido usado para llamar a la API."""
29
- self._client = client
30
-
31
- def build_draft(self, parsed_data: dict[str, Any]) -> Document:
32
- """
33
- Emite el borrador de un DTE a partir de datos ya normalizados.
34
-
35
- `parsed_data` es el `Encabezado`/`Detalle` (y demás nodos) del
36
- formato DTE del SII, incluyendo `Encabezado.IdDoc.Folio` (el SDK
37
- no asigna folios: eso lo decide quien llama, típicamente porque
38
- lleva el correlativo). Sin CAF ni certificado, el resultado no
39
- queda timbrado (`Document.is_timbrado` es `False`).
40
- """
41
- data = self._client.call(
42
- self._BUILD_OPERATION,
43
- bag={'parsedData': parsed_data},
44
- )
45
- return Document.from_api(data)
46
-
47
- def build_signed(
48
- self,
49
- parsed_data: dict[str, Any],
50
- *,
51
- caf_xml: str,
52
- certificate: Certificate,
53
- ) -> Document:
54
- """
55
- Genera el DTE real, timbrado y firmado.
56
-
57
- Requiere un CAF real (XML tal como lo entrega el SII, cubriendo
58
- el folio indicado en `parsed_data`) y el certificado digital del
59
- emisor. Para pruebas, ambos se pueden generar con
60
- `IdentifierComponent.caf_faker` y
61
- `TradingPartiesComponent.mandatario_manager`.
62
- """
63
- data = self._client.call(
64
- self._BUILD_OPERATION,
65
- bag={
66
- 'parsedData': parsed_data,
67
- 'caf': caf_xml,
68
- 'certificate': certificate.to_payload(),
69
- },
70
- )
71
- return Document.from_api(data)