# F29 — propuesta y declaración (/docs/impuestos/f29)

## La propuesta [#la-propuesta]

```bash
curl https://api.facturia.cl/v1/tax/f29/proposals/2026-07 \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"
```

```json
{
  "id": "f29_01K2RC4T8V2M",
  "object": "f29_proposal",
  "period": "2026-07",
  "status": "ready",
  "computed_at": "2026-08-14T06:15:00Z",
  "based_on": { "rcv_synced_at": "2026-08-14T06:12:03Z", "purchases": 42, "sales": 118 },
  "summary": {
    "iva_debito": 3210000,
    "iva_credito": 1985000,
    "iva_retenido": 190000,
    "remanente_anterior": 0,
    "ppm": 148000,
    "total_payable": 1373000,
    "due_date": "2026-08-20"
  },
  "codes": [
    { "code": 538, "label": "Total débitos",                          "value": 3210000 },
    { "code": 537, "label": "Total créditos",                         "value": 1985000 },
    { "code": 39,  "label": "IVA total retenido (facturas de compra)","value": 190000 },
    { "code": 519, "label": "Crédito IVA por facturas de compra",     "value": 190000 },
    { "code": 520, "label": "Crédito IVA — documentos electrónicos",  "value": 1795000 },
    { "code": 77,  "label": "Remanente crédito fiscal período siguiente", "value": 0 },
    { "code": 62,  "label": "PPM neto determinado",                   "value": 148000 },
    { "code": 91,  "label": "Total a pagar dentro del plazo",         "value": 1373000 }
  ],
  "observations": [
    { "code": "rcv_pendientes", "severity": "warning", "message": "3 documentos de compra están en estado PENDIENTE y no fueron considerados.", "entry_ids": ["rcv_...", "rcv_...", "rcv_..."] }
  ],
  "upload_file_url": "https://api.facturia.cl/v1/tax/f29/proposals/2026-07/upload_file",
  "filing_id": null
}
```

Dos cosas que conviene tener claras:

**Esta propuesta la calcula Facturia** desde tu RCV sincronizado más tus parámetros de PPM y
remanente. El SII prellena su propio F29 para su propio portal, pero no publica una API para eso.
Donde ambos difieran, nuestras `observations` normalmente te dicen por qué (compras no registradas,
pendientes, huecos de caracterización) — y siempre puedes comparar contra la pantalla del SII antes
de declarar.

**La lista `codes` es lo que va a llevar el archivo de carga**, código por código. No se declara nada
que no puedas leer aquí primero.

## El archivo de carga [#el-archivo-de-carga]

```bash
curl -L https://api.facturia.cl/v1/tax/f29/proposals/2026-07/upload_file \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" -o f29-202607.txt
```

Es el `.txt` de formato fijo del SII, listo para subir a mano si prefieres hacerlo tú.

## Recalcular y sobrescribir códigos [#recalcular-y-sobrescribir-códigos]

```bash
curl -X POST https://api.facturia.cl/v1/tax/f29/proposals/2026-07/recompute \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"overrides":[{"code":62,"value":150000,"reason":"PPM ajustado por tasa variable"}]}'
```

### El código 048 es una entrada externa [#el-código-048-es-una-entrada-externa]

> **Trampa — Facturia no sincroniza el libro de remuneraciones**
>
> El código **048** (retención de impuesto único a los trabajadores, art. 74 N°1) sale de la nómina, no
> del RCV. Facturia no tiene acceso a esa información, así que no la puede calcular.
>
> Si tu empresa tiene trabajadores, ese código lo aportas tú con `overrides` en `recompute` antes de
> declarar. Un F29 declarado con 048 en cero cuando debería tener valor es una declaración incorrecta,
> y no es un error que la API pueda detectar por ti.

```bash
curl -X POST https://api.facturia.cl/v1/tax/f29/proposals/2026-07/recompute \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"overrides":[{"code":48,"value":656814,"reason":"Retención art. 74 N°1 — libro de remuneraciones julio"}]}'
```

Todo `override` queda con su `reason` guardado, lo que importa si alguna vez hay que explicar de
dónde salió una cifra.

## Declarar [#declarar]

> **Trampa — No existe webservice del SII para declarar un F29**
>
> El único canal no manual es el flujo *Upload* del SII: un `.txt` de formato fijo enviado a través de
> una sesión autenticada en navegador. Facturia automatiza exactamente eso — una sesión autenticada con
> certificado manejando la carga real — y lo modela como un job asíncrono con estados honestos, porque
> eso es lo que es.
>
> Si el SII cambia ese formulario, el job falla y te lo decimos. No hay una API estable debajo que nos
> proteja.

```bash
curl -sS https://api.facturia.cl/v1/tax/f29/filings \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
  -d '{"period":"2026-07","proposal_id":"f29_01K2RC4T8V2M","confirm_total_payable":1373000}'
```

```json
{
  "id": "f29fil_01K2RD9K3M8T",
  "object": "f29_filing",
  "period": "2026-07",
  "status": "queued",
  "declared": { "total_payable": 1373000 },
  "attempts": 0,
  "folio": null,
  "receipt_url": null,
  "created_at": "2026-08-14T14:30:02Z"
}
```

### Las barreras [#las-barreras]

**`confirm_total_payable` es obligatorio** y debe ser igual a la cifra de la propuesta, o recibes
`422 f29_amount_confirmation_mismatch`. Declarar un formulario tributario no es algo que una API deba
dejarte hacer por accidente.

Ese campo hace dos cosas a la vez: te obliga a leer la propuesta antes de declarar, y te obliga a que
la cifra aparezca explícita en tu propio código — con lo cual aparece explícita en cualquier pantalla
o mensaje que tu sistema muestre antes de apretar el botón.

### Los estados del job [#los-estados-del-job]

| `status`    | Significado                                                               |
| ----------- | ------------------------------------------------------------------------- |
| `queued`    | Aceptado; esperando un worker de declaración                              |
| `preparing` | Generando y validando el archivo de carga                                 |
| `uploading` | Sesión autenticada abierta en el SII, enviando                            |
| `submitted` | Subido; esperando el acuse del SII                                        |
| `accepted`  | Declarado. `folio` y `receipt_url` quedan poblados                        |
| `rejected`  | El SII rechazó la declaración; `errors[]` explica                         |
| `failed`    | No pudimos completar el envío (SII caído, sesión rechazada). Reintentable |

```bash
curl https://api.facturia.cl/v1/tax/f29/filings/f29fil_01K2RD9K3M8T \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA"

curl -G https://api.facturia.cl/v1/tax/f29/filings \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" -d "period[gte]=2026-01"
```

Eventos: `tax.f29.filing.submitted`, `tax.f29.filing.succeeded`, `tax.f29.filing.rejected`,
`tax.f29.filing.failed`.

Una declaración no soporta `PATCH` ni `DELETE`. Un F29 declarado se corrige con una *rectificatoria*,
que está fuera del alcance de la v1.

## Declaración automática [#declaración-automática]

`auto_file_f29` viene **apagada**, como toda automatización.

| Ajuste                      | Por defecto | Efecto                                                                         |
| --------------------------- | ----------- | ------------------------------------------------------------------------------ |
| `auto_file_f29`             | `false`     | Declara el F29 del período en `auto_file_f29_day`                              |
| `auto_file_f29_day`         | `18`        | El día del mes en que se declara                                               |
| `auto_file_f29_max_payable` | `0`         | Solo declara sola si el `total_payable` calculado es menor o igual a este tope |

Con el tope en 0 —el valor de fábrica— solo una posición cero o a favor se declara sola, y cualquier
mes en que efectivamente debas IVA espera por ti. Sube el tope deliberadamente, empresa por empresa.

> **Trampa — Una propuesta con observaciones nunca se declara sola**
>
> Si hay `observations` con `severity: error`, la declaración automática se suprime y se dispara
> `tax.f29.review_required`. Declarar un F29 es una declaración jurídicamente vinculante: publicamos la
> capacidad porque una empresa chica que la quiera no debería tener que entrar al SII el día 18, pero
> es opt-in por empresa, con tope, y publica `tax.f29.filing.succeeded` / `.failed` para que puedas
> supervisarla.
