# Contrato de la puerta de alta de trámite

Especificación normativa de la **puerta para herramientas** de Actívate con Telcel: qué acepta, qué responde y qué garantiza. Cuando la [guía de integración](https://docs.brainsuite.mx/api/guia.html) y este contrato difieren, manda este contrato. La forma legible por máquina es [`openapi.yaml`](https://docs.brainsuite.mx/api/openapi.yaml).

Versión del servicio documentada: `0.368.0`, con última comprobación en las pruebas automáticas de la puerta en la `0.371.0`.

## 00. Qué es esta superficie y qué no

La herramienta **entrega un expediente; Actívate decide y avanza**. La puerta no es una ruta nueva: son **las mismas rutas por etapa** que usan el sitio y el panel de Actívate, llamadas con `source:'herramienta'` y un token de servicio. Lo que cambia es la entrada (identidad, dueño, idempotencia, marca), no el proceso.

**Alcance de la v1.** Caminos **línea nueva** y **portabilidad**. Migración y cambio de plan responden `422 CAMINO_NO_DISPONIBLE`.

**La herramienta nunca, por ninguna ruta de esta puerta, ordena:** consultas de crédito, la orden de alta, el alta del trámite en el sistema del operador, el reenvío de una orden, la corrección del NIP, la confirmación de un nombre, las notas ni el token por SMS. Esas acciones pertenecen al panel de Actívate y ninguna acepta el token de una herramienta. **Única excepción:** al crear un trámite de venta real, una herramienta habilitada puede *pedir* la activación automática (`activacion_automatica`, §03.1). Actívate la ejecuta solo si se cumplen varias condiciones suyas, dentro de su horario y una sola vez, e incluye una consulta de crédito del cliente (necesita su autorización); la herramienta no la dispara ni la repite.

**Pero lo que la herramienta entrega sí puede poner en marcha la automatización de Actívate:**

| Efecto indirecto | Qué lo provoca | Aplica a `clase:'prueba'` |
|---|---|---|
| **Revisión automática del expediente** | Cada escritura que termina bien sobre un trámite; la revisión corre solo si cambió algo de lo que se valida | Sí |
| **Alta del trámite en un sistema externo de registro** | Cuando el expediente queda validado sin faltantes y el proceso automático de Actívate está encendido, el sistema crea el alta | **No** (una prueba es simulada y el paso se frena) |
| **Consulta al operador por la foto** | Cada foto de INE de la que la revisión lee datos de identidad con confianza suficiente inicia una consulta automática del adeudo de esa persona, también con la foto rechazada | **No** |
| **Activación** | Solo si la herramienta la pidió en el alta (`activacion_automatica`) y se cumplen las condiciones de Actívate | **No** (solo clase real) |
| **Publicación de las fotos de la INE** | Cuando el expediente queda validado, Actívate publica una copia de cada foto vigente en una dirección con código aleatorio y sin autenticación, que comparte con el sistema externo de registro | **Sí** |
| **Registros internos** | El alta crea a la persona en el directorio de contactos y `plan_linea` crea el pedido | Sí (con la marca de prueba) |

Una prueba simulada no inicia consultas con el operador ni crea el alta automática; según el diseño y la lectura del código, y **no se ha observado con tráfico real**. Aun así, en pruebas usa solo fotos ficticias o de muestra: el almacén las guarda y las analiza. En un trámite de venta real, ambos efectos ocurren sin que la herramienta los pida.

**Superficie** (10 operaciones; la base de todas es `/v1/sites/{key}`):

| # | Método | Ruta | Permiso |
|---|---|---|---|
| 1 | `POST` | `/leads` | `write` |
| 2 | `PATCH` | `/leads/{folio}/plan_linea` | `write` |
| 3 | `PATCH` | `/leads/{folio}/plan` | `write` |
| 4 | `PATCH` | `/leads/{folio}/datos` | `write` |
| 5 | `PATCH` | `/leads/{folio}/referencias` | `write` |
| 6 | `PATCH` | `/leads/{folio}/portabilidad` | `write` |
| 7 | `POST` | `/leads/{folio}/documents/{kind}` (solo `ine_frente`, `ine_reverso`) | `write` |
| 8 | `GET` | `/leads/{folio}/estado` | `access` |
| 9 | `GET` | `/leads/{folio}/documents` | `access` |
| 10 | `GET` | `/leads/{folio}/identificadores` | `access` |

**Sitio.** `key` = **`zom`** (Actívate con Telcel). `GET /v1/sites/zom/plans` es público y sin autenticación y da los `plan_ref` válidos (campo `plans[].key`, p. ej. `libre-1`).

## 01. Identidad y permisos

| Aspecto | Contrato |
|---|---|
| Credencial | **Token de servicio** (JWT RS256, `type:"service"`) en `Authorization: Bearer <token>`. Nunca en la URL, el cuerpo ni la query |
| Permisos | `19:tramites:write` para las rutas 1 a 7; `19:tramites:access` para las rutas 8 a 10. **`write` no implica `access`**: una herramienta que escribe y lee pide los dos |
| Consumidor | Sale de `sub` del token (`service:<slug>`, `slug` = `[a-z0-9-]{1,80}`). **Nunca del cuerpo.** Lo asigna Actívate con Telcel y es único |
| Sesión de persona | **403** `SOLICITANTE_NO_AUTORIZADO`, aunque la persona tenga permisos amplios |
| Sin token | **401** |
| Revocado | **401** `Service token revocado`. **No es inmediato:** una revocación puede tardar unos minutos en surtir efecto |
| Consumidor no dado de alta | **403** `CONSUMIDOR_NO_REGISTRADO`. Un token válido entra solo si Actívate con Telcel dio de alta a su consumidor |
| Consumidor suspendido | **403** `CONSUMIDOR_SUSPENDIDO`, desde la siguiente llamada. Es el corte inmediato; revocar el token tarda unos minutos |
| Modo del consumidor | `suspendido` · `prueba` (solo trámites de prueba simulada) · `real`. Lo fija Actívate con Telcel y puede cambiarlo en cualquier momento |
| Validación no disponible | **503** `Auth no disponible…` en el momento en que el servicio no puede validar tu token |

**Cómo declarar que eres herramienta** (sin esto el trámite se trata como ajeno y responde **404**, no 401):

| Ruta | Dónde va `herramienta` |
|---|---|
| 1 a 6 (JSON) | Campo **`source: "herramienta"` en el cuerpo** |
| 7, 9, 10 | Header **`X-Funnel-Source: herramienta`** |
| 8 (`estado`) | No hace falta: la ruta solo acepta herramientas |

Un `PATCH` sin `source` o un `GET documents` sin el header **no son un error de autenticación**: se evalúan como el anónimo del sitio, y un trámite de herramienta no lo ve el anónimo → `404 LEAD_NOT_FOUND`.

**Forma de los 401.** `{"error":"<texto>"}` sin `code`, y el texto no es un identificador estable (`Authorization token required`, `Invalid or expired token`, `Service token revocado`). **Se ramifica por el status, no por el texto.** Los 403 de la puerta sí traen código (`SOLICITANTE_NO_AUTORIZADO`).

## 02. Propiedad del trámite

- Una herramienta **solo ve y escribe los trámites que ella creó** (`source='herramienta'` y mismo consumidor). Cualquier otro folio —de otra herramienta, del sitio, del panel o inexistente— responde **404 `LEAD_NOT_FOUND`**, idéntico en todos los casos cuando el llamador trae un token válido.
- A la inversa, un trámite de herramienta lo leen o escriben su herramienta y el panel de Actívate con sesión. El anónimo del sitio recibe 404.
- **Teléfono con trámite abierto.** Un teléfono con un trámite abierto no se retoma por la puerta: ver `409 TRAMITE_ABIERTO` y `TRAMITE_ABIERTO_OTRO_CANAL` en §03.1.

## 03. Rutas

Todas las respuestas de éxito son `{ "data": … }`. Los timestamps son ISO-8601 UTC con `Z`, salvo `offer_snapshot.captured_at` y similares. Folios, teléfonos, ICCID y NIP viajan como **cadenas**.

### 03.1 `POST /v1/sites/zom/leads`: crear el trámite

**Cuerpo.**

| Campo | Tipo | Obligatorio | Regla |
|---|---|---|---|
| `source` | `"herramienta"` | sí | Con otro valor la ruta se comporta como la del sitio (anónima) y **no es la puerta** |
| `referencia_externa` | cadena | **sí** | Tu id de negocio. 1 a 80 caracteres imprimibles (sin caracteres de control). Única por consumidor en **todos** los sitios. Es la llave de idempotencia |
| `clase` | `"real"` \| `"prueba"` | **sí** | Sin valor por omisión. `real` solo para consumidores en modo `real`; `prueba` es siempre simulada |
| `consent` | objeto | **sí** | `basis` (cadena) y `evidence` (objeto con al menos una entrada válida). Cómo y cuándo aceptó el cliente. **Se recorta, no se rechaza:** `basis` a 40 caracteres, claves a 40, valores cadena a 300, solo las 10 primeras entradas, y se descartan en silencio las entradas con otro tipo de valor o vacías (si no queda ninguna: `CONSENTIMIENTO_REQUERIDO`) |
| `contact_name` | cadena | sí | Nombre del cliente |
| `contact_phone` | cadena | sí | Celular de 10 dígitos; admite `+52`, `52`, `044`, `045`, `521`, espacios y guiones. 10 dígitos iguales o `0123456789`/`9876543210` son sospechosos |
| `contact_email` | cadena | no | Correo; un `+` **en cualquier parte** se rechaza (`EMAIL_PLUS_NO_PERMITIDO`) |
| `plan_ref` | cadena | no | `plans[].key` del catálogo (p. ej. `libre-1`). **Un valor inexistente aquí se ignora en silencio** (el trámite nace sin plan); en `PATCH plan` sí da 422 |
| `modalidad` | `"abierta"` \| `"controlada"` | no | Cualquier otro valor se trata como `abierta` |
| `opciones_extendidas` | objeto | no | `{activar, paperless, esim}`, cada llave booleana; `activar` vale `true` por omisión |
| `validacion_previa` | objeto | no | `{ request_id, sistema }` (se recortan a 100 y 40). **Evidencia, nunca veredicto**: Actívate corre su propia validación sobre los documentos que recibe |
| `activacion_automatica` | objeto | no | **Función restringida.** Pide que Actívate active el trámite cuando cumpla los requisitos. Solo con `clase:"real"` y con la función habilitada por Actívate con Telcel para tu consumidor (`403 ACTIVACION_AUTOMATICA_NO_HABILITADA`). Actívate con Telcel entrega su especificación al habilitarla. Queda fija desde el alta: repetir el alta con la petición distinta responde `409 ACTIVACION_AUTOMATICA_NO_COINCIDE` con el `folio`. `{solicitada:false}` equivale a no mandarla |

Los campos del sitio (`session_id`, `referral`, `angle`, `opened_at_ms`, `_hp`) **se ignoran** en una alta de herramienta. `consent.marketing` se ignora: la herramienta **nunca** inscribe al cliente a publicidad (`marketing:false` siempre).

**Respuestas.**

| Status | Cuándo | Cuerpo |
|---|---|---|
| `201` | Trámite nuevo | `data` = el trámite público (ver abajo) + `resumed:false`, `referencia_externa`, `clase:{clase, alcance, origen:"propia"}` |
| `200` | Misma `referencia_externa` y mismo teléfono: **el mismo folio**. Se comparan `referencia_externa`, teléfono y la petición de activación: **el resto del cuerpo se valida de nuevo, pero no se aplica (incluida `clase`)** y responde igual aunque el trámite esté vencido, perdido o activado | igual, con `resumed:true` |
| `401` · `403` · `404` · `409` · `422` · `423` · `429` · `500` | Ver la tabla de errores de la [guía](https://docs.brainsuite.mx/api/guia.html#06-errores) | `{error, message?, …}` |

**El trámite público** (`data`), el mismo objeto que devuelven las etapas: `folio`, `status`, `docs_status`, `activation_status`, `camino`, `offer_snapshot`, `address`, `geo`, `nombre_completo`, `contact_lada`, `line_lada`, `line_msisdn`, `contact_email`, `home_phone`, `home_phone_declinado`, `opciones_extendidas`, `next_step`, `pendientes` (mapa etapa→bool), `no_aplica` (mapa etapa→bool), `datos_adelantados` y `tracking.event_id` (se devuelve aunque a la herramienta no le sirve; **ningún evento de publicidad sale** para `source:'herramienta'`). **No lleva `reopen_token`.** Los campos pueden crecer: ignora los desconocidos.

`geo.approx` es la geolocalización aproximada **de la IP que hizo la llamada** (la de tu servidor, no la del cliente). No la uses como dato del cliente.

**Conflictos de teléfono** (después de la idempotencia):

| Situación | Respuesta |
|---|---|
| Esa `referencia_externa` ya creó un trámite con **otro teléfono** | `409 REFERENCIA_EN_CONFLICTO` (sin folio) |
| Esa `referencia_externa` ya se usó en **otro sitio** | `409 REFERENCIA_EN_CONFLICTO` |
| El teléfono tiene un trámite abierto **tuyo** con otra referencia | `409 TRAMITE_ABIERTO` con `folio` y `referencia_externa` de ese trámite |
| El teléfono tiene un trámite abierto de **otro canal u otra herramienta** | `409 TRAMITE_ABIERTO_OTRO_CANAL`, **sin folio ni datos**. Lo resuelve un asesor |

**Tope diario.** 20 altas por consumidor y día de Ciudad de México. Con el tope lleno: `429 TOPE_DIARIO` con `Retry-After` (segundos hasta la medianoche de México, mínimo 60). **Un reintento idempotente sigue respondiendo `200`** aunque el tope esté lleno. El tope se cuenta por consumidor y las altas simultáneas se serializan: una ráfaga no lo rebasa.

### 03.2 `PATCH …/leads/{folio}/plan_linea`: camino y línea

Cuerpo (todo opcional salvo `source`):

| Campo | Regla |
|---|---|
| `has_number_to_keep` | `false` ⇒ **línea nueva**. Cualquier otro valor (incluida la ausencia) ⇒ conserva número |
| `current_carrier` | Compañía actual. Con `has_number_to_keep` distinto de `false`: `"telcel"` ⇒ migración o cambio de plan (rechazados); cualquier otra compañía ⇒ portabilidad. La compañía se compara sin mayúsculas, acentos ni espacios de sobra (`"Telcel"` y `" TÉLCEL "` son Telcel) y se guarda como llegó |
| `has_plan` | Solo con `telcel`: `true` ⇒ cambio de plan; si no, migración |
| `line_msisdn` | Número a portar (10 dígitos). Errores `LINE_PHONE_INVALID` / `LINE_PHONE_SUSPICIOUS` |
| `line_lada` | Solo línea nueva: LADA elegida (debe estar en el catálogo, `LINE_LADA_INVALID`) |
| `plan_ref`, `modalidad` | Recalculan la oferta. **Un `plan_ref` inexistente aquí se ignora en silencio** (conserva el plan anterior) |
| `orden_prepago` | Booleano; otro tipo ⇒ `422 ORDEN_PREPAGO_INVALID` |

Camino derivado: `linea_nueva` | `portabilidad` | `migracion` | `cambio_plan`. Los dos últimos ⇒ `422 CAMINO_NO_DISPONIBLE` con `camino` en el cuerpo. Respuesta `200`: `data` = trámite público + `recalculated`. Efectos: `status` pasa a `qualified`; en línea nueva `activation_status` pasa a `not_required`; **se crea el pedido** la primera vez (el `409 DATOS_CONGELADOS_POR_SISACT` de esta etapa solo aplica con la línea ya activada) (si repites la etapa con otro camino, Actívate cambia el camino y **el pedido conserva el de la primera llamada**). Si el servicio de pedidos no responde: `503 PEDIDOS_NO_DISPONIBLE`, reintentable.

### 03.3 `PATCH …/leads/{folio}/plan`: plan elegido

`plan_ref` (cadena) y/o `modalidad`. Sin ninguno: `422 PLAN_REF_REQUIRED`. `modalidad` distinta de `abierta`/`controlada`: `422 MODALIDAD_INVALIDA`. `plan_ref` inexistente: `422 PLAN_NOT_FOUND`. Si el plan cambia y el trámite ya fue enviado al operador: `409 PLAN_CONGELADO_POR_SISACT`. Respuesta `200`: trámite público + `recalculated`.

### 03.4 `PATCH …/leads/{folio}/datos`: datos personales y domicilio

| Campo | Regla |
|---|---|
| `contact_email` | Obligatorio **si el trámite aún no tiene correo** (el del alta cuenta). `422 EMAIL_REQUIRED_STAGE3`; con `+` en la parte local `422 EMAIL_PLUS_NO_PERMITIDO` |
| `nombre_completo` | **Obligatorio.** O bien objeto `{ nombres, apellido_paterno, apellido_materno }` (`apellido_materno:"*"` = sin materno), o bien cadena de **al menos 3 palabras**. En el objeto: solo letras, acentos y `' . -`; además rechaza palabras genéricas (`prueba`, `test`, `demo`, `cliente`, `asdf`, `qwerty`, `xxx`, `ninguno`, `n/a`) con `NOMBRE_GENERICO`. La cadena no pasa por la regla de caracteres ni por el filtro de palabras genéricas (también se acepta `{nombre:"…"}`) |
| `address` | Objeto. Obligatorios no vacíos: `cp` (5 dígitos), `colonia`, `calle`, `numero_ext`. Actívate completa estado, municipio y ciudad a partir del CP |
| `home_phone` | **Obligatorio** (teléfono adicional, 10 dígitos —si mandas más, se queda con los últimos 10—, distinto del de contacto: `LINE_SAME_AS_CONTACT`). Rechaza dígitos repetidos (`HOME_PHONE_SUSPICIOUS`) |
| `home_phone_declinado` | **No disponible para herramientas**: `true` ⇒ `422 HOME_PHONE_DECLINE_AGENT_ONLY` |
| `geo.precise` | Se acepta y se guarda; no lo mandes sin permiso del cliente |

Respuesta `200`: trámite público (con `status:"submitted"`) y, cuando el CP, la línea y los teléfonos apuntan a regiones distintas, **`region_avisos`** (arreglo): para una herramienta es un **aviso** y nunca bloquea. Si el trámite ya fue enviado al operador y un dato duro cambia: `409 DATOS_CONGELADOS_POR_SISACT` con `campos[]`. Después de responder, Actívate revalida el expediente por su cuenta.

### 03.5 `PATCH …/leads/{folio}/referencias`

`references`: arreglo de `{ name, phone, relationship?, holder_informed? }`; **solo cuentan las dos primeras**. Se requieren **2** con teléfono (`references_required`). **La ruta acepta menos**: acepta una sola referencia y también una sin `phone` (responde `200`; el estado mostrará después la falta `referencia_N`); lo que exige el trámite son 2 completas. `name` con al menos 2 palabras. `phone`, si viene, de 10 dígitos, distinto del contacto, del adicional y de la línea, y distintos entre sí. Respuesta `200`: trámite público (en `data`) con `references_captured`, `references_required` y, si aplica, `region_avisos`. Reenviar reemplaza por posición.

### 03.6 `PATCH …/leads/{folio}/portabilidad`: NIP o ICCID

`nip` (4 dígitos) y/o `iccid` (19 dígitos que empiezan con `89`; si no empieza con `8999` debe pasar el dígito Luhn). **Si el cuerpo trae `nip` o `iccid` con valor, las dos caras de la INE tienen que estar subidas y no rechazadas**: si no, `422 DOCUMENTOS_INCOMPLETOS` con `missing[]`. Un valor vacío (`""` o `null`) borra el dato y no exige INE. En **línea nueva** la etapa no aplica (`no_aplica.portabilidad:true`) pero la ruta acepta el `iccid` igual. Respuesta `200`: trámite público; `activation_status` pasa a `complete` cuando en portabilidad hay NIP.

### 03.7 `POST …/leads/{folio}/documents/{kind}`: fotos de la INE

`kind` ∈ {`ine_frente`, `ine_reverso`}; cualquier otro de los tipos válidos del sitio ⇒ `422 KIND_NO_DISPONIBLE`, y uno que no existe ⇒ `422 KIND_INVALIDO`.

- **Cuerpo = los bytes de la imagen** (no `multipart`), con su `Content-Type` real. El almacén de documentos valida el contenido y, si no es una imagen o documento aceptable, rechaza con `422 DOCUMENTO_RECHAZADO` (`detail`). Máximo **20 MB**: por encima, el servicio responde `413 {error:"DOCUMENTO_MUY_GRANDE"}` en JSON **antes** de autenticar; si lo rechaza el almacén, la respuesta es `422 DOCUMENTO_MUY_GRANDE`.
- Headers: `Authorization`, **`X-Funnel-Source: herramienta`**, `X-Original-Name` (nombre del archivo, codificado con `encodeURIComponent`, 200 caracteres como máximo) y **`X-Adjunto-Ref`** (`^[A-Za-z0-9-]{1,64}$`): la referencia propia de este archivo. Con ella, repetir la subida del mismo `kind` responde `200 { data, duplicado:true, doc_id }` sin duplicar. **Un valor con otra forma se descarta en silencio** (sin idempotencia).
- Máximo **20 documentos vivos** por trámite (`429 DEMASIADOS_DOCUMENTOS`); **una foto rechazada también cuenta**.
- La etapa de documentos apagada en el sitio ⇒ `423 ETAPA_DOCUMENTOS_APAGADA`. Trámite vencido o perdido ⇒ `410 LEAD_EXPIRED`.
- Actívate **analiza la foto al recibirla** (clasificación, lado, calidad, cotejo de nombre) y siempre guarda el registro. **En un trámite de venta real, la revisión puede iniciar una consulta al operador** (§00), también con la foto rechazada; en prueba simulada no.
- La corrección de una foto rechazada es **otra foto**; ningún texto de la respuesta es una instrucción para una herramienta.

Respuestas: `201 { data: expediente, side_correction, captura_compartida, name_check, needs_human_review, calidad_advertencia, credential_match, reclasificado, captura_telcel }`, donde `expediente` = `{ folio, docs_status, required, missing, next_step, documents[] }` y cada documento `{ doc_id, kind, requested_kind, original_name, bytes, review_status, review_reason, name_mismatch_confirmed, name_check, created_at, adjunto_ref }`. `review_status` ∈ `pending_review` · `needs_human_review` · `rejected` · **`approved`** (cuando un asesor aprueba la foto). `doc_id` es un **entero**. **Una foto que la revisión rechaza responde `422 DOCUMENTO_RECHAZADO_IA`** con `motivo_code`, `lado_detectado?` y `data` (el expediente ya actualizado): el 422 no significa que no se guardó. `motivo_code` es un conjunto abierto (ejemplos: `motivo_no_es_ine`, `DOC_SIDE_MISMATCH`, `motivo_<problema de calidad>`).

**`captura_telcel`** en la respuesta: si la imagen subida es una captura del SMS de Telcel, la respuesta devuelve **el código del token o el NIP leídos**. Trátalo como dato de vida corta; una herramienta no debe subir esas capturas.

La subida a una *sesión* y la confirmación de nombre **no son de la puerta**: `403 SOLICITANTE_NO_AUTORIZADO`.

### 03.8 `GET …/leads/{folio}/estado`: estado público

`200 { data: { folio, referencia_externa, estado, camino, clase, actualizado_at, faltan?, motivo?, activacion_automatica? } }`.

- `estado` ∈ `capturando` · `validando` · `requiere_dato` · `en_tramite` · `activado` · `detenido` (§05).
- `faltan` (solo en `capturando` y `requiere_dato`): `[{ codigo, que }]`, únicamente lo que **impide** avanzar y **le toca al cliente**. `codigo` es un conjunto abierto de identificadores estables (p. ej. `foto_frente`, `foto_reverso`, `ine_vencido`, `contacto_celular`, `correo`, `referencia_1`, `referencia_2`, `domicilio_incompleto`, `sin_plan`, `nip`, `iccid`); `que` es texto para una persona.
- `motivo` (solo en `detenido`): texto en llano.
- Un trámite en `capturando` o `requiere_dato` que ya pasó su vencimiento se ve `detenido` con motivo `El trámite venció sin completarse`. Es derivado: no cambia el trámite.
- `activacion_automatica` (solo si el trámite la pidió): `{ solicitada: true, estado }`, con `estado` ∈ `en_espera` (falta un requisito) · `lanzada` (Actívate la envió al operador; el avance se ve en `estado`) · `con_asesor` (se detuvo y la sigue una persona). Sin motivos internos. `en_espera` puede prolongarse: también cubre un trámite de más de 30 días, que ya no se evalúa.
- `actualizado_at` es la última modificación del trámite, no la hora de la consulta.
- `503 TRAMITE_NO_DISPONIBLE` si no se pudo armar la ficha (reintentable).
- **Sin datos internos**: nunca devuelve folios del sistema del operador, dictámenes, resultados de consultas de crédito ni notas.

### 03.9 `GET …/leads/{folio}/documents` y `…/identificadores`

Con header `X-Funnel-Source: herramienta`, token y permiso `access`. `documents`: el `expediente` de §03.7. `identificadores`: `{ estado, motivos[], fotos:{iccid_foto, pantalla_foto}, identificadores[], iccid_listo }` (lectura de la foto del chip; una herramienta no puede subir esas fotos, así que queda en `sin_foto`). **Contienen datos de personas** (cotejo del nombre leído de la INE en `name_check`): ver la [guía](https://docs.brainsuite.mx/api/guia.html#08-datos-personales). Manda siempre el token.

## 04. Orden de evaluación

El orden decide qué error ves cuando hay varios. En el alta:

1. Sitio inexistente → `404 SITE_NOT_FOUND`.
2. Autenticación, consumidor dado de alta y no suspendido, y permiso → `401` / `403`.
3. Reglas propias de la puerta, sin escribir nada: `referencia_externa` → `clase` (`CLASE_REQUERIDA`, `CLASE_INVALIDA`, `MODO_REAL_NO_HABILITADO`) → `consent` → `validacion_previa` → `activacion_automatica`.
4. Aviso de privacidad pendiente en el sitio → `423 G0.8_AVISO_PRIVACIDAD_PENDIENTE`.
5. `contact_name`, teléfono, correo, `opciones_extendidas`.
6. Idempotencia, conflictos de teléfono y tope diario (`200`, `409`, `429`).
7. Inserción. Un error no previsto → `500 INTERNAL`.

En las rutas por folio de las etapas (`PATCH`) y en `estado`: limitador por IP (429) → sitio (404) → autenticación (401/403) → carga del trámite → dueño (404) → validaciones de la ruta y reglas de congelamiento (409) → escritura. **En estas rutas la autenticación va antes de buscar el folio**: sin token siempre `401`, exista o no. El orden de las validaciones **no es uniforme**: en `referencias` el `409 DATOS_CONGELADOS_POR_SISACT` se evalúa antes que los nombres y teléfonos; en `portabilidad` el `409` va después de `ICCID_LUHN` pero antes de `NIP_FORMAT`, `ICCID_FORMAT` y `DOCUMENTOS_INCOMPLETOS`.

En la subida de INE el orden es: limitador (12/min, 429) → lectura del cuerpo (`413` por tamaño) → sitio → autenticación → etapa apagada (`423`) → `kind` válido (`422`) → `kind` permitido (`422`) → folio (`404`) → dueño (`404`) → trámite vencido (`410`) → duplicado por `X-Adjunto-Ref` (`200`) → cuerpo vacío (`422 ARCHIVO_VACIO`) → tamaño (`413`) → tope de documentos (`429`) → almacén (`503` caído · `422 DOCUMENTO_RECHAZADO`) → análisis (`422 DOCUMENTO_RECHAZADO_IA`).

## 05. Estados públicos

| Interno | Público |
|---|---|
| captura | `capturando` |
| por revisar, y **hay** faltas impeditivas del cliente | `requiere_dato` |
| por revisar, y **no hay** faltas del cliente | `validando` |
| listo · en activación | `en_tramite` |
| activada · cerrada | `activado` |
| perdida | `detenido` |

`detenido` sale de «perdida» y de un trámite que espera datos con el vencimiento pasado; el motivo es `El trámite venció sin completarse` si el trámite expiró, y `El trámite se cerró sin activarse` en cualquier otro caso. **Un trámite atorado en una etapa posterior se ve `en_tramite`**, no `detenido`: la herramienta no puede distinguir «sigue en curso» de «necesita a un asesor». `activado` significa que Actívate dio la línea por activa. La v1 no avisa de vuelta: la herramienta consulta con intervalo ([guía](https://docs.brainsuite.mx/api/guia.html#54-consultar-el-estado-con-intervalo)).

## 06. Garantías de seguridad

1. Nadie se declara `herramienta` sin token: toda ruta con `source:'herramienta'` autentica **antes** de tocar nada, y el alta no deja un trámite a medias si una compuerta falla.
2. Una herramienta solo toca lo suyo; el 404 es indistinguible entre ajeno e inexistente.
3. Ningún efecto con costo fuera de Actívate se *ordena* desde aquí (consultas de crédito, orden, token). La activación se puede *pedir* en el alta de un trámite real y Actívate la ejecuta solo si se cumplen sus condiciones. **Lo que sí puede ocurrir, sin que la herramienta lo pida, es la automatización de Actívate sobre trámites reales** (§00).
4. La marca `clase` nace con el trámite, es de la herramienta y un consumidor nuevo solo puede crear `prueba`. Un consumidor sin alta no entra; uno suspendido deja de entrar en su siguiente llamada.
5. El consentimiento es compuerta en esta puerta, porque nadie de Actívate vio al cliente.
6. Ningún evento de conversión de publicidad sale para `source:'herramienta'`.
7. La bitácora del trámite firma cada cambio como proveniente de la herramienta y de tu consumidor.

## 07. Límites

| Límite | Valor | Dónde aplica |
|---|---|---|
| Altas | **20 por consumidor y día** (hora de México) | `POST /leads` |
| Consultas | **30 por minuto por IP** (ventana de 60 s) | `PATCH plan_linea`, `plan`, `datos`, `referencias`, `portabilidad` y `GET estado`. Exceso: `429 RATE_LIMITED`, **sin `Retry-After`** |
| Subidas | **12 por minuto por IP** | `POST …/documents/{kind}`. Exceso: `429 RATE_LIMITED` |
| Tamaño de foto | 20 MB | subida |
| Documentos por trámite | 20 | subida |
| Referencias | 2 | `referencias` |

La IP es la del llamador tal como la ve el servicio: varias herramientas detrás de la misma salida de red **comparten** el límite.

## 08. Lo que no resuelve esta versión

- **Migración y cambio de plan.**
- **Aviso de vuelta** (webhook): se diseña cuando un consumidor real lo pida.
- **Altas de consumidores nuevos:** las decide Actívate con Telcel, caso por caso.
- **`clase:'real'` y la activación automática:** habilitarlas es decisión de Actívate con Telcel por consumidor.
- **Activación automática de portabilidad:** hoy solo se activa sola una línea nueva; una portabilidad espera.

## 09. Comportamientos a tener en cuenta

Cosas que el servicio hace hoy y que conviene saber antes de integrar, declaradas para que no sorprendan:

| # | Qué esperarías | Qué hace el servicio | Qué haces tú |
|---|---|---|---|
| 1 | Límite de 30/min también en el alta | Solo lo aplican los `PATCH` por etapa y `estado`; la subida de INE tiene 12/min | Trata el alta con el tope diario, no con 30/min |
| 2 | Que el nombre del consumidor viniera del cuerpo | Sale de `sub` del token | Nada: lo asigna Actívate con Telcel |
| 3 | Un error de autenticación al olvidar `source` | Recibes `404`, no `401` | Declara `source` en el cuerpo (rutas 1 a 6) o el header (7, 9, 10) |
| 4 | `detenido` para un trámite atorado | Sale cuando el trámite se pierde o espera datos con el vencimiento pasado; uno atorado en una etapa posterior se ve `en_tramite` | No lo interpretes como «sigue en curso» sin límite de tiempo |
| 5 | Poder declinar el teléfono adicional | Una herramienta no puede; debe mandar `home_phone` | Es un dato obligatorio para ti |
| 6 | Reintentos simultáneos idempotentes | Dos subidas **simultáneas** con el mismo `X-Adjunto-Ref` todavía pueden duplicar la foto | Serializa las subidas de un mismo trámite |
| 7 | Error si `plan_ref` es inválido | En el alta y en `plan_linea` se ignora en silencio; solo `PATCH plan` da `PLAN_NOT_FOUND` | Verifica el plan con `GET /plans` o corrígelo con `PATCH plan` |
| 8 | La clase `prueba` protege de efectos reales | Protege del alta automática y de la consulta al operador por la foto; lo que Actívate haga después con un trámite de prueba no se comprobó paso por paso | Ver §00; usa datos y fotos ficticios |
| 9 | Pedir la activación de un trámite de prueba | `activacion_automatica` solo se acepta con `clase:"real"` | Ver §03.1 |
| 10 | Que un trámite vencido se cierre solo | Se ve `detenido` en `estado` si espera datos, pero el trámite no se escribe ni se cierra | No cuentes con un cierre automático |
| 11 | Que la ruta de referencias exija 2 completas | Acepta una sola referencia y también una sin `phone` | El estado mostrará la falta; manda las dos completas |
| 12 | Revocación instantánea | Revocar el token puede tardar unos minutos; suspender al consumidor es inmediato | Si tu token se compromete, avisa de inmediato a Actívate con Telcel |
