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

#
# 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 el Código de Asignación de Folios (CAF) del SII.

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

from __future__ import annotations

from typing import Any, cast

from .. import ApiBase, ApiResponse


[documentos] class Caf(ApiBase): """ Cliente para el Código de Asignación de Folios (CAF) de la API. Provee métodos para consultar, solicitar y anular folios. :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 estado_timbraje( self, emisor: str, dte: int, certificacion: str | None = None, ) -> ApiResponse[dict[str, Any]]: """ Estado de timbraje de un tipo de DTE. Folios timbrables y observaciones del SII sobre el contribuyente. Solo consulta, no solicita folios. Respuesta (ejemplo):: { "data": { "dte": { "codigo": 33, "limitado": true, "maximo_autorizado": 100, "folios_disponibles": 20, "timbraje_permitido": 80 }, "contribuyente": { "observado": false, "glosa": null, "observaciones": [] } }, "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor. :param int dte: Código del tipo de documento. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, datos del tipo de DTE y situación del contribuyente. :rtype: ApiResponse[dict[str, Any]] """ url = self._build_url( '/sii/dte/caf/estado_timbraje/%(emisor)s/%(dte)s' % {'emisor': emisor, 'dte': dte}, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return self._json(response)
[documentos] def solicitar( self, emisor: str, dte: int, cantidad: int, certificacion: str | None = None, ) -> bytes: """ Solicita un nuevo CAF (folios) al SII. La respuesta es el XML del CAF, no un objeto JSON. :param str emisor: RUT del emisor del CAF. :param int dte: Código del tipo de documento. :param int cantidad: Cantidad de folios a solicitar. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Contenido del XML del CAF. :rtype: bytes """ url = self._build_url( '/sii/dte/caf/solicitar/%(emisor)s/%(dte)s/%(cantidad)s' % {'emisor': emisor, 'dte': dte, 'cantidad': cantidad}, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return response.content
[documentos] def xml( self, emisor: str, dte: int, folio_inicial: int, folio_final: int, fecha_autorizacion: str, certificacion: str | None = None, ) -> bytes: """ Obtiene el XML de un CAF ya solicitado. :param str emisor: RUT del emisor del CAF. :param int dte: Código del tipo de documento. :param int folio_inicial: Folio inicial del CAF. :param int folio_final: Folio final del CAF. :param str fecha_autorizacion: Fecha de autorización del CAF. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Contenido del XML del CAF. :rtype: bytes """ url = self._build_url( '/sii/dte/caf/xml/%(emisor)s/%(dte)s/%(folio_inicial)s' '/%(folio_final)s/%(fecha_autorizacion)s' % { 'emisor': emisor, 'dte': dte, 'folio_inicial': folio_inicial, 'folio_final': folio_final, 'fecha_autorizacion': fecha_autorizacion, }, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return response.content
[documentos] def estado( self, emisor: str, dte: int, folio: int, certificacion: str | None = None, formato: str | None = None, ) -> ApiResponse[dict[str, Any]] | str: """ Estado de un folio en el SII (estado, glosa, track id de envío). Respuesta (ejemplo):: { "data": { "estado": "recibido", "estado_glosa": "Documento recibido por el SII", "track_id": "231986674" }, "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor del folio. :param int dte: Código del tipo de documento. :param int folio: Folio a consultar. :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 folio. 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/caf/estado/%(emisor)s/%(dte)s/%(folio)s' % {'emisor': emisor, 'dte': dte, 'folio': folio}, 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] def anular( self, emisor: str, dte: int, folio_inicial: int, folio_final: int, certificacion: str | None = None, formato: str | None = None, ) -> ApiResponse[dict[str, Any]] | str: """ Anula un rango de folios ya solicitados al SII. Respuesta (ejemplo):: { "data": { "emisor": "76192083-9", "fecha_anulacion": "2024-01-15", "dte": 39, "folio_inicial": 707, "folio_final": 707, "usuario": "12345678-9" }, "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor. :param int dte: Código del tipo de documento. :param int folio_inicial: Primer folio a anular. :param int folio_final: Último folio a anular. :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`, datos de la anulación (fecha, usuario, rango). 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/caf/anular/%(emisor)s/%(dte)s/%(folio_inicial)s' '/%(folio_final)s' % { 'emisor': emisor, 'dte': dte, 'folio_inicial': folio_inicial, 'folio_final': folio_final, }, 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] def solicitudes( self, emisor: str, dte: int, pagina: int, certificacion: str | None = None, formato: str | None = None, ) -> ApiResponse[list[dict[str, Any]]] | str: """ Listado paginado de solicitudes de CAF de un emisor. `pagina` parte en `1` — usar `metadata.siguiente_pagina` de la respuesta (`None` si es la última) para paginar. Respuesta (ejemplo):: { "data": [ { "inicial": 1022, "final": 1022, "cantidad": 1, "fecha": "2025-09-24", "mandatario": "NOMBRE MANDATARIO 1" } ], "metadata": {"timestamp": "...", "siguiente_pagina": 2} } :param str emisor: RUT del emisor del CAF. :param int dte: Código del tipo de documento. :param int pagina: Página a consultar, partiendo desde `1`. :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`, el listado de solicitudes; en `metadata`, `siguiente_pagina` (`None` si es la última). Con `formato='html'` se entrega el HTML del SII como `str`, sin `data` ni `metadata`. :rtype: ApiResponse[list[dict[str, Any]]] | str """ url = self._build_url( '/sii/dte/caf/solicitudes/%(emisor)s/%(dte)s' % {'emisor': emisor, 'dte': dte}, pagina=pagina, 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] def estados( self, emisor: str, dte: int, folio_inicial: int, folio_final: int, estado: str, certificacion: str | None = None, ) -> ApiResponse[list[dict[str, Any]]]: """ Estados de un rango de folios en el SII, agrupados por tramos. Respuesta (ejemplo):: { "data": [{"inicial": 1002, "final": 1004, "cantidad": 3}], "metadata": {"timestamp": "..."} } :param str emisor: RUT del emisor. :param int dte: Código del tipo de documento. :param int folio_inicial: Primer folio del rango. :param int folio_final: Último folio del rango. :param str estado: Estado de los folios a consultar: `recibidos`, `anulados` o `pendientes`. :param str certificacion: `'0'` producción, `'1'` certificación. :return: Respuesta de la API, con `data` y `metadata`. En `data`, listado de tramos (inicial/final/cantidad). :rtype: ApiResponse[list[dict[str, Any]]] """ url = self._build_url( '/sii/dte/caf/estados/%(emisor)s/%(dte)s/%(folio_inicial)s' '/%(folio_final)s/%(estado)s' % { 'emisor': emisor, 'dte': dte, 'folio_inicial': folio_inicial, 'folio_final': folio_final, 'estado': estado, }, certificacion=certificacion, ) body = {'auth': self._get_auth()} response = self.client.post(url, data=body) return self._json(response)