diff --git a/src/cl_sii/cte/data_models.py b/src/cl_sii/cte/data_models.py index 91155774..c38b1809 100644 --- a/src/cl_sii/cte/data_models.py +++ b/src/cl_sii/cte/data_models.py @@ -1,6 +1,8 @@ from __future__ import annotations from collections.abc import Sequence +from datetime import date +from typing import Optional import pydantic @@ -38,3 +40,49 @@ class LegalRepresentative: """ Fecha de incorporación. """ + + +@pydantic.dataclasses.dataclass( + frozen=True, + config=pydantic.ConfigDict( + arbitrary_types_allowed=True, + extra='forbid', + ), +) +class TaxpayerData: + start_of_activities_date: Optional[date] + """ + Fecha de inicio de actividades. + """ + economic_activities: str + """ + Actividades Económicas + """ + tax_category: str + """ + Categoría Tributaria + """ + address: str + """ + Domicilio + """ + branches: Sequence[str] + """ + Sucursales + """ + last_filed_documents: Sequence[LastFiledDocument] + """ + Últimos documentos timbrados + """ + tax_observations: Optional[str] = None + """ + Observaciones tributarias + """ + + +@pydantic.dataclasses.dataclass( + frozen=True, +) +class LastFiledDocument: + name: str + date: date diff --git a/src/cl_sii/cte/parsers.py b/src/cl_sii/cte/parsers.py index bbcd6a5a..5242cfef 100644 --- a/src/cl_sii/cte/parsers.py +++ b/src/cl_sii/cte/parsers.py @@ -1,8 +1,10 @@ from __future__ import annotations +from datetime import datetime + from bs4 import BeautifulSoup -from .data_models import LegalRepresentative, TaxpayerProvidedInfo +from .data_models import LastFiledDocument, LegalRepresentative, TaxpayerData, TaxpayerProvidedInfo def parse_taxpayer_provided_info(html_content: str) -> TaxpayerProvidedInfo: @@ -89,3 +91,88 @@ def parse_taxpayer_provided_info(html_content: str) -> TaxpayerProvidedInfo: company_formation=company_formation, participation_in_existing_companies=participation_in_companies, ) + + +def parse_taxpayer_data(html_content: str) -> TaxpayerData: + """ + Parse the CTE HTML content to extract the content of the section: + "Datos del Contribuyente" + + Args: + html_content: HTML string containing the taxpayer information table + + Returns: + TaxpayerData instance with the parsed data + """ + soup = BeautifulSoup(html_content, 'html.parser') + table = soup.find('table', id='tbl_dbcontribuyente') + if not table: + raise ValueError("Could not find 'Datos del Contribuyente' table in HTML") + + fecha_inicio_elem = table.find(id='td_fecha_inicio') # type: ignore[attr-defined] + if fecha_inicio_elem: + start_of_activities_date = ( + datetime.strptime(fecha_inicio_elem.get_text(strip=True), "%d-%m-%Y").date() + if fecha_inicio_elem.get_text(strip=True) + else None + ) + else: + start_of_activities_date = None + + actividades_elem = table.find(id='td_actividades') # type: ignore[attr-defined] + if actividades_elem: + economic_activities = actividades_elem.get_text(separator="\n", strip=True) + else: + economic_activities = "" + + categoria_elem = table.find(id='td_categoria') # type: ignore[attr-defined] + if categoria_elem: + tax_category = categoria_elem.get_text(strip=True) + else: + tax_category = "" + + domicilio_elem = table.find(id='td_domicilio') # type: ignore[attr-defined] + if domicilio_elem: + address = domicilio_elem.get_text(strip=True) + else: + address = "" + + # Sucursales + branches = [] + sucursales_row = table.find( # type: ignore[attr-defined] + 'td', + string=lambda s: s and 'Sucursales:' in s, + ) + if sucursales_row: + sucursales_td = sucursales_row.find_next_sibling('td') + if sucursales_td: + branches_text = sucursales_td.get_text(separator="\n", strip=True) + branches = [b for b in branches_text.split("\n") if b] + + # Últimos documentos timbrados + last_filed_documents = [] + tim_nombre_elem = table.find(id='td_tim_nombre') # type: ignore[attr-defined] + tim_fecha_elem = table.find(id='td_tim_fecha') # type: ignore[attr-defined] + if tim_nombre_elem and tim_fecha_elem: + names = tim_nombre_elem.get_text(separator="\n", strip=True).split("\n") + dates = tim_fecha_elem.get_text(separator="\n", strip=True).split("\n") + for name, date_str in zip(names, dates): + if name and date_str: + doc_date = datetime.strptime(date_str, "%d-%m-%Y").date() + last_filed_documents.append(LastFiledDocument(name=name, date=doc_date)) + + # Observaciones tributarias + tax_observations = None + observaciones_elem = table.find(id='td_observaciones') # type: ignore[attr-defined] + if observaciones_elem: + tax_observations = observaciones_elem.get_text(strip=True) + + return TaxpayerData( + start_of_activities_date=start_of_activities_date, + economic_activities=economic_activities, + tax_category=tax_category, + address=address, + branches=branches, + last_filed_documents=last_filed_documents, + tax_observations=tax_observations, + ) diff --git a/src/tests/test_cte_parsers.py b/src/tests/test_cte_parsers.py index c2fb8005..ab1c5d4c 100644 --- a/src/tests/test_cte_parsers.py +++ b/src/tests/test_cte_parsers.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import date from unittest import TestCase from cl_sii.cte import data_models, parsers @@ -49,3 +50,58 @@ def test_parse_taxpayer_provided_info(self) -> None: assert_raises_cm.exception.args, ("Could not find taxpayer information table in HTML",), ) + + def test_parse_taxpayer_data(self) -> None: + html_content = read_test_file_str_utf8('test_data/sii-cte/cte_empty_f29.html') + with self.subTest("Parsing ok"): + result = parsers.parse_taxpayer_data(html_content) + expected_obj = data_models.TaxpayerData( + start_of_activities_date=date(2023, 11, 15), + economic_activities=( + "SERVICIOS DE ASESORIA Y CONSULTORIA EN MATERIA DE ADMINISTRACION DE EMPRESAS " + "Y OTROS SERVICIOS DE ASESORIA ADMINISTRATIVA Y DE NEGOCIOS N.C.P.\n" + "ACTIVIDADES DE OTRAS ORGANIZACIONES EMPRESARIALES N.C.P.\n" + "OTRAS ACTIVIDADES DE SERVICIOS PERSONALES N.C.P." + ), + tax_category="Primera categoría", + address="AV REAL, LAS CONDES", + branches=[], + last_filed_documents=[ + data_models.LastFiledDocument( + name="FACTURA ELECTRONICA", date=date(2025, 7, 24) + ), + data_models.LastFiledDocument( + name="FACTURA NO AFECTA O EXENTA ELECTRONICA", date=date(2025, 7, 17) + ), + data_models.LastFiledDocument( + name="GUIA DESPACHO ELECTRONICA", date=date(2025, 5, 14) + ), + data_models.LastFiledDocument( + name="NOTA CREDITO ELECTRONICA", date=date(2025, 7, 18) + ), + ], + tax_observations="No tiene observaciones.", + ) + self.assertEqual(result, expected_obj) + + with self.subTest("Parsing empty content"): + with self.assertRaises(ValueError) as assert_raises_cm: + parsers.parse_taxpayer_data("") + self.assertEqual( + assert_raises_cm.exception.args, + ("Could not find 'Datos del Contribuyente' table in HTML",), + ) + + with self.subTest("Parsing content with empty table"): + html_content = read_test_file_str_utf8('test_data/sii-cte/cte_empty_table.html') + result = parsers.parse_taxpayer_data(html_content) + expected_obj = data_models.TaxpayerData( + start_of_activities_date=None, + economic_activities="", + tax_category="", + address="", + branches=[], + last_filed_documents=[], + tax_observations=None, + ) + self.assertEqual(result, expected_obj) diff --git a/src/tests/test_data/sii-cte/cte_empty_f29.html b/src/tests/test_data/sii-cte/cte_empty_f29.html new file mode 100644 index 00000000..e3189952 --- /dev/null +++ b/src/tests/test_data/sii-cte/cte_empty_f29.html @@ -0,0 +1,1141 @@ +
+| + |
|
+ Revise los datos contenidos en esta Carpeta Tributaria + Electrónica y, si están correctos, seleccione el botón "Continuar". Si detecta + información incorrecta, reporte esta situación llamando a nuestra Mesa de Ayuda + Telefónica, al (02) 395 11 15, o ingresando a nuestra página Web, www.sii.cl, menú + Contáctenos, opción Problemas y quejas. + |
+
|
+ CARPETA TRIBUTARIA ELECTRÓNICA PARA SOLICITAR CRÉDITOS + |
+
|
+ + |
+
|
+
+ Importante: Esta información es válida para la fecha y hora en que se generó la carpeta. |
+
|
+ + |
+
| Nombre del emisor: | +INVERSIONES SPA | +
| RUT del emisor: | +47797573 - 9 | +
| Fecha de generación de la carpeta: | +25/07/2025 07:21 | +
| Datos del Contribuyente + | +||
| Fecha de Inicio de Actividades: + | +15-11-2023 | +|
| Actividades Económicas: | + SERVICIOS DE ASESORIA Y CONSULTORIA EN MATERIA DE ADMINISTRACION DE EMPRESAS Y OTROS SERVICIOS DE ASESORIA ADMINISTRATIVA Y DE NEGOCIOS N.C.P. ACTIVIDADES DE OTRAS ORGANIZACIONES EMPRESARIALES N.C.P. OTRAS ACTIVIDADES DE SERVICIOS PERSONALES N.C.P. + |
+ |
| Categoría tributaria: | +Primera categoría | +|
| Domicilio: | +AV REAL, LAS CONDES | +|
| Sucursales: | ++ | |
| Últimos documentos timbrados: | +FACTURA ELECTRONICA FACTURA NO AFECTA O EXENTA ELECTRONICA GUIA DESPACHO ELECTRONICA NOTA CREDITO ELECTRONICA + |
+ 24-07-2025 17-07-2025 14-05-2025 18-07-2025 + |
+
| Observaciones tributarias: | +
+
+
+ No tiene observaciones. + + |
+ |
| Información proporcionada por el contribuyente para fines tributarios (1) + | +|||
| + | Nombre o Razón Social + | +RUT | +Fecha de Incorporación + | +
| Representante(s) Legal(es) | ++ | + | + |
| + | DAVID USUARIO DE PRUEBA | +76354771-K | +20-09-2023 | +
| + | JAVIERA USUARIO DE PRUEBA | +38855667-6 | +20-09-2023 + | +
| Conformación de la sociedad | ++ | + | + |
| + | JAVIERA USUARIO DE PRUEBA | +38855667-6 | +20-09-2023 | +
| + | MARÍA USUARIO DE PRUEBA | +34413183-k | +23-02-2024 | +
| Participación en sociedades vigentes(2) + | ++ | + | + |
| + | - No existen sociedades para el RUT - | +||
(1): Información declarada por el contribuyente y que puede haber sufrido modificaciones.
+ |
+ |||
| Propiedades y Bienes Raíces (3) + | +|||||||
| Comuna | +Rol | +Dirección | +Destino | +Avalúo Fiscal | +Cuotas vencidas por pagar | +Cuotas vigentes por pagar | +Condición (4) |
+
| - No se registra información para este RUT - + | +|||||||
|
+
+ (3): La presente información no acredita dominio de una propiedad.
+ |
+ |||||||
| Boletas de Honorarios Electrónicas (5) + | +|||
| Períodos | +Honorario bruto | +Retención de terceros | +PPM de contribuyente | +
| - No se registran Boletas de Honorarios Electrónicas emitidas en los últimos 12 meses - + | +|||
|
+ (5): Además de las Boletas de Honorarios Electrónicas, un contribuyente puede tener boletas de honorarios emitidas en papel, cuyo detalle no está disponible en forma electrónica. + + |
+ |||
| Declaraciones de IVA (F29) + | +|
| JULIO 2025 | +1 / 1 | +
|
+
+
+ + - No se registra declaración para este período - + + + + |
+ |
| + |
|
+ Revise los datos contenidos en esta Carpeta Tributaria + Electrónica y, si están correctos, seleccione el botón "Continuar". Si detecta + información incorrecta, reporte esta situación llamando a nuestra Mesa de Ayuda + Telefónica, al (02) 395 11 15, o ingresando a nuestra página Web, www.sii.cl, menú + Contáctenos, opción Problemas y quejas. + |
+
|
+ CARPETA TRIBUTARIA ELECTRÓNICA PARA SOLICITAR CRÉDITOS + |
+
|
+ + |
+
|
+
+ Importante: Esta información es válida para la fecha y hora en que se generó la carpeta. |
+
|
+ + |
+
| Nombre del emisor: | +INVERSIONES SPA | +
| RUT del emisor: | +47797573 - 9 | +
| Fecha de generación de la carpeta: | +25/07/2025 07:21 | +
| Datos del Contribuyente + | +||
| Fecha de Inicio de Actividades: + | +||
| Actividades Económicas: | + +||
| Categoría tributaria: | +||
| Domicilio: | +||
| Sucursales: | +||
| Últimos documentos timbrados: | + +||
| Observaciones tributarias: | +||
| Información proporcionada por el contribuyente para fines tributarios (1) + | +|||
| + | Nombre o Razón Social + | +RUT | +Fecha de Incorporación + | +
| Representante(s) Legal(es) | ++ | + | + |
| + | + | + | + |
| + | + | + | + |
| Conformación de la sociedad | ++ | + | + |
| + | + | + | + |
| + | + | + | + |
| Participación en sociedades vigentes(2) + | ++ | + | + |
| + | - No existen sociedades para el RUT - | +||
(1): Información declarada por el contribuyente y que puede haber sufrido modificaciones.
+ |
+ |||
| Propiedades y Bienes Raíces (3) + | +|||||||
| Comuna | +Rol | +Dirección | +Destino | +Avalúo Fiscal | +Cuotas vencidas por pagar | +Cuotas vigentes por pagar | +Condición (4) |
+
| - No se registra información para este RUT - + | +|||||||
|
+
+ (3): La presente información no acredita dominio de una propiedad.
+ |
+ |||||||
| Boletas de Honorarios Electrónicas (5) + | +|||
| Períodos | +Honorario bruto | +Retención de terceros | +PPM de contribuyente | +
| - No se registran Boletas de Honorarios Electrónicas emitidas en los últimos 12 meses - + | +|||
|
+ (5): Además de las Boletas de Honorarios Electrónicas, un contribuyente puede tener boletas de honorarios emitidas en papel, cuyo detalle no está disponible en forma electrónica. + + |
+ |||
| Declaraciones de IVA (F29) + | +|
| JULIO 2025 | +1 / 1 | +
|
+
+
+ + - No se registra declaración para este período - + + + + |
+ |
| Declaraciones de Renta (F22) + | +|
| Año Tributario 2025 | +1 / 3 | +
| + | |
|
+
+
+ + - No se registra declaración para este período - + + + + |
+ |
| Año Tributario 2024 | +2 / 3 | +
| + | |
|
+
+
+ + - No se registra declaración para este período - + + + + |
+ |
| Año Tributario 2023 | +3 / 3 | +
| + | |
|
+
+
+ + - No se registra declaración para este período - + + + + |
+ |