# Cómo funciona

> Cada comprobación por la que pasa un envío, en orden, y lo que puede devolver cada una.

Cada envío a `/f/<formId>` pasa por el mismo pipeline. Las comprobaciones baratas van primero, así que un flood o un formulario desconocido no cuestan casi nada. La primera comprobación que falla decide la respuesta.

El pipeline, en orden:

1. Protección contra floods por IP: como máximo 600 envíos por minuto desde una IP, sumando todos los formularios, antes de buscar el formulario. Falla con `429 rate_limited`.
2. Búsqueda del formulario: los formularios desconocidos, eliminados y desactivados fallan con `404 not_found`.
3. Límites de frecuencia del formulario: el preset del formulario, contado por IP de visitante y por formulario. Falla con `429 rate_limited`.
4. Los formularios en pausa fallan con `403 form_paused`.
5. Lectura del cuerpo: urlencoded, multipart o JSON, como máximo 256 KB. Falla con `415`, `400 invalid_body`, `413 payload_too_large`, `422 too_deep` o `422 files_not_supported`.
6. Clave de API, cuando se envía una cabecera `Authorization`. Falla con `403 forbidden` o `403 origin_not_allowed`.
7. Dominios permitidos: un envío desde otro sitio se guarda como spam o falla con `403 origin_not_allowed`.
8. Verificación humana: falla con `403 challenge_required` o `403 captcha_failed`. Los envíos de formularios del navegador reciben en su lugar una página de verificación.
9. Validación de campos: falla con `422 validation_failed`.
10. Puntuación de spam: una puntuación de 5 o más se guarda como spam, pero igualmente se responde como un éxito.
11. Idempotencia: una clave repetida devuelve la primera respuesta, o falla con `409` o `422`.
12. Uso mensual: muy por encima del límite falla con `429 quota_exceeded`.
13. Guardado: `201` con el id del envío, o una redirección `303` para envíos del navegador. Después vienen los webhooks, los emails de notificación y la actualización del panel en vivo.

## Qué hace cada etapa

### Los límites de frecuencia van antes que todo

Los límites de frecuencia se aplican antes de leer el cuerpo. La protección contra floods (600 por minuto por IP) se ejecuta incluso antes de buscar el formulario, así que un flood nunca llega a la base de datos. Las reglas propias del formulario se aplican justo después de la búsqueda. Las peticiones rechazadas no consumen el margen. Los números exactos están en [Límites de frecuencia](https://ikiform.com/es/docs/rate-limits.md).

### Lectura

El cuerpo se lee como urlencoded, multipart o JSON. El texto se decodifica como UTF-8, se normaliza a NFC, CRLF pasa a LF y se eliminan los caracteres de control. Los nombres que empiezan con `_` son instrucciones reservadas, como `_redirect` y `_subject`, y nunca se guardan como respuestas. Consulta [Formularios HTML](https://ikiform.com/es/docs/html.md#reserved-fields).

### Quién lo envió

La cabecera `Authorization: Bearer` es opcional. Una clave secreta (`ik_sk_`) con el permiso `submissions:write` se salta la comprobación de dominios permitidos y la verificación humana de Ikiform. Una clave publicable (`ik_pub_`) solo funciona desde los dominios indicados en la clave. Sin clave, el envío se evalúa por su `Origin`.

### Spam

El spam nunca es un error. Un envío que no cumple la política de dominios permitidos en modo spam, o que puntúa 5 o más, se guarda con el estado `spam` y recibe la misma respuesta de éxito que uno real, así los bots no aprenden nada. El spam no envía webhooks ni emails y no cuenta para el uso. Consulta [Protección antispam](https://ikiform.com/es/docs/spam.md).

### Guardado y entrega

El envío, sus entregas de webhook y su email de notificación se escriben en un solo lote de base de datos y luego pasan a una cola. La entrega de webhooks y el email nunca ralentizan la respuesta, y un envío nunca se guarda sin sus entregas.

## JSON o una redirección

El mismo endpoint responde a los navegadores con páginas y al código con JSON. Ikiform decide en cada petición:

| Petición | Respuesta |
| --- | --- |
| `Accept` incluye `application/json` | JSON |
| El cuerpo es JSON | JSON |
| `Sec-Fetch-Mode: navigate` (un envío de formulario normal) | Redirección o página alojada |
| Cualquier otro `Sec-Fetch-Mode`, como `cors` desde `fetch` | JSON |
| Sin `Sec-Fetch-Mode`, y `Accept` incluye `text/html` | Redirección o página alojada |
| Cualquier otra cosa, como curl | JSON |

En modo JSON, el éxito es `201 { "ok": true, "id": "sub_..." }`. En modo página, el éxito es una redirección `303`, y los errores muestran una página de error alojada que enumera los problemas de cada campo por su etiqueta.

## Después de guardar un envío

| Qué | Cuándo | Detalles |
| --- | --- | --- |
| Webhooks | Segundos después, con reintentos durante unos 3 días | [Webhooks](https://ikiform.com/es/docs/webhooks.md) |
| Email de notificación | Segundos después, si las notificaciones están activas en el formulario | Máximo 50 emails por formulario por hora (10 en espacios de trabajo con menos de 7 días), luego un resumen por hora |
| Panel | Al instante, en vivo | El spam aparece en el filtro de spam |
| API REST | Al instante | [API REST](https://ikiform.com/es/docs/api.md) |

> Los formularios cuyos envíos contienen nombres de campo del tipo contraseña, tarjeta o frase semilla se marcan y sus emails de notificación se retienen para revisión. No recojas credenciales con un backend de formularios.

## Ids

| Id | Formato | Notas |
| --- | --- | --- |
| Formulario | `frm_xxxxxxxxxxxxxxxx` | `frm_` más 16 caracteres aleatorios. Es público: está en la URL de action de tu formulario. |
| Envío | `sub_01k7v2c9t3e8m5q4x6z0a1b2c3` | `sub_` más 26 caracteres, ordenados por tiempo. |
| Entrega de webhook | `dlv_...` | Se envía como `webhook-id`; no cambia entre reintentos. |
| Petición | `req_...` | Cada respuesta tiene una cabecera `X-Request-Id`. Indícala cuando contactes con soporte. |

**201** (`Éxito con JSON`)

```json
{ "ok": true, "id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3" }
```

**303** (`Éxito en el navegador`)

```http
HTTP/1.1 303 See Other
Location: https://example.com/thanks
X-Request-Id: req_01k7v2c9t3e8m5q4x6z0a1b2c3
```
