> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payana.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Resolver flujos desde tu sistema

> Integra tus propios pasos de aprobación, clasificación y etiquetado con el flujo de un documento en Payana.

Si tus usuarios ya trabajan en tu propio sistema, no tienen por qué entrar a Payana a repetir la decisión. Esta guía integra los dos lados: cada paso de tu flujo interno tiene un **espejo** en Payana, y cuando alguien decide en tu sistema, esa decisión resuelve el paso espejo.

El principio que sostiene todo lo demás:

<Info>
  **No replicues el flujo en tu código.** Pregunta a Payana qué paso está pendiente y qué opciones admite, y devuelve una de esas opciones. Así, cuando tu compañía reconfigure su flujo — agregue un paso, cambie los responsables, sume valores a un campo — tu integración sigue funcionando sin cambios.
</Info>

<Note>
  Esta guía es para integrarte **con el flujo**: respetar sus pasos, sus responsables y sus efectos. Si lo único que necesitas es dejar un documento aprobado sin pasar por él, usa [Aprobar documento](/api-reference/documents/approve).
</Note>

## Requisitos previos

<Steps>
  <Step title="Una API key de tu compañía">
    Se envía en el header `api-key`. Consulta las URLs base en la [introducción a la API](/api-reference/introduction).
  </Step>

  <Step title="La API de flujos habilitada">
    Si no lo está, ambos endpoints responden `403` con `feature_not_enabled`. Pídela a tu contacto en Payana.
  </Step>

  <Step title="Los usuarios que deciden, dados de alta en Payana">
    Con el mismo email que usan en tu sistema. Toda decisión queda registrada a nombre de una persona, y ese email es el vínculo entre ambos lados.
  </Step>

  <Step title="Un endpoint para el webhook `document.created`">
    Es como te enteras de que hay un documento nuevo que espejar. Opcional si prefieres consultar por tu cuenta.
  </Step>
</Steps>

## Cómo se ven los dos lados

```mermaid theme={null}
flowchart LR

DIAN[DIAN / SAT]

subgraph payana [Payana]
  DOC[Documento creado] --> FLUJO[Flujo del documento]
  FLUJO --> PC[Paso: clasificación]
  PC --> PA[Paso: aprobación]
  PA --> CAU[Causación]
end

subgraph tuyo [Tu sistema]
  CASO[Caso FC-123] --> TC[Tu etiquetado]
  TC --> TA[Tu aprobación]
end

DIAN -->|sincronización| DOC
DOC -.->|webhook document.created| CASO
PC -.->|consultas el paso| TC
TC -.->|resuelves el paso| PC
PA -.->|consultas el paso| TA
TA -.->|resuelves el paso| PA
```

Los pasos de tu sistema no tienen que corresponder uno a uno con los de Payana, ni llamarse igual. Lo único que necesitas saber en cada momento es cuál está pendiente del lado de Payana.

## El ciclo, en tres llamadas

```mermaid theme={null}
sequenceDiagram
  participant P as Payana
  participant T as Tu sistema
  P->>T: webhook document.created — trae la reference del documento
  T->>P: GET /workflow-steps/current?entity_reference=…
  P-->>T: el paso pendiente: su referencia, su clase y sus opciones
  Note over T: tu usuario decide
  T->>P: POST /workflow-steps/{reference}/complete
  P-->>T: status completed — el flujo avanza
```

<Note>
  No existe un webhook por cada paso: Payana no te avisa cuando aparece uno nuevo. Consulta el paso actual cuando tu sistema lo necesite — al abrir el caso, al refrescar una bandeja, o con un sondeo periódico.
</Note>

## Guarda la referencia del documento

El webhook `document.created` trae en `data.reference` la referencia interna del documento en Payana. Guárdala junto a tu propio identificador: es la única llave que necesitas para todo lo que sigue.

```json theme={null}
{
  "event": "document.created",
  "data": {
    "reference": "b4d1689c-e729-478b-a15b-339ad050192f",
    "document_number": "FE-1234",
    "amount": 1190000,
    "amount_currency": "COP"
  }
}
```

<Card title="Webhook: Document Created" icon="webhook" href="/api-reference/documents/webhooks/documento-recibido">
  El payload completo y cómo verificar la firma.
</Card>

## 1. Consulta el paso pendiente

Un documento tiene, a lo sumo, un paso pendiente a la vez.

```bash theme={null}
curl "https://api.prod.payana.cloud/public/api/v1/workflow-steps/current?entity_reference=b4d1689c-e729-478b-a15b-339ad050192f" \
  -H "api-key: $PAYANA_API_KEY"
```

```json theme={null}
{
  "step": {
    "reference": "550e8400-e29b-41d4-a716-446655440042",
    "action": "document.approval",
    "status": "pending",
    "actionable": true,
    "entity": {
      "type": "document",
      "reference": "b4d1689c-e729-478b-a15b-339ad050192f",
      "document_number": "FE-1234",
      "amount_cents": "1234500",
      "amount_currency": "COP",
      "third_party": [
        { "type": "beneficiary", "name": "ACME S.A.S." }
      ]
    },
    "decision": {
      "kind": "approval",
      "multiple": false,
      "field_name": null,
      "options": [
        { "value": "approve", "label": "Aprobar" },
        { "value": "reject", "label": "Rechazar" }
      ],
      "options_complete": true
    },
    "assignees": [
      { "email": "ana@acme.co", "name": "Ana Gómez", "acted": false }
    ],
    "recommendations": []
  }
}
```

Lo que te importa de esa respuesta:

| Campo | Para qué lo usas |
| - | - |
| `step.reference` | Va en el path al completar. No es la referencia del documento. |
| `decision.kind` | Qué clase de decisión es, y por tanto qué control muestras. |
| `decision.options` | Lo único que puedes devolver. Muestra `label`, envía `value`. |
| `assignees` | Quiénes pueden resolverlo. Uno de estos emails va en la decisión. |
| `actionable` | Si es `false`, no te corresponde: no ofrezcas el control. |

<Warning>
  `entity.amount_cents` viene en centavos y como texto. El endpoint de documentos, en cambio, devuelve `amount` decimal: `"1234500"` aquí es el mismo monto que `12345.00` allá.
</Warning>

## 2. Traduce la decisión a tu interfaz

Hay tres clases de decisión, y `decision.kind` te dice cuál tienes enfrente:

| `kind` | `multiple` | Qué hay en `options` |
| - | - | - |
| `approval` | `false` | Dos opciones fijas: `approve` y `reject` |
| `classification` | `false` | Los valores activos del campo que nombra `field_name` |
| `tagging` | `true` | Etiquetas de tu compañía |

Dos reglas al construir el control:

<AccordionGroup>
  <Accordion title="El value es opaco — no lo interpretes">
    `"approve"`, `"451"`, `"88"`: son tokens que Payana traduce a la rama del flujo, al valor del campo o a la etiqueta. Muestra el `label` a tu usuario, guarda el `value` y devuélvelo tal cual. No construyas lógica sobre su contenido ni lo mapees a tus propios códigos.
  </Accordion>

  <Accordion title="Si options_complete es false, la lista no es el límite">
    Ocurre en etiquetado sin restricción: `options` es una muestra del catálogo y **cualquier etiqueta de tu compañía es válida**, aunque no aparezca. Si tu interfaz solo ofrece lo que llegó, estarás recortando opciones legítimas — deja que tu usuario escriba una etiqueta libre.
  </Accordion>
</AccordionGroup>

<Tip>
  Cuando el flujo tiene un agente configurado, `recommendations` trae su sugerencia con un `confidence_level` y el porqué. Úsala para preseleccionar la opción en tu interfaz, o para decidir sin intervención cuando la confianza sea alta.
</Tip>

## 3. Envía la decisión

```bash theme={null}
curl -X POST "https://api.prod.payana.cloud/public/api/v1/workflow-steps/550e8400-e29b-41d4-a716-446655440042/complete" \
  -H "api-key: $PAYANA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "approve",
    "acting_user_email": "ana@acme.co"
  }'
```

```json theme={null}
{
  "step": {
    "reference": "550e8400-e29b-41d4-a716-446655440042",
    "action": "document.approval",
    "status": "completed",
    "completed_at": "2026-08-05T14:03:11.000Z",
    "decision": {
      "kind": "approval",
      "values": ["approve"],
      "labels": ["Aprobar"]
    },
    "acted_by": { "email": "ana@acme.co", "name": "Ana Gómez" }
  }
}
```

El cuerpo tiene tres campos:

| Campo | Requerido | Descripción |
| - | - | - |
| `decision` | Sí | Un `value` de `options`, o una lista de `value` cuando `multiple` es `true` |
| `acting_user_email` | Sí | El miembro de tu compañía a nombre de quien se registra la decisión |
| `comment` | No | Motivo libre, hasta 2000 caracteres. Útil al rechazar |

<Warning>
  `acting_user_email` es obligatorio y debe ser uno de los `assignees` del paso. Tu `api-key` identifica a tu compañía, no a una persona: como una aprobación queda registrada a nombre de alguien, tienes que indicar de quién. No existe un "usuario del sistema" al que atribuirla.
</Warning>

Ejemplos de cuerpo para las otras dos clases:

```json theme={null}
{ "decision": "451", "acting_user_email": "ana@acme.co" }
```

```json theme={null}
{ "decision": ["88", "91"], "acting_user_email": "ana@acme.co" }
```

Y un rechazo con motivo:

```json theme={null}
{
  "decision": "reject",
  "comment": "Falta el soporte de la factura",
  "acting_user_email": "ana@acme.co"
}
```

<Note>
  Si el paso exige que decidan **todos** sus responsables, la respuesta vuelve con `status: pending`. No es un error: tu decisión quedó registrada y el paso espera a los demás. Vuelve a consultar el paso actual para ver quiénes faltan — son los `assignees` con `acted: false`.
</Note>

## Cuando no hay nada que responder

Dos situaciones que conviene no confundir:

| Respuesta | Qué significa | Qué hacer |
| - | - | - |
| `204` sin cuerpo | El documento no tiene ningún paso pendiente: su flujo terminó, o todavía no arrancó | Nada. Si acabas de recibir el webhook, vuelve a consultar más tarde |
| `200` con `actionable: false` y `decision: null` | Hay un paso pendiente, pero no se decide desde afuera | No ofrezcas el control. Lo está resolviendo un agente, o su acción se resuelve por otra vía — la causación, por ejemplo, se resuelve al registrar el documento en tu ERP |

## Errores y reintentos

<AccordionGroup>
  <Accordion title="403 · not_assigned">
    El miembro que indicaste no está habilitado para ese paso. Consulta `assignees` antes de decidir; la lista cambia si reconfiguran el flujo.
  </Accordion>

  <Accordion title="409 · step_not_pending">
    Alguien ya lo resolvió, desde Payana o desde otra llamada tuya. No lo reintentes: vuelve a consultar el paso actual del documento y sigue desde ahí.
  </Accordion>

  <Accordion title="422 · invalid_decision">
    El valor no está entre los que el paso ofrece. En etiquetado sin restricción también se rechaza una etiqueta que no exista en tu compañía.
  </Accordion>

  <Accordion title="422 · unsupported_step">
    El paso no se decide por esta API. `GET /workflow-steps/current` lo anticipa con `actionable: false`.
  </Accordion>

  <Accordion title="400 · acting_user_not_found_in_company">
    Ese email no es miembro de tu compañía en Payana. Suele ser un usuario que existe en tu sistema y nunca se dio de alta, o que fue eliminado.
  </Accordion>
</AccordionGroup>

Los códigos `code` son tokens estables en `snake_case`; el `message` es texto para humanos y puede cambiar. Decide sobre el `code`.

## Cómo confirmas que funcionó

Tres señales, de la más inmediata a la más definitiva:

1. **La respuesta del `complete`** trae `status: completed` y `acted_by`.
2. **El paso ya no está pendiente**: volver a consultar devuelve el paso siguiente, o `204` si el flujo terminó.
3. **El efecto del flujo se ve en el documento.** Si al paso le sigue la generación de la cuenta por pagar, aparece en `payables` al consultar el documento — ese array vacío antes de la decisión y con la CxP después es la confirmación de punta a punta.

<Card title="Obtener un documento" icon="file-invoice" href="/api-reference/documents/get">
  Pide `fields=payables,payments` para ver el efecto de la decisión.
</Card>

El registro de actividad del documento, en Payana, deja constancia de que la decisión entró por integración y a nombre de quién — distinguible de esa misma persona actuando desde la aplicación.

## Puesta en marcha

* [ ] Recibe el webhook `document.created` y guarda `data.reference` contra tu identificador
* [ ] Verifica que los emails de quienes deciden existan como miembros en Payana
* [ ] Consulta el paso actual y construye el control desde `decision.kind` y `options`
* [ ] Respeta `options_complete: false` en etiquetado
* [ ] Envía la decisión con `acting_user_email`
* [ ] Trata el `409` como "vuelve a consultar", no como fallo
* [ ] Prueba el ciclo completo contra `api.develop.payana.cloud` antes de producción

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Paso actual de un documento" icon="magnifying-glass" href="/api-reference/workflow-steps/current">
    La referencia completa del primer endpoint.
  </Card>

  <Card title="Resolver un paso" icon="circle-check" href="/api-reference/workflow-steps/complete">
    La referencia completa del segundo.
  </Card>

  <Card title="Flujos personalizados" icon="diagram-project" href="/documentos/flujos-personalizados">
    Cómo se configuran los pasos por los que pasa un documento.
  </Card>

  <Card title="Registro de facturas en el ERP" icon="file-import" href="/api-reference/guias-de-integracion/registro-de-facturas-erp">
    La otra mitad de la integración: catálogo y causación.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.