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:
| Caso | Acción | Resultado esperado |
|---|---|---|
| Sin autenticación | Invocar cualquier endpoint SII sin Authorization | 401 |
| Token de otro usuario/empresa | Listar DTE, CAF o folios | No expone recursos de la otra empresa |
| CAF solapado | Cargar de nuevo un rango ya importado | Rechazo sin crear folios duplicados |
| Folios agotados | Emitir sin folios available del tipo solicitado | Rechazo sin DTE válido ni folio consumido |
| Payload inválido | Crear DTE con totales/datos incompatibles | Error de validación, sin envío a SII |
| Token SII vencido | Forzar GET /sii/auth/regenerate-token y volver a validar | Nuevo 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:
- Empresa configurada para certificación, con RUT, giro y dirección tributaria correctos.
- Certificado tributario vigente del firmante autorizado y su contraseña.
- CAF de certificación para el tipo de DTE y rango de folios que se emitirá.
- Datos de resolución, RUT de firmante y tipos de documento en el perfil.
- 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:
- En
/admin/configuracion/siise listan las razones sociales chilenas del holding (GET /api/sii/companies). - Integrar llama
POST /api/sii/companies/:id/factura-integrationy dejafacturaStatus: linked. /facturacionsolo muestra compañías integradas y usa su UUID Sapira en las llamadas BFF de certificado, CAF y DTE./dte/addInvoicesigue fuera de contrato consendDTE; 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:
- Identifique tipo de documento, emisor/receptor, montos y resultado esperado.
- Guarde el payload usado con secretos removidos y asócielo al caso de SII.
- Cree, envíe y consulte hasta estado terminal; archive XML, TrackId, glosa y fecha.
- Registre el resultado en una matriz de evidencia:
caso,tipoDTE,folio,DTE_ID,TrackId,estadoSii,resultado,observación. - 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.