# Flow Standard V2 (borrador)

## Objetivo

Flow Standard V2 define un runtime generico y determinista para conversaciones
transaccionales. No contiene reglas especiales por tenant, industria, nombre de
slot o proveedor.

Los datos no producen acciones implicitamente:

- capturar un documento no implica buscar un cliente;
- capturar una ubicacion no implica consultar cobertura;
- capturar un comprobante no implica registrar un pago.

La relacion entre un dato y una capacidad siempre se declara en un step
`action`.

## Responsabilidades

El runtime:

- conserva estado;
- determina el step activo;
- evalua branches;
- construye inputs de actions;
- devuelve el proximo limite operativo.

El runtime no:

- interpreta lenguaje natural;
- redacta el texto final;
- inventa transiciones;
- ejecuta integraciones;
- conoce semantica especial de DNI, ventas, soporte o cobranza.

## Estructura minima

```json
{
  "id": "soporte_basico",
  "version": 2,
  "name": "Soporte basico",
  "settings": {
    "entry_step": "pedir_documento"
  },
  "slots": {},
  "backend_facts": {},
  "steps": []
}
```

## Step question

Captura un slot.

```json
{
  "id": "pedir_documento",
  "type": "question",
  "slot": "documento",
  "instruction": "Solicitar DNI o CUIT.",
  "next_step": "validar_cliente"
}
```

Si el slot ya existe, el runtime avanza sin volver a preguntarlo.

## Step action

Solicita al orquestador que ejecute una capacidad registrada.

```json
{
  "id": "validar_cliente",
  "type": "action",
  "action": "buscar_cliente",
  "input": {
    "dni": {
      "$from": "slot.documento"
    }
  },
  "on_success": "continuar",
  "on_failure": {
    "target": "pedir_otro_documento",
    "clear": ["slot.documento"]
  }
}
```

El runtime devuelve `execute_action`, pero no llama directamente a la
integracion. El orquestador ejecuta la capacidad y reingresa un evento
`action_result` con el mismo token.

Las transiciones pueden ser un step id o un objeto. La forma objeto permite
limpiar datos que dejaron de ser validos:

```json
{
  "target": "pedir_documento",
  "clear": ["slot.documento", "fact.cliente"]
}
```

El borrado es explicito: una falla de action no implica por si sola que un slot
sea incorrecto.

## Bindings

### Referencia

```json
{ "$from": "slot.documento" }
{ "$from": "fact.cliente.id" }
{ "$from": "context.tenant_id" }
{ "$from": "action.consultar_factibilidad.planes" }
```

### Literal

```json
{ "$literal": "ventas" }
```

### Template

```json
{
  "$template": "Alta solicitada por {{slot.nombre}} en {{slot.localidad}}"
}
```

Los objetos y arrays de `input` se resuelven recursivamente.

## Fuentes estructuradas de slots

Un slot puede declarar fuentes deterministicas. Capturar el dato no ejecuta
ninguna action:

```json
{
  "documento": {
    "type": "string",
    "sources": ["detected.document"],
    "normalize": ["digits_only"]
  }
}
```

Fuentes iniciales:

- `detected.*`: datos encontrados por normalizadores de entrada;
- `context.*`: datos confiables entregados por el canal o tenant.
- `message.text`: texto normalizado del mensaje actual.

Normalizadores iniciales:

- `trim`
- `lowercase`
- `uppercase`
- `digits_only`

Los enums pueden configurar aliases sin agregar reglas al codigo:

```json
{
  "categoria": {
    "type": "enum",
    "sources": ["message.text"],
    "match_mode": "contains",
    "values": ["sin_servicio", "lentitud"],
    "aliases": {
      "sin_servicio": ["sin internet", "no tengo conexion"],
      "lentitud": ["anda lento"]
    }
  }
}
```

`match_mode` puede ser `exact` o `contains`. El modo `contains` solo se aplica
cuando el flujo lo declara; el runtime no conoce el significado de los aliases.

## Step branch

```json
{
  "id": "resolver_factibilidad",
  "type": "branch",
  "cases": [
    {
      "when": {
        "ref": "fact.factibilidad",
        "op": "eq",
        "value": "disponible"
      },
      "go_to": "mostrar_planes"
    }
  ],
  "default_next": "derivar_revision"
}
```

Operadores iniciales:

- `eq`
- `neq`
- `exists`
- `in`

También se aceptan condiciones compuestas con `all`, `any` y `not`.

`default_next` es obligatorio para impedir que una rama deje el flujo sin
salida.

## Step message

```json
{
  "id": "indicar_reinicio",
  "type": "message",
  "instruction": "Indicar reiniciar el modem y probar nuevamente.",
  "next_step": "preguntar_resultado"
}
```

Al devolver `emit_message`, el estado ya queda posicionado en `next_step`.
Esto evita que el mensaje se repita en el siguiente turno.

## Step end

```json
{
  "id": "fin",
  "type": "end"
}
```

## Resultados del runtime

- `needs_input`
- `execute_action`
- `emit_message`
- `blocked`
- `completed`
- `invalid_flow`
- `invalid_state`
- `invalid_event`
- `runtime_error`

## Idempotencia

Cada `execute_action` incluye un token estable mientras la action permanece
pendiente. Si el orquestador vuelve a solicitar el runtime sin entregar un
resultado, recibe la misma action y el mismo token.

El ejecutor de capacidades debe usar ese token como clave de idempotencia para
evitar búsquedas, altas, cobros o transferencias duplicadas.
