Guía de pruebas de certificación y facturación electrónica

Esta guía valida el flujo completo de api-factura contra SII, desde la provisión de una empresa hasta la consulta de estado de un DTE. Se ejecuta directamente contra la API en http://localhost:9005; el Apéndice B cubre la capa BFF de Sapira.

Alcance: ejecute los pasos 1 a 5 en cualquier ambiente de desarrollo con datos de prueba. Los pasos 6 a 10 contactan SII y requieren credenciales tributarias válidas de certificación. No reutilice certificados, CAF ni RUT de producción para pruebas.

Convenciones y preparación

api-factura no tiene prefijo /api/v1. Swagger está disponible en http://localhost:9005/api.

Defina las variables de la sesión de terminal:

export BASE_URL=http://localhost:9005
export OAUTH_CLIENT_ID='api-sapira'
export OAUTH_CLIENT_SECRET='secreto-del-cliente-oauth'
export SAPIRA_COMPANY_ID='uuid-de-la-empresa-en-sapira'
export HOLDING_ID='uuid-del-holding-en-sapira'
export TEST_EMAIL='sii.certificacion@example.test'
export TEST_PASSWORD='Password123!'
export CERTIFICATE_FILE='/ruta/segura/certificado.pfx'
export CERTIFICATE_PASSWORD='clave-del-certificado'
export CAF_FILE='/ruta/segura/CAF_33.xml'

Las respuestas exitosas siguen el envoltorio { "status": true, "message": "...", "data": ... }. Conserve los identificadores retornados:

export EMPRESA_ID='id-mongodb-devuelto-por-provision'
export TOKEN='jwt-de-usuario-o-token-oauth'
export DTE_ID='id-mongodb-devuelto-por-createDte'
export TRACK_ID='trackId-devuelto-por-sendDte'

Para ver las respuestas con formato, reemplace curl por curl ... | jq; no imprima ni guarde tokens, contraseñas o certificados en repositorios, historiales compartidos o tickets.

Prueba 0: obtener token OAuth para una API consumidora

Objetivo: obtener un token de corta duración para una API o worker mediante OAuth2 client_credentials.

curl --fail-with-body -sS -X POST "$BASE_URL/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=$OAUTH_CLIENT_ID" \
  --data-urlencode "client_secret=$OAUTH_CLIENT_SECRET" \
  --data-urlencode "scope=factura:empresas:provision"

Respuesta esperada: access_token, token_type: "Bearer" y expires_in de 900 segundos o el valor configurado. Use el token retornado como TOKEN.

Criterio de éxito: un token con factura:empresas:provision permite la prueba 1; sin ese scope, la API responde 403.

Prueba 1: provisionar la empresa

Objetivo: crear o actualizar el vínculo idempotente entre una compañía externa y la empresa de api-factura.

Precondición: TOKEN OAuth incluye el scope factura:empresas:provision.

Request:

curl --fail-with-body -sS -X POST "$BASE_URL/empresas/provision" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "externalReference": {
      "companyId": "'"$SAPIRA_COMPANY_ID"'",
      "tenantId": "'"$HOLDING_ID"'"
    },
    "rut": "76123456-7",
    "razonSocial": "Empresa Certificacion SpA",
    "giro": "Desarrollo de software",
    "direccion": "Av. Providencia 1234",
    "comuna": "Providencia",
    "ciudad": "Santiago",
    "ambienteSII": "certificacion"
  }'

Respuesta esperada: status: true y data._id. Guarde ese valor como EMPRESA_ID.

Criterio de éxito: repetir el mismo request retorna el mismo _id; no crea una segunda empresa ni permite cambiar el tenantId del vínculo externo. Para una empresa directa, use el mismo valor para el integratorSlug del cliente OAuth y externalReference.companyId, y omita tenantId.

Troubleshooting: 401 indica token vencido o inválido; 403 indica scope ausente. Un error de validación indica que faltan campos obligatorios o que el RUT/UUID no tiene formato aceptado.

Prueba 2: registrar usuario y obtener JWT

Objetivo: asociar un usuario de prueba a la empresa y obtener el JWT que determina empresaId en las operaciones tributarias.

Precondición: EMPRESA_ID corresponde a la respuesta de la prueba 1. El empresaId enviado en posteriores bodies o headers no sustituye al claim del JWT.

Request de registro:

curl --fail-with-body -sS -X POST "$BASE_URL/auth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "'"$TEST_EMAIL"'",
    "password": "'"$TEST_PASSWORD"'",
    "nombre": "Operador",
    "apellido": "Certificacion",
    "empresaId": "'"$EMPRESA_ID"'"
  }'

Request de login:

curl --fail-with-body -sS -X POST "$BASE_URL/auth/login" \
  -H "Content-Type: application/json" \
  -d '{ "email": "'"$TEST_EMAIL"'", "password": "'"$TEST_PASSWORD"'" }'

Respuesta esperada: login retorna access_token y user.empresaId. Exporte access_token como TOKEN.

Criterio de éxito: GET /auth/profile con Authorization: Bearer $TOKEN devuelve el usuario vinculado al EMPRESA_ID.

Troubleshooting: si el correo ya existe, inicie sesión con el usuario existente, cree un correo de prueba distinto o use POST /auth/reset-password (email, newPassword) para definir una contraseña conocida. En producción ese endpoint responde 403 salvo que AUTH_PASSWORD_RESET_ENABLED=true. Un 401 en recursos SII suele indicar un token vencido o que el usuario no está vinculado a una empresa.

Prueba 3: configurar el perfil de certificación

Objetivo: guardar los datos que permiten armar la carátula de EnvioDTE y seleccionar el ambiente SII.

Precondición: JWT válido. Para empresa ya autorizada, use alreadyAuthorized: true, resolución real y solamente environment: "produccion" cuando SII haya habilitado la operación.

Request:

curl --fail-with-body -sS -X PATCH "$BASE_URL/sii/certification-profile" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "environment": "certificacion",
    "enabledDocumentTypes": [33],
    "resolutionNumber": 0,
    "resolutionDate": "2014-08-22",
    "siiContactEmail": "contacto@example.test",
    "authorizedSignerRut": "12345678-9",
    "alreadyAuthorized": false,
    "concurrencyMode": "sequential"
  }'

Respuesta esperada: perfil con los campos enviados y un objeto readiness.

Criterio de éxito: GET /sii/certification-profile muestra resolución, RUT firmante y ambiente. resolutionDate, resolutionNumber y authorizedSignerRut son necesarios al construir el envío.

Troubleshooting: 400 suele indicar tipos incorrectos: enabledDocumentTypes debe ser arreglo numérico y el correo debe tener formato válido. Verifique que los datos de resolución correspondan a los asignados por SII.

Prueba 4: cargar y verificar certificado

Objetivo: cargar el PFX/P12 del firmante para firmar solicitudes y DTE.

Precondición: certificado tributario vigente del RUT firmante y contraseña correcta. Nunca adjunte el archivo a la documentación ni lo registre en logs.

Request:

curl --fail-with-body -sS -X POST "$BASE_URL/certificates/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@$CERTIFICATE_FILE" \
  -F "certificateCin=12345678-9" \
  -F "password=$CERTIFICATE_PASSWORD"

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/certificates/$EMPRESA_ID"

Respuesta esperada: la carga retorna certificado procesado; el listado contiene al menos un certificado activo y su fecha de expiración.

Criterio de éxito: el certificado aparece para la empresa del JWT y puede emplearse en la prueba 6.

Troubleshooting: si falla la carga, confirme extensión .pfx/.p12, integridad del archivo y contraseña. Si la fecha está vencida, renueve el certificado antes de continuar.

Prueba 5: cargar CAF y comprobar folios

Objetivo: importar el CAF de certificación y confirmar que los folios individuales estén disponibles.

Precondición: el CAF debe ser XML válido de SII para el tipo de DTE a probar (en este ejemplo, 33) y no debe solaparse con un rango previamente cargado.

Request:

curl --fail-with-body -sS -X POST "$BASE_URL/caf/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@$CAF_FILE"

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/caf?docType=33"

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/folios"

Respuesta esperada: la carga retorna el CAF; GET /folios lista folios tipo 33 con status: "available".

Criterio de éxito: hay al menos un folio disponible. La API reserva el menor folio disponible dentro de POST /dte/createDte; no existe un endpoint público de reserva.

Troubleshooting: un error de rango solapado requiere usar un CAF no cargado o revisar los rangos existentes con GET /caf. Si no hay folios para tipo 33, solicite/cargue el CAF correcto.

Prueba 6: obtener token SII

Objetivo: validar el flujo semilla → firma → token frente a Maullín y la reutilización del token persistido.

Precondición: pruebas 3 y 4 aprobadas; servicio con conectividad hacia SII; perfil en certificacion.

Request:

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/sii/auth/valid-token"

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/sii/auth/validate-token"

Respuesta esperada: valid-token retorna data.token; validate-token retorna data.isValid: true.

Criterio de éxito: una segunda llamada reutiliza el token mientras no esté próximo a expirar.

Troubleshooting: fallas de semilla/token suelen originarse en ambiente incorrecto, certificado vencido/no asociado a la empresa, clave incorrecta o indisponibilidad temporal del SII. No intente el flujo en producción hasta contar con autorización.

Prueba 7: crear DTE firmado

Objetivo: reservar folio, construir TED, firmar, validar XML y persistir el DTE.

Precondición: pruebas 3 a 6 aprobadas y al menos un folio 33 disponible. Reemplace emisor, receptor y montos por datos admitidos por el set de certificación.

Request:

cat > /tmp/dte-certificacion.json <<'JSON'
{
  "Encabezado": {
    "IdDoc": { "TipoDTE": 33, "FchEmis": "2026-09-09", "FmaPago": 1 },
    "Emisor": {
      "RUTEmisor": "76123456-7",
      "RznSoc": "Empresa Certificacion SpA",
      "GiroEmis": "Desarrollo de software",
      "DirOrigen": "Av. Providencia 1234",
      "CmnaOrigen": "Providencia"
    },
    "Receptor": {
      "RUTRecep": "66666666-6",
      "RznSocRecep": "Receptor de prueba",
      "GiroRecep": "Servicios",
      "DirRecep": "Av. Ejemplo 100",
      "CmnaRecep": "Santiago"
    },
    "Totales": { "MntNeto": 10000, "TasaIVA": 19, "IVA": 1900, "MntTotal": 11900 }
  },
  "Detalle": [
    {
      "NroLinDet": 1,
      "NmbItem": "Servicio de certificación",
      "QtyItem": 1,
      "UnmdItem": "UN",
      "PrcItem": 10000,
      "MontoItem": 10000
    }
  ]
}
JSON

curl --fail-with-body -sS -X POST "$BASE_URL/dte/createDte" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @/tmp/dte-certificacion.json

Respuesta esperada: status: true, data.id y data.validation.valid: true. Exporte data.id como DTE_ID.

Criterio de éxito: GET /dte muestra el documento con folio asignado y el folio correspondiente queda used.

Troubleshooting: un error XSD o de firma exige corregir el payload o los datos del emisor. Un error de folios exige cargar CAF del tipo correcto. El folio enviado por cliente no debe controlar la numeración.

Prueba 8: enviar DTE a SII

Objetivo: crear el EnvioDTE, transmitirlo a DTEUpload y registrar el TrackId.

Precondición: DTE_ID creado en la empresa del JWT; token SII válido; datos conformes al caso de certificación.

Request:

curl --fail-with-body -sS -X POST "$BASE_URL/dte/sendDte" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "dteIds": ["'"$DTE_ID"'"] }'

Respuesta esperada: status: true y data.trackId. Exporte ese valor como TRACK_ID.

Criterio de éxito: GET /dte muestra el DTE con trackId y estadoSii: "ENVIADO".

Troubleshooting: valide token, certificado, CAF, datos de carátula y conectividad. No reenvíe a ciegas ante timeout: primero liste el DTE y consulte el estado para evitar duplicar operación.

Prueba 9: consultar estado de envío

Objetivo: obtener y persistir el resultado de SII mediante QueryEstUp.

Precondición: prueba 8 aprobada. SII puede procesar el envío de forma asíncrona.

Request:

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/dte/$DTE_ID/estado"

Use ?trackId=$TRACK_ID solo si necesita sobrescribir temporalmente el TrackId almacenado:

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/dte/$DTE_ID/estado?trackId=$TRACK_ID"

Respuesta esperada: data.estado, data.glosaEstado, data.trackId y data.fechaConsulta.

Criterio de éxito: el documento persiste el estado y glosa SII. Registre la evidencia de aceptación, reparo o rechazo junto al XML.

Troubleshooting: un estado en proceso requiere reconsulta posterior; no significa que el documento haya sido aceptado. Sin TrackId, vuelva a verificar la prueba 8.

Prueba 10: descargar evidencia XML y PDF

Objetivo: recuperar el XML firmado y su representación PDF cuando exista.

Precondición: DTE_ID válido de la empresa autenticada.

Request:

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  -o "./DTE-$DTE_ID.xml" \
  "$BASE_URL/dte/$DTE_ID/xml"

curl --fail-with-body -sS \
  -H "Authorization: Bearer $TOKEN" \
  -o "./DTE-$DTE_ID.pdf" \
  "$BASE_URL/dte/$DTE_ID/pdf"

Respuesta esperada: XML con Content-Type: application/xml; PDF descargable cuando se haya generado.

Criterio de éxito: conservar XML firmado y el estado SII asociado. El PDF debe incluir el contenido tributario y PDF417 cuando aplique.

Troubleshooting: 404 para PDF indica que aún no existe representación. Verifique siempre el XML, que es el documento tributario oficial.

Prueba 11: controles negativos y aislamiento

Ejecute estas comprobaciones antes de avanzar al set formal:

CasoAcciónResultado esperado
Sin autenticaciónInvocar cualquier endpoint SII sin Authorization401
Token de otro usuario/empresaListar DTE, CAF o foliosNo expone recursos de la otra empresa
CAF solapadoCargar de nuevo un rango ya importadoRechazo sin crear folios duplicados
Folios agotadosEmitir sin folios available del tipo solicitadoRechazo sin DTE válido ni folio consumido
Payload inválidoCrear DTE con totales/datos incompatiblesError de validación, sin envío a SII
Token SII vencidoForzar GET /sii/auth/regenerate-token y volver a validarNuevo token válido, sin mezclar empresas

Documente por caso: fecha, empresa de prueba, request, código HTTP, mensaje y evidencia. Oculte tokens, claves y datos sensibles en los registros.

Apéndice A: prerrequisitos reales SII

Para ejecutar pruebas contra Maullín se necesita:

  1. Empresa configurada para certificación, con RUT, giro y dirección tributaria correctos.
  2. Certificado tributario vigente del firmante autorizado y su contraseña.
  3. CAF de certificación para el tipo de DTE y rango de folios que se emitirá.
  4. Datos de resolución, RUT de firmante y tipos de documento en el perfil.
  5. Casos y receptores definidos por el set de pruebas vigente del SII.

Los pasos 6 a 10 dependen de activos reales y conectividad SII. Las pruebas unitarias/integración con simuladores no sustituyen la evidencia de certificación oficial.

Apéndice B: pruebas por BFF de front-sapira

El navegador no llama api-factura. El flujo de habilitación es navegador → /api/sii/** → api-sapira → POST /empresas/provision. Certificado, CAF y DTE van navegador → /api/factura/** → api-factura. X-Factura-Company-Id es el UUID de la compañía en Sapira.

Comprobaciones:

  1. En /admin/configuracion/sii se listan las razones sociales chilenas del holding (GET /api/sii/companies).
  2. Integrar llama POST /api/sii/companies/:id/factura-integration y deja facturaStatus: linked.
  3. /facturacion solo muestra compañías integradas y usa su UUID Sapira en las llamadas BFF de certificado, CAF y DTE.
  4. /dte/addInvoice sigue fuera de contrato con sendDTE; no lo use en certificación.

Apéndice C: ejecución del set formal de certificación

Use los pasos 3 a 10 como plantilla por cada caso requerido por el SII:

  1. Identifique tipo de documento, emisor/receptor, montos y resultado esperado.
  2. Guarde el payload usado con secretos removidos y asócielo al caso de SII.
  3. Cree, envíe y consulte hasta estado terminal; archive XML, TrackId, glosa y fecha.
  4. Registre el resultado en una matriz de evidencia: caso, tipoDTE, folio, DTE_ID, TrackId, estadoSii, resultado, observación.
  5. Incluya simulaciones de volumen, intercambio (recepción, acuse y aceptación/rechazo) y muestra PDF417 cuando el set aplicable lo exija.

La hoja de ruta define los entregables pendientes antes de solicitar autorización productiva.