# XSLT de la cadena original — CFDI 4.0

La cadena original es la secuencia de datos que se firma para producir el sello del comprobante.
Sólo se obtiene aplicando la transformación oficial del SAT: no se construye a mano.

| Archivo | Origen |
|---|---|
| `cadenaoriginal_4_0.xslt` | `http://www.sat.gob.mx/sitio_internet/cfd/4/cadenaoriginal_4_0/cadenaoriginal_4_0.xslt` |
| `utilerias.xslt` | `http://www.sat.gob.mx/sitio_internet/cfd/2/cadenaoriginal_2_0/utilerias.xslt` |
| `sin_complemento.xslt` | Nuestro. Marcador de posición, ver abajo. |

## La única modificación: los `href` de los `xsl:include`

El archivo del SAT trae 33 `xsl:include` apuntando a URLs de `sat.gob.mx`. **Se reescribieron los
`href` a rutas relativas locales**; no se tocó ni una plantilla, ni un `xsl:template`, ni el orden
de los campos.

- El include de `utilerias.xslt` apunta al archivo local del mismo nombre.
- Los otros 32, todos de complementos (Carta Porte, Comercio Exterior, Nómina, Pagos…), apuntan a
  `sin_complemento.xslt`, que es un stylesheet vacío.

**Por qué es inocuo:** cada XSLT de complemento sólo aporta plantillas que hacen `match` sobre nodos
de su propio complemento. Este módulo emite CFDI 4.0 tipo ingreso **sin complementos**, así que esas
plantillas nunca se invocan. Verificado: la cadena original que produce esta versión local es
**idéntica, carácter a carácter**, a la que produce el archivo del SAT resolviendo los includes por
red. Hay una prueba que lo fija en `backend/tests/test_fin_stamping.py`.

## Por qué se hizo así, y no con un resolver

Porque **`libxslt` sale a internet a resolver los `xsl:include` aunque el parser de lxml se cree con
`no_network=True`**. Está comprobado: con el archivo original y sin resolver, la transformación
completa funciona, lo que sólo es posible si descargó `utilerias.xslt` de `sat.gob.mx` en ese
momento.

Eso es inaceptable aquí por tres razones: el timbrado dependería de que `sat.gob.mx` esté arriba y
responda rápido; la cadena original —el dato que se firma— vendría de una descarga no verificada en
tiempo de ejecución; y un cambio silencioso en el servidor del SAT cambiaría los sellos sin que
nadie lo note.

Se intentó primero con un `etree.Resolver` personalizado, que es lo que hace el sistema legado
(`CFDI.cs`, el bloque `SafeXsltResolver` comentado hacia la línea 8333). No funcionó: el resolver
también intercepta la resolución del documento principal, y devolver un stylesheet vacío ahí deja la
transformación sin plantillas y **la cadena original sale vacía, sin ningún error**. Un sello sobre
una cadena vacía es un CFDI que el SAT rechaza — o peor, un sello que parece válido y no lo es.

Con los `href` locales el problema desaparece de raíz: no hay nada que resolver fuera del directorio.

## Cómo actualizar estos archivos

1. Descarga el original del SAT (URLs de la tabla).
2. Reescribe los `href` de los `xsl:include`: el de `utilerias.xslt` al archivo local, el resto a
   `sin_complemento.xslt`.
3. Corre `pytest tests/test_fin_stamping.py`. La prueba de la cadena original tiene que seguir
   verde: si cambió el orden o el número de campos, el sello cambia y hay que revisarlo con Fiscal
   antes de subir nada.

## Si algún día se soporta un complemento

Trae **su** XSLT oficial, déjalo en este directorio y apunta el `href` de ese include concreto al
archivo real, en lugar de a `sin_complemento.xslt`. No basta con añadir el nodo al XML: sin su
plantilla, el complemento no entra en la cadena original y el sello sale mal.
