libredte-lib-sdk 0.2.0__tar.gz → 0.4.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 (85) hide show
  1. libredte_lib_sdk-0.4.0/.claude/scheduled_tasks.lock +1 -0
  2. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/PKG-INFO +6 -6
  3. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/README.rst +5 -5
  4. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/loader.py +2 -2
  5. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/models.py +9 -9
  6. libredte_lib_sdk-0.4.0/libredte_lib_sdk/billing/document/builder.py +83 -0
  7. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/examples.py +1 -1
  8. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/models.py +13 -13
  9. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/renderer.py +16 -7
  10. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/models.py +3 -3
  11. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/models.py +3 -3
  12. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/models.py +7 -7
  13. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/sdk.py +2 -2
  14. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/pyproject.toml +1 -1
  15. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/conftest.py +5 -5
  16. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_book.py +1 -1
  17. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_certificate_loader.py +5 -5
  18. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_examples.py +8 -8
  19. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_exchange.py +2 -2
  20. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_ownership_transfer.py +1 -1
  21. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/test_client.py +1 -1
  22. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/test_models.py +2 -2
  23. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/test_services.py +68 -20
  24. libredte_lib_sdk-0.2.0/libredte_lib_sdk/billing/document/builder.py +0 -71
  25. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.github/workflows/ci.yml +0 -0
  26. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.github/workflows/docs.yml +0 -0
  27. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.gitignore +0 -0
  28. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/.readthedocs.yaml +0 -0
  29. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/CLAUDE.md +0 -0
  30. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/LICENSE +0 -0
  31. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/Makefile +0 -0
  32. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/docs/Makefile +0 -0
  33. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/docs/conf.py +0 -0
  34. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/docs/index.rst +0 -0
  35. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/__init__.py +0 -0
  36. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/__init__.py +0 -0
  37. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/__init__.py +0 -0
  38. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/builder.py +0 -0
  39. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/book/validator.py +0 -0
  40. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/common.py +0 -0
  41. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/__init__.py +0 -0
  42. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/dispatcher.py +0 -0
  43. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/loader.py +0 -0
  44. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/document/validator.py +0 -0
  45. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/enums.py +0 -0
  46. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/__init__.py +0 -0
  47. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/document_response.py +0 -0
  48. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/exchange/models.py +0 -0
  49. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/__init__.py +0 -0
  50. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_faker.py +0 -0
  51. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_loader.py +0 -0
  52. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/caf_validator.py +0 -0
  53. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/identifier/models.py +0 -0
  54. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/__init__.py +0 -0
  55. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_dte.py +0 -0
  56. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_rcv.py +0 -0
  57. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/integration/sii_rtc.py +0 -0
  58. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/__init__.py +0 -0
  59. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/ownership_transfer/aec.py +0 -0
  60. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/__init__.py +0 -0
  61. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/billing/trading_parties/mandatario_manager.py +0 -0
  62. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/client.py +0 -0
  63. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/exceptions.py +0 -0
  64. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/__init__.py +0 -0
  65. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/integration/__init__.py +0 -0
  66. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/integration/previred_provider.py +0 -0
  67. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/payroll/__init__.py +0 -0
  68. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/payroll/calculator.py +0 -0
  69. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/human_resources/payroll/renderer.py +0 -0
  70. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/py.typed +0 -0
  71. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/__init__.py +0 -0
  72. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/certificate/__init__.py +0 -0
  73. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/certificate/loader.py +0 -0
  74. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/repository/__init__.py +0 -0
  75. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/libredte_lib_sdk/system/repository/catalog.py +0 -0
  76. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/__init__.py +0 -0
  77. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/conftest.py +0 -0
  78. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/__init__.py +0 -0
  79. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_caf.py +0 -0
  80. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_loader.py +0 -0
  81. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_rendering.py +0 -0
  82. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_document_validation.py +0 -0
  83. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_human_resources.py +0 -0
  84. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_operation_not_found.py +0 -0
  85. {libredte_lib_sdk-0.2.0 → libredte_lib_sdk-0.4.0}/tests/integration/test_repository.py +0 -0
@@ -0,0 +1 @@
1
+ {"sessionId":"64edfb1a-a3db-44cc-af25-6c7405269fdf","pid":73057,"procStart":"Mon Sep 7 03:13:55 2026","acquiredAt":1789224889985}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: libredte-lib-sdk
3
- Version: 0.2.0
3
+ Version: 0.4.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.example, caf_xml=caf.xml_base64, certificate=certificate,
224
224
  )
225
225
 
226
226
  # Catálogos/repositorios reales (comunas, tipos de documento, etc.)
@@ -238,7 +238,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
238
238
  # ya extraídos en una sola llamada — mismo `Certificate` que usan
239
239
  # build_signed()/dispatcher.create(), ya con todos los datos.
240
240
  certificate = s.certificate.loader.load(datos_del_archivo_pfx, 'contraseña')
241
- print(certificate.rut, certificate.nombre, certificate.email)
241
+ print(certificate.id, certificate.name, certificate.email)
242
242
 
243
243
  Errores
244
244
  -------
@@ -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.example, caf_xml=caf.xml_base64, certificate=certificate,
180
180
  )
181
181
 
182
182
  # Catálogos/repositorios reales (comunas, tipos de documento, etc.)
@@ -194,7 +194,7 @@ necesita para poblar sus propios datos, no operaciones de facturación.
194
194
  # ya extraídos en una sola llamada — mismo `Certificate` que usan
195
195
  # build_signed()/dispatcher.create(), ya con todos los datos.
196
196
  certificate = s.certificate.loader.load(datos_del_archivo_pfx, 'contraseña')
197
- print(certificate.rut, certificate.nombre, certificate.email)
197
+ print(certificate.id, certificate.name, certificate.email)
198
198
 
199
199
  Errores
200
200
  -------
@@ -31,8 +31,8 @@ class BookLoaderService:
31
31
  Normaliza `caratula`/`detalle` de `bag` en un `BookBag`.
32
32
 
33
33
  `bag` trae `tipo`/`caratula`/`detalle`/`emisor`, tal cual el
34
- formato SII. `BookBag.datos` siempre viene `None` (no construye
35
- el libro); `BookBag.auth` viene poblado solo si
34
+ formato SII. `BookBag.book` siempre viene `None` (no construye
35
+ el libro); `BookBag.book_auth` viene poblado solo si
36
36
  `bag['emisor']` incluye `autorizacion_dte`.
37
37
  """
38
38
  data = self._client.call(
@@ -43,16 +43,16 @@ class BookBag:
43
43
  Esta operación solo normaliza `caratula`/`detalle` a partir de los
44
44
  datos de entrada — no construye el libro ni genera su documento
45
45
  firmado (eso lo hace `BookBuilderService.build`), por lo que
46
- `datos` (el libro ya construido) siempre viene `None` acá, sin
47
- importar los datos de entrada recibidos. `auth` (la autorización
48
- del emisor del libro) solo viene poblada si esa autorización se
49
- incluyó junto con los datos del emisor en la solicitud; si no, viene
50
- `None`.
46
+ `book` (el libro ya construido) siempre viene `None` acá, sin
47
+ importar los datos de entrada recibidos. `book_auth` (la
48
+ autorización del emisor del libro) solo viene poblada si esa
49
+ autorización se incluyó junto con los datos del emisor en la
50
+ solicitud; si no, viene `None`.
51
51
  """
52
52
 
53
- datos: dict[str, Any] | None
53
+ book: dict[str, Any] | None
54
54
  book_type: dict[str, Any]
55
- auth: dict[str, Any] | None
55
+ book_auth: dict[str, Any] | None
56
56
  caratula: dict[str, Any]
57
57
  detalle: list[dict[str, Any]]
58
58
  raw: dict[str, Any]
@@ -61,9 +61,9 @@ class BookBag:
61
61
  def from_api(cls, data: dict[str, Any]) -> BookBag:
62
62
  """Construye un `BookBag` desde el `data` que devuelve la API."""
63
63
  return cls(
64
- datos=data.get('book'),
64
+ book=data.get('book'),
65
65
  book_type=data['book_type'],
66
- auth=data.get('book_auth'),
66
+ book_auth=data.get('book_auth'),
67
67
  caratula=data.get('caratula') or {},
68
68
  detalle=data.get('detalle') or [],
69
69
  raw=data,
@@ -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.example` 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.
@@ -56,29 +56,29 @@ class DocumentBag:
56
56
  Bolsa normalizada devuelta por `document.loader::loadXml`.
57
57
 
58
58
  No trae el XML del documento ni `id` — solo la bolsa ya
59
- normalizada: `datos` (`Encabezado`/`Detalle`), `document_type`
59
+ normalizada: `document` (`Encabezado`/`Detalle`), `document_type`
60
60
  (metadatos del tipo de documento: `codigo`, `nombre`, `categoria`,
61
- `es_boleta`, etc., sin tipar) y `stamp_xml` (el TED, como XML
61
+ `es_boleta`, etc., sin tipar) y `document_stamp` (el TED, como XML
62
62
  plano, no en base64 ni parseado). Sirve para reconstruir los datos
63
63
  de un DTE ya emitido a partir de su XML.
64
64
  """
65
65
 
66
- datos: dict[str, Any]
66
+ document: dict[str, Any]
67
67
  document_type: dict[str, Any]
68
- stamp_xml: str | None
69
- extra: dict[str, Any] | None
70
- auth: dict[str, Any] | None
68
+ document_stamp: str | None
69
+ document_extra: dict[str, Any] | None
70
+ document_auth: dict[str, Any] | None
71
71
  raw: dict[str, Any]
72
72
 
73
73
  @classmethod
74
74
  def from_api(cls, data: dict[str, Any]) -> DocumentBag:
75
75
  """Construye un `DocumentBag` desde el `data` de la API."""
76
76
  return cls(
77
- datos=data['document'],
77
+ document=data['document'],
78
78
  document_type=data['document_type'],
79
- stamp_xml=data.get('document_stamp'),
80
- extra=data.get('document_extra'),
81
- auth=data.get('document_auth'),
79
+ document_stamp=data.get('document_stamp'),
80
+ document_extra=data.get('document_extra'),
81
+ document_auth=data.get('document_auth'),
82
82
  raw=data,
83
83
  )
84
84
 
@@ -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
+ `example` se pasa directamente al parámetro `input_data` 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
+ example: 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
+ example=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)
@@ -193,11 +193,11 @@ class GetDocumentSiiReceptionDateResponse:
193
193
  Fecha de recepción de un DTE en el SII (`getDocumentSiiReceptionDate`).
194
194
 
195
195
  Si el documento consultado no tiene fecha de recepción registrada,
196
- la API levanta `LibreDteApiError` — `reception_date` nunca es
196
+ la API levanta `LibreDteApiError` — `fecha_recepcion_sii` nunca es
197
197
  `None`.
198
198
  """
199
199
 
200
- reception_date: datetime
200
+ fecha_recepcion_sii: datetime
201
201
  raw: dict[str, Any]
202
202
 
203
203
  @classmethod
@@ -207,7 +207,7 @@ class GetDocumentSiiReceptionDateResponse:
207
207
  ) -> GetDocumentSiiReceptionDateResponse:
208
208
  """Construye desde el `data` que devuelve la API."""
209
209
  return cls(
210
- reception_date=datetime.fromisoformat(
210
+ fecha_recepcion_sii=datetime.fromisoformat(
211
211
  data['fecha_recepcion_sii'],
212
212
  ),
213
213
  raw=data,
@@ -16,16 +16,16 @@ class Aec(XmlPayloadMixin):
16
16
  """
17
17
  AEC (Archivo Electrónico de Cesión) construido (`aec::build`).
18
18
 
19
- `datos` es la estructura ya parseada (bajo la clave `AEC` en la
19
+ `aec` es la estructura ya parseada (bajo la clave `AEC` en la
20
20
  respuesta cruda de la API), `xml_base64` el XML completo — permite
21
21
  encadenar a `AecService.validate_schema`/`.validate_signature` o a
22
22
  `SiiRtcService.send_aec`.
23
23
  """
24
24
 
25
- datos: dict[str, Any]
25
+ aec: dict[str, Any]
26
26
  xml_base64: str
27
27
 
28
28
  @classmethod
29
29
  def from_api(cls, data: dict[str, Any]) -> Aec:
30
30
  """Construye un `Aec` desde el `data` que devuelve la API."""
31
- return cls(datos=data.get('AEC') or {}, xml_base64=data['xml'])
31
+ return cls(aec=data.get('AEC') or {}, xml_base64=data['xml'])
@@ -30,8 +30,8 @@ class Certificate:
30
30
 
31
31
  certificate: str
32
32
  private_key: str
33
- rut: str | None = None
34
- nombre: str | None = None
33
+ id: str | None = None
34
+ name: str | None = None
35
35
  email: str | None = None
36
36
  valid_from: datetime | None = None
37
37
  valid_until: datetime | None = None
@@ -63,8 +63,8 @@ class Certificate:
63
63
  return cls(
64
64
  certificate=data['cert'],
65
65
  private_key=data['pkey'],
66
- rut=data.get('id'),
67
- nombre=data.get('name'),
66
+ id=data.get('id'),
67
+ name=data.get('name'),
68
68
  email=data.get('email'),
69
69
  valid_from=(
70
70
  datetime.fromisoformat(data['from'])
@@ -89,13 +89,13 @@ class Certificate:
89
89
  class Mandatario:
90
90
  """Mandatario (representante) dueño de un certificado digital."""
91
91
 
92
- rut: str
92
+ run: str
93
93
  nombre: str
94
94
  email: str | None = None
95
95
 
96
96
  def to_payload(self) -> dict[str, Any]:
97
97
  """Payload esperado por la API para un mandatario."""
98
- payload: dict[str, Any] = {'run': self.rut, 'nombre': self.nombre}
98
+ payload: dict[str, Any] = {'run': self.run, 'nombre': self.nombre}
99
99
  if self.email is not None:
100
100
  payload['email'] = self.email
101
101
  return payload
@@ -104,7 +104,7 @@ class Mandatario:
104
104
  def from_api(cls, data: dict[str, Any]) -> Mandatario:
105
105
  """Construye un `Mandatario` desde el `data` que devuelve la API."""
106
106
  return cls(
107
- rut=data['run'],
107
+ run=data['run'],
108
108
  nombre=data['nombre'],
109
109
  email=data.get('email'),
110
110
  )
@@ -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.4.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
  ):
@@ -24,7 +24,7 @@ from libredte_lib_sdk.billing.trading_parties import Mandatario
24
24
  pytestmark = pytest.mark.live
25
25
 
26
26
  _MANDATARIO = Mandatario(
27
- rut='76192083-9',
27
+ run='76192083-9',
28
28
  nombre='SASCO SpA',
29
29
  email='demo@sasco.example',
30
30
  )
@@ -56,8 +56,8 @@ def test_load_extracts_the_mandatario_and_a_usable_certificate(
56
56
  ):
57
57
  loaded = real_sdk.system.certificate.loader.load(fake_pkcs12, _PASSWORD)
58
58
 
59
- assert loaded.rut == '76192083-9'
60
- assert loaded.nombre == 'SASCO SpA'
59
+ assert loaded.id == '76192083-9'
60
+ assert loaded.name == 'SASCO SpA'
61
61
  assert loaded.email == 'demo@sasco.example'
62
62
  assert loaded.is_active is True
63
63
  assert loaded.certificate.startswith('-----BEGIN CERTIFICATE-----')
@@ -69,6 +69,6 @@ def test_create_from_certificate_matches_load(fake_pkcs12, real_sdk):
69
69
  mandatario_manager = real_sdk.billing.trading_parties.mandatario_manager
70
70
  mandatario = mandatario_manager.create_from_certificate(loaded)
71
71
 
72
- assert mandatario.rut == loaded.rut
73
- assert mandatario.nombre == loaded.nombre
72
+ assert mandatario.run == loaded.id
73
+ assert mandatario.nombre == loaded.name
74
74
  assert mandatario.email == loaded.email
@@ -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 `example` realmente utilizable por
10
10
  `DocumentBuilderService`, no solo que la llamada HTTP responde.
11
11
  """
12
12
 
@@ -20,7 +20,7 @@ pytestmark = pytest.mark.live
20
20
 
21
21
  _EMISOR = {'rut': '76192083-9', 'razon_social': 'SASCO SpA'}
22
22
  _MANDATARIO = Mandatario(
23
- rut='76192083-9',
23
+ run='76192083-9',
24
24
  nombre='SASCO SpA',
25
25
  email='demo@sasco.example',
26
26
  )
@@ -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_example_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.example
44
+ assert 'Detalle' in example.example
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 `example` 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.example['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.example,
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 @@ def test_certificate_from_api_maps_cert_and_pkey_fields():
42
42
 
43
43
 
44
44
  def test_mandatario_to_payload_omits_email_when_absent():
45
- mandatario = Mandatario(rut='76192083-9', nombre='SASCO SpA')
45
+ mandatario = Mandatario(run='76192083-9', nombre='SASCO SpA')
46
46
 
47
47
  assert mandatario.to_payload() == {
48
48
  'run': '76192083-9',
@@ -52,7 +52,7 @@ def test_mandatario_to_payload_omits_email_when_absent():
52
52
 
53
53
  def test_mandatario_to_payload_includes_email_when_present():
54
54
  mandatario = Mandatario(
55
- rut='76192083-9',
55
+ run='76192083-9',
56
56
  nombre='SASCO SpA',
57
57
  email='sasco@example.com',
58
58
  )
@@ -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
  )
@@ -220,15 +241,15 @@ def test_mandatario_manager_create_fake_certificate(sdk):
220
241
 
221
242
  service = sdk.billing.trading_parties.mandatario_manager
222
243
  certificate = service.create_fake_certificate(
223
- Mandatario(rut='76192083-9', nombre='SASCO SpA'),
244
+ Mandatario(run='76192083-9', nombre='SASCO SpA'),
224
245
  )
225
246
 
226
247
  sent = json.loads(route.calls.last.request.content)['parameters']
227
248
  assert sent['mandatario']['run'] == '76192083-9'
228
249
  assert certificate.certificate == 'CERT-PEM'
229
250
  assert certificate.private_key == 'PKEY-PEM'
230
- assert certificate.rut == '76192083-9'
231
- assert certificate.nombre == 'SASCO SpA'
251
+ assert certificate.id == '76192083-9'
252
+ assert certificate.name == 'SASCO SpA'
232
253
  assert certificate.email == 'sasco@example.com'
233
254
  assert certificate.is_active is True
234
255
  assert certificate.issuer == 'Derafu Test Certificate Authority'
@@ -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.example == _INPUT_DATA
511
559
  assert example.expected['Encabezado']['Totales']['MntTotal'] == 1190
512
560
 
513
561
 
@@ -538,7 +586,7 @@ def test_mandatario_manager_create_from_certificate(sdk):
538
586
  'certificate': 'cert-pem',
539
587
  'privateKey': 'key-pem',
540
588
  }
541
- assert mandatario.rut == '76192083-9'
589
+ assert mandatario.run == '76192083-9'
542
590
  assert mandatario.nombre == 'SASCO SpA'
543
591
  assert mandatario.email == 'demo@sasco.example'
544
592
 
@@ -694,8 +742,8 @@ def test_certificate_loader_load_sends_base64_and_decodes_metadata(sdk):
694
742
  assert (
695
743
  base64.b64decode(sent['certificate']['data']) == b'contenido-del-p12'
696
744
  )
697
- assert loaded.rut == '76192083-9'
698
- assert loaded.nombre == 'SASCO SpA'
745
+ assert loaded.id == '76192083-9'
746
+ assert loaded.name == 'SASCO SpA'
699
747
  assert loaded.email == 'demo@sasco.example'
700
748
  assert loaded.is_active is True
701
749
  assert loaded.certificate == 'CERT-PEM'
@@ -990,7 +1038,7 @@ def test_sii_rcv_get_document_sii_reception_date_parses_the_datetime(sdk):
990
1038
  certificate=Certificate(certificate='cert-pem', private_key='key-pem'),
991
1039
  )
992
1040
 
993
- assert result.reception_date.isoformat() == '2025-01-02T10:30:00'
1041
+ assert result.fecha_recepcion_sii.isoformat() == '2025-01-02T10:30:00'
994
1042
  assert result.raw == {'fecha_recepcion_sii': '2025-01-02 10:30:00'}
995
1043
 
996
1044
 
@@ -1106,7 +1154,7 @@ def test_aec_build_sends_cedente_cesionario_cesion(sdk):
1106
1154
  }
1107
1155
  assert sent['cesionario']['RazonSocial'] == 'Factoring S.A.'
1108
1156
  assert sent['cesion']['MontoCesion'] == 119000
1109
- assert result.datos['DocumentoAEC']['Caratula']['RutCedente'] == (
1157
+ assert result.aec['DocumentoAEC']['Caratula']['RutCedente'] == (
1110
1158
  '76192083-9'
1111
1159
  )
1112
1160
  assert result.xml_base64 == 'PEFFQz48L0FFQz4='
@@ -1170,11 +1218,11 @@ def test_document_loader_load_xml_decodes_the_full_bag(sdk):
1170
1218
 
1171
1219
  sent = json.loads(route.calls.last.request.content)['parameters']
1172
1220
  assert sent['xml'] == 'RFRFeG1s'
1173
- assert result.datos == {'Encabezado': {}}
1221
+ assert result.document == {'Encabezado': {}}
1174
1222
  assert result.document_type == {'codigo': 33, 'nombre': 'Factura'}
1175
- assert result.stamp_xml == '<TED/>'
1176
- assert result.extra is None
1177
- assert result.auth is None
1223
+ assert result.document_stamp == '<TED/>'
1224
+ assert result.document_extra is None
1225
+ assert result.document_auth is None
1178
1226
 
1179
1227
 
1180
1228
  _BOOK_BAG = {
@@ -1279,8 +1327,8 @@ def test_book_loader_load_decodes_book_type_caratula_and_detalle(sdk):
1279
1327
 
1280
1328
  # `book`/`book_auth` siempre vienen `None` desde `load()` (no
1281
1329
  # construye el libro) — ver docstring de `BookBag`.
1282
- assert result.datos is None
1283
- assert result.auth is None
1330
+ assert result.book is None
1331
+ assert result.book_auth is None
1284
1332
  assert result.book_type == {
1285
1333
  'codigo': 'libro_ventas',
1286
1334
  'nombre': 'Libro de ventas',
@@ -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)