Código fuente para apigatewaycl.api_client.sii.dte

#
# API Gateway: Cliente de API en Python.
# Copyright (C) API Gateway <https://www.apigateway.cl>
#
# Este programa es software libre: usted puede redistribuirlo y/o modificarlo
# bajo los términos de la GNU Lesser General Public License (LGPL) publicada
# por la Fundación para el Software Libre, ya sea la versión 3 de la Licencia,
# o (a su elección) cualquier versión posterior de la misma.
#
# Este programa se distribuye con la esperanza de que sea útil, pero SIN
# GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
# PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la GNU Lesser General
# Public License (LGPL) para obtener una información más detallada.
#
# Debería haber recibido una copia de la GNU Lesser General Public License
# (LGPL) junto a este programa. En caso contrario, consulte
# <http://www.gnu.org/licenses/lgpl.html>.
#

"""
Módulo para Documentos Tributarios Electrónicos (DTE) del SII.

Para más información sobre la API, consulte la `documentación completa
de los DTE
<https://www.apigateway.cl/docs>`_.
"""

from __future__ import annotations

from typing import Any, cast

from .. import ApiBase, ApiResponse


[documentos] class Contribuyentes(ApiBase): """ Cliente para los endpoints de contribuyentes de la API de API Gateway. Proporciona métodos para consultar la autorización de emisión de DTE de un contribuyente, sus datos privados y sus usuarios autorizados. :param str identificador: Identificador del contribuyente (opcional, sólo para los recursos que requieren autenticación). :param str clave: Clave del identificador (opcional). :param kwargs: Argumentos adicionales. """ def __init__( self, identificador: str | None = None, clave: str | None = None, **kwargs: str, ) -> None: """ Autentica con `identificador`/`clave` del contribuyente. Ambos son opcionales porque `autorizacion()` es un recurso público: no requiere credenciales del contribuyente. """ argumentos: dict[str, str] = dict(kwargs) if identificador and clave: argumentos['identificador'] = identificador argumentos['clave'] = clave super().__init__( **argumentos, # type: ignore[arg-type] )
[documentos] def autorizacion( self, rut: str, certificacion: bool | None = None, ) -> ApiResponse[dict[str, Any]]: """ Verifica si un contribuyente está autorizado para emitir DTE. Respuesta (ejemplo):: { "data": { "autorizado": true, "direccion_regional": "VI", "documentos": [ { "autorizado": "2016-03-01", "codigo": 39, "desautorizado": null, "descripcion": "BOLETA ELECTRONICA" } ], "razon_social": "API Gateway", "resolucion": {"fecha": "2014-08-22", "numero": 80}, "rut": "76192083-9", "software": "mercado" }, "metadata": {"timestamp": "..."} } :param str rut: RUT del contribuyente a verificar. :param bool certificacion: Indica si se consulta en ambiente de certificación (opcional). :return: Respuesta de la API, con `data` y `metadata`. En `data`, estado de autorización. :rtype: ApiResponse[dict[str, Any]] """ certificacion_flag = 1 if certificacion else 0 url = ( '/sii/dte/contribuyentes/autorizado/%(rut)s' '?certificacion=%(certificacion_flag)s' % {'rut': rut, 'certificacion_flag': certificacion_flag} ) response = self.client.get(url) return self._json(response)
[documentos] def datos( self, contribuyente: str, certificacion: str | None = None, ) -> ApiResponse[dict[str, Any]]: """ Datos privados del contribuyente autenticado. Incluye resolución de autorización, correos y software de facturación declarado. Respuesta (ejemplo):: { "data": { "rut": "12345678-9", "autorizado": "2025-02-25", "razon_social": "RAZON SOCIAL SPA", "resolucion": {"numero": 99, "fecha": "2014-10-21"}, "emails": { "administrador": "admin@example.com", "sii": "sii@example.com", "intercambio": "intercambio@example.com" }, "software": {"nombre": "SII", "url": "www.example.com"} }, "metadata": {"timestamp": "..."} } :param str contribuyente: RUT del contribuyente. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, datos privados del contribuyente. :rtype: ApiResponse[dict[str, Any]] """ url = self._build_url( '/sii/dte/contribuyentes/datos/%(contribuyente)s' % {'contribuyente': contribuyente}, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return self._json(response)
[documentos] def set_datos( self, contribuyente: str, datos: dict[str, Any], certificacion: str | None = None, ) -> ApiResponse[dict[str, Any]]: """ Actualiza los datos privados del contribuyente (emails, software). Respuesta (ejemplo):: { "data": { "data": { "rut": "12345678-9", "autorizado": "2019-12-23", "razon_social": "RAZON SOCIAL SPA", "resolucion": {"numero": 0, "fecha": "2019-12-23"}, "emails": { "administrador": "admin@example.com", "sii": "sii@example.com", "intercambio": "dte@example.com" }, "software": {"nombre": "LIBREDTE", "url": "www.libredte.cl"} } }, "metadata": {"timestamp": "..."} } :param str contribuyente: RUT del contribuyente a actualizar. :param dict datos: Datos a actualizar (`emails`, `software`). :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, datos privados del contribuyente, ya actualizados. :rtype: ApiResponse[dict[str, Any]] """ url = self._build_url( '/sii/dte/contribuyentes/set_datos/%(contribuyente)s' % {'contribuyente': contribuyente}, certificacion=certificacion, ) body = {'auth': self._get_auth(), 'datos': datos} response = self.client.post(url, data=body) return self._json(response)
[documentos] def usuarios( self, rut: str, certificacion: str | None = None, ) -> ApiResponse[list[dict[str, Any]]]: """ Listado de usuarios autorizados de un contribuyente. Respuesta (ejemplo):: { "data": [ { "nombre": "Juan", "permisos": { "administrador": true, "anular_folios": true, "consultar": true, "enviar": true, "firmar": true, "...": "..." }, "run": "1-9" } ], "metadata": {"timestamp": "..."} } :param str rut: RUT del contribuyente a consultar. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, listado de usuarios, con nombre, RUN y permisos. :rtype: ApiResponse[list[dict[str, Any]]] """ url = self._build_url( '/sii/dte/contribuyentes/usuarios/%(rut)s' % {'rut': rut}, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return self._json(response)
[documentos] def set_usuario( self, contribuyente: str, usuario: dict[str, Any], certificacion: str | None = None, ) -> ApiResponse[list[dict[str, Any]]]: """ Asigna un usuario autorizado, o modifica sus permisos si ya existe. `usuario` lleva `run` y `permisos`, con las claves booleanas `administrador`, `solicitar_folios`, `anular_folios`, `firmar`, `enviar` y `consultar`; las que no vengan cuentan como `False`. Para eliminar un usuario se envían todos los permisos en `False`: no hay un recurso aparte para eliminar. Respuesta (ejemplo):: { "data": [ { "run": "66666666-6", "nombre": "Juan", "permisos": { "administrador": true, "solicitar_folios": true, "anular_folios": true, "firmar": true, "enviar": true, "...": "..." } } ], "metadata": {"timestamp": "..."} } :param str contribuyente: RUT del contribuyente a actualizar. :param dict usuario: Usuario a asignar (`run` y `permisos`). :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, la lista completa de usuarios autorizados después del cambio (no solo el enviado), con sus permisos. :rtype: ApiResponse[list[dict[str, Any]]] """ url = self._build_url( '/sii/dte/contribuyentes/set_usuario/%(contribuyente)s' % {'contribuyente': contribuyente}, certificacion=certificacion, ) body = {'auth': self._get_auth(), 'usuario': usuario} response = self.client.post(url, data=body) return self._json(response)
[documentos] def autorizacion_certificado( self, rut: str, certificacion: str | None = None, ) -> ApiResponse[dict[str, Any]]: """ Estado de autorización de un contribuyente, con certificado. A diferencia de `autorizacion()` (`GET`, pública), esta variante requiere certificado digital y agrega el correo electrónico de intercambio del contribuyente a la respuesta. Respuesta (ejemplo):: { "data": { "autorizado": true, "direccion_regional": "VI", "documentos": [ { "autorizado": "2015-09-01", "codigo": 33, "desautorizado": null, "descripcion": "FACTURA ELECTRONICA" } ], "emails": {"intercambio": {"email": "dte@example.com"}}, "razon_social": "API Gateway", "resolucion": {"fecha": "2014-08-22", "numero": 80}, "rut": "76192083-9", "software": "mercado" }, "metadata": {"timestamp": "..."} } :param str rut: RUT del contribuyente a consultar. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, autorización, resolución, dirección regional, software declarado, email de intercambio y documentos autorizados. :rtype: ApiResponse[dict[str, Any]] """ url = self._build_url( '/sii/dte/contribuyentes/autorizado/%(rut)s' % {'rut': rut}, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return self._json(response)
[documentos] def autorizados( self, certificacion: str | None = None, dia: str | None = None, formato: str | None = None, ) -> ApiResponse[list[dict[str, Any]]] | bytes: """ Descarga masiva de contribuyentes autorizados a emitir DTE. Descarga la base completa del SII (cercana a un millón de registros, cientos de MB, hasta 15 minutos). No es para consultas puntuales de un RUT — para eso usar `autorizacion()` o `autorizacion_certificado()`. Respuesta (ejemplo):: { "data": [ { "rut": "76192083-9", "razon_social": "API Gateway", "resolucion_numero": "80", "resolucion_fecha": "2014-08-22", "email": "dte@example.com", "url": "www.example.com" } ], "metadata": {"timestamp": "..."} } :param str certificacion: `'0'` producción, `'1'` certificación. :param str dia: Fecha de corte (AAAAMMDD o AAAA-MM-DD). :param str formato: `'json'`, `'csv'` o `'csv_sii'` (oficial del SII, sin transformar — recomendado y valor por defecto de la API). :return: Respuesta de la API, con `data` y `metadata`. En `data`, listado de contribuyentes autorizados (RUT, razón social, resolución, email de intercambio, URL). Con `formato='json'` se entrega ya decodificado; con los dos formatos CSV se entrega el archivo crudo, sin decodificar (`csv_sii` viene en ISO-8859-1). :rtype: ApiResponse[list[dict[str, Any]]] | bytes """ url = self._build_url( '/sii/dte/contribuyentes/autorizados', certificacion=certificacion, dia=dia, formato=formato, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) # Sólo `json` responde JSON. Los dos formatos CSV (incluído # `csv_sii`, el que aplica la API si no se pide otro) vienen # como texto plano y decodificarlos fallaría. if formato == 'json': return self._json(response) return response.content
[documentos] class Emitidos(ApiBase): """ Cliente específico para gestionar DTE emitidos. Permite verificar la validez y autenticidad de un DTE emitido. :param str identificador: Identificador del contribuyente. :param str clave: Clave del identificador. :param kwargs: Argumentos adicionales. """ def __init__( self, identificador: str, clave: str, **kwargs: str, ) -> None: """Autentica con `identificador`/`clave` del contribuyente.""" super().__init__( identificador=identificador, clave=clave, **kwargs, # type: ignore[arg-type] )
[documentos] def verificar( self, emisor: str, receptor: str, dte: int, folio: int, fecha: str, total: int, firma: str | None = None, certificacion: str | None = None, ) -> ApiResponse[dict[str, Any]]: """ Verifica la validez de un DTE emitido. Respuesta (ejemplo):: { "data": { "status": "DOK", "detalle": "Documento recibido por el SII. Datos ...", "track_id": 321421421521, "razon_social": "SASCO SPA" }, "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor del DTE. :param str receptor: RUT del receptor del DTE. :param int dte: Tipo de DTE. :param int folio: Número de folio del DTE. :param str fecha: Fecha de emisión del DTE. :param int total: Monto total del DTE. :param str firma: Firma electrónica del DTE (opcional). :param str certificacion: `'0'` producción, `'1'` certificación. Sólo aplica a documentos que no son boletas: las boletas (39 y 41) se verifican siempre en producción. :return: Respuesta de la API, con `data` y `metadata`. En `data`, resultado de la verificación del DTE. :rtype: ApiResponse[dict[str, Any]] """ url = self._build_url( '/sii/dte/emitidos/verificar', certificacion=certificacion, ) body = { 'auth': self._get_auth(), 'dte': { 'emisor': emisor, 'receptor': receptor, 'dte': dte, 'folio': folio, 'fecha': fecha, 'total': total, 'firma': firma, }, } response = self.client.post(url, data=body) return self._json(response)
[documentos] def estado_envio( self, emisor: str, track_id: int, certificacion: str | None = None, formato: str | None = None, ) -> ApiResponse[dict[str, Any]] | str: """ Estado del envío de un XML de DTE al SII. Solo consulta envíos de empresas a las que el usuario autenticado con certificado digital tenga acceso. Respuesta (ejemplo):: { "data": { "documentos": { "33": { "aceptados": 1, "dte": 33, "informados": 1, "rechazados": 0, "reparos": 0, "...": "..." } }, "emisor": "76192083-9", "estado": "Envio Procesado [EPR].", "track_id": "1234" }, "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor de los documentos. :param int track_id: Identificador del envío. :param str certificacion: `'0'` producción, `'1'` certificación. :param str formato: `'json'` o `'html'`. :return: Respuesta de la API, con `data` y `metadata`. En `data`, estado del envío y resumen de documentos por tipo de DTE. Con `formato='html'` se entrega el HTML del SII como `str`, sin `data` ni `metadata`. :rtype: ApiResponse[dict[str, Any]] | str """ url = self._build_url( '/sii/dte/emitidos/estado_envio/%(emisor)s/%(track_id)s' % {'emisor': emisor, 'track_id': track_id}, certificacion=certificacion, formato=formato, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) # Con `formato='html'` la API responde el HTML como una cadena # JSON, sin `data` ni `metadata`. if formato == 'html': return cast(str, response.json()) return self._json(response)
[documentos] class Iecv(ApiBase): """ Cliente para la Información Electrónica de Compras y Ventas (IECV). :param str identificador: Identificador del contribuyente. :param str clave: Clave del identificador. :param kwargs: Argumentos adicionales. """ def __init__( self, identificador: str, clave: str, **kwargs: str, ) -> None: """Autentica con `identificador`/`clave` del contribuyente.""" super().__init__( identificador=identificador, clave=clave, **kwargs, # type: ignore[arg-type] )
[documentos] def codigo_reemplazo( self, emisor: str, periodo: str, operacion: str, tipo: str, track_id: int, certificacion: str | None = None, ) -> ApiResponse[dict[str, Any]]: """ Código de reemplazo de un libro IECV, para poder rectificarlo. Solo para períodos de 201707 hacia atrás. Respuesta (ejemplo):: { "data": {"codigo_reemplazo": "1234567890"}, "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor de los documentos. :param str periodo: Período del registro (AAAAMM). :param str operacion: `'VENTA'` o `'COMPRA'`. :param str tipo: `'MENSUAL'` o `'RECTIFICA'`. :param int track_id: Identificador del envío del libro a reemplazar. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, código de reemplazo del libro. :rtype: ApiResponse[dict[str, Any]] """ url = self._build_url( '/sii/dte/iecv/codigo_reemplazo/%(emisor)s/%(periodo)s' '/%(operacion)s/%(tipo)s/%(track_id)s' % { 'emisor': emisor, 'periodo': periodo, 'operacion': operacion, 'tipo': tipo, 'track_id': track_id, }, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return self._json(response)