# 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:

```bash
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:

```bash
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`.

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:**

```bash
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:

```bash
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:**

```bash
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:

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](./roadmap.md) define los entregables pendientes antes de solicitar autorización productiva.
