Por qué no debes editar un CAF al generar un DTE

 

Cuando desarrollamos un sistema de facturación electrónica en Chile, una de las piezas fundamentales para generar un DTE es el CAF (Código de Autorización de Folios) entregado por el SII.

A simple vista, el CAF es XML y puede resultar tentador tratarlo igual que cualquier otro XML de nuestra aplicación: leer sus nodos, normalizar textos, limitar largos o incluso cambiar su codificación.

Ese es un error.

Me encontré con este problema mientras trabajaba en la generación y envío de DTE. En mi caso, el error concreto fue aparentemente muy pequeño: estaba acortando el contenido del nodo RS del CAF.

El resultado fue suficiente para invalidar la información firmada.

¿Qué contiene el CAF?

Dentro del CAF encontramos información que autoriza a un determinado contribuyente a utilizar un rango de folios para un tipo específico de documento.

Simplificando su estructura, podemos encontrar algo parecido a:

<CAF version="1.0">
    <DA>
        <RE>76040308-3</RE>
        <RS>RAZON SOCIAL DEL EMISOR</RS>
        <TD>33</TD>

        <RNG>
            <D>678</D>
            <H>678</H>
        </RNG>

        <FA>2026-09-17</FA>

        <RSAPK>
            ...
        </RSAPK>

        <IDK>100</IDK>
    </DA>

    <FRMA algoritmo="SHA1withRSA">
        ...
    </FRMA>
</CAF>

En un DTE real generado durante mis pruebas se puede observar esta misma estructura: dentro de DA aparecen el RUT, la razón social (RS), el tipo de documento, el rango autorizado y posteriormente la firma FRMA del CAF.

La parte importante es comprender que no estamos frente a datos que nuestra aplicación pueda modificar libremente.

El CAF contiene información firmada.

El error: acortar el nodo RS

En mi aplicación tenía funciones destinadas a normalizar los textos utilizados para construir un DTE.

Entre otras cosas, podía limitar el largo de determinados campos.

Conceptualmente tenía una operación similar a esta:

String razonSocial = obtenerRazonSocial();

razonSocial =
        TextoUtils.acortarTexto(razonSocial, largoMaximo);

Este tipo de validación puede ser perfectamente válida cuando estamos trabajando con información proveniente de nuestra propia base de datos.

Por ejemplo:

Cliente
Producto
Dirección
Giro
Descripción
        ↓
validar
        ↓
normalizar
        ↓
generar DTE

El problema apareció porque estaba aplicando esa misma lógica sobre el nodo RS obtenido desde el CAF.

Es decir:

CAF
 ↓
leer <RS>
 ↓
acortarTexto()
 ↓
incorporar al TED

Y eso no debe hacerse.

¿Por qué no puedo simplemente acortar la razón social?

Porque el contenido del CAF está firmado.

Supongamos que el CAF contiene:

<RS>ESTEBAN GABRIEL GUENUL ALMONACID SERVIC</RS>

Si mi aplicación decide transformarlo en:

<RS>ESTEBAN GABRIEL GUENUL ALMONACID</RS>

para nosotros puede parecer prácticamente la misma información.

Criptográficamente no lo es.

No importa que ambas cadenas representen al mismo contribuyente. Tampoco importa que solamente hayamos eliminado algunos caracteres del final.

Los datos ya no son los mismos que fueron firmados.

Ese fue el origen del problema.

El CAF no debe pasar por las normalizaciones del DTE

La conclusión que saqué fue separar claramente dos procesos.

Los datos que controla mi aplicación pueden necesitar:

validación
normalización
control de largos
tratamiento de caracteres

Todo eso debe ocurrir antes de construir y firmar el documento correspondiente.

El CAF es diferente.

El tratamiento debería ser conceptualmente:

CAF entregado
      ↓
leer
      ↓
utilizar
      ↓
incorporar al TED
      ↓
NO MODIFICAR

No debería pasar por las funciones genéricas utilizadas para limpiar los datos provenientes de la aplicación.

El problema no estaba en acortarTexto()

Esta fue probablemente la parte más interesante del error.

La función que acortaba el texto no estaba necesariamente mal.

El problema era dónde la estaba utilizando.

Si tengo una descripción de producto que supera el largo permitido, puede ser necesario validarla o limitarla según corresponda antes de generar el documento.

Pero no puedo aplicar automáticamente esa misma política sobre información que recibí dentro de un CAF.

En otras palabras:

Datos propios
    ↓
normalizar
    ↓
construir XML

no es equivalente a:

CAF firmado
    ↓
normalizar     ← NO
    ↓
construir TED

Reutilizar código no significa que una misma transformación sea válida para cualquier origen de datos.

Otra precaución: no asumir UTF-8

En mi caso el problema no fue UTF-8. El error que encontré fue haber acortado el nodo RS.

Sin embargo, revisando el manejo del CAF aparece otra precaución que vale la pena mencionar: no deberíamos asumir arbitrariamente que cualquier XML que recibimos debe ser interpretado o reescrito como UTF-8.

La codificación declarada por el documento debe respetarse.

Por ejemplo, el EnvioDTE generado durante estas pruebas comienza con:

<?xml version="1.0" encoding="ISO-8859-1"?>

Por lo tanto, algo como esto no debería utilizarse automáticamente sobre cualquier XML recibido:

new InputStreamReader(
    inputStream,
    StandardCharsets.UTF_8
);

si desconocemos o estamos ignorando deliberadamente la codificación original.

Especialmente cuando trabajamos con información firmada, no tiene sentido realizar conversiones innecesarias del contenido.

No fue el bug que encontré en esta ocasión, pero responde al mismo principio: conservar aquello que recibimos firmado.

El TED también contiene el CAF

Esto se vuelve especialmente relevante al construir el timbre electrónico.

En el XML generado podemos observar que el TED contiene el DD y dentro de éste se incorpora el CAF utilizado para el folio. En el mismo bloque aparecen posteriormente TSTED y la firma FRMT.

Por eso debemos tener claro qué información estamos generando nosotros y cuál estamos incorporando desde una autorización existente.

No necesitamos "mejorar" el CAF.

Necesitamos preservarlo.

Una regla que me quedó después de encontrar el error

Después de corregir este problema, la regla que utilizo es bastante sencilla:

Si un contenido ya viene firmado, mi aplicación no debe intentar corregirlo.

No acortarlo.

No normalizarlo.

No cambiar sus textos porque nuestra base de datos tenga otra versión de la razón social.

No modificarlo para que quede visualmente más bonito.

El CAF debe ser tratado como información que ya tiene una integridad criptográfica que debemos respetar.

Conclusión

Al implementar facturación electrónica, algunos de los errores más difíciles de encontrar no necesariamente están en grandes algoritmos.

En este caso, el problema estaba en una operación aparentemente inocente: acortar un String.

La función funcionaba correctamente.

El XML seguía teniendo una estructura aparentemente correcta.

La razón social seguía siendo reconocible.

Pero había cambiado un dato perteneciente al CAF.

Y cuando trabajamos con contenido firmado, cambiar un dato significa que ya no estamos trabajando con exactamente el mismo contenido que fue firmado.

Por eso conviene mantener una separación muy clara:

DATOS DE MI SISTEMA
        ↓
validar
normalizar
adaptar
        ↓
generar DTE


CAF
        ↓
leer
        ↓
utilizar
        ↓
NO MODIFICAR

Esa pequeña diferencia conceptual puede ahorrar bastante tiempo buscando errores en la generación del timbre.

En mi caso, la solución terminó siendo más sencilla que el problema:

dejar el CAF tal como estaba.

Comentarios

Entradas populares de este blog

Boleta Electrónica en Chile: Los 3 Ambientes del SII para Desarrolladores

Configurando Servlets y JSP en Jetty

GUIA GENERAL DE GENERACION DE DOCUMENTOS TRIBUTARIOS ELECTRONICOS