Step Functions para no morir. Parte 5: Step Manual.
Tags: serverless, aws, tutorial, node
Published: 2022-03-02 15:00:47

Buenas!! :D
Hago el post hoy porque ayer fue feriado. 

![Imagen de buen miercoles](https://media.giphy.com/media/jluHLy0OgYuJvbTRrA/giphy.gif)

En este capítulo vamos a estar viendo los siguientes temas. 

1. Step Functions actualizada.
2. Lambdas dentro de la step.
3. Lambda fuera de la step.
4. Eventos HTTP.
5. Roles.
6. Nuevas validaciones.
7. Pruebas.

En el primer capítulo, hablamos de las task manuales. Las cuales, son las tasks que dependen de una confirmación externa para poder continuar su funcionamiento. 

_Ejemplo gráfico_

![Tareas Manuales](https://docs.aws.amazon.com/es_es/step-functions/latest/dg/images/human-in-the-loop.png)

**En este capítulo vamos a agregar este tipo de task a nuestra máquina de estado actual.** 

##Step Functions actualizada.

En esta imagen tenemos el ejemplo de la step function que vamos a querer crear. 


![Nueva orquestación de la stepFunction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/whhbwkl8v2rxx0ydufd9.png)

Para ello vamos a empezar orquestando las lambdas extras que vamos a utilizar. 

En el archivo `.asl` de la máquina de estados, dentro de `States` y arriba de la task `Medios de pago`, vamos a agregar el siguiente código.

```
"States": {
       "Obtener Planes": {
           "Type": "Task",
           "Resource": "arn:aws:states:::lambda:invoke",
           "Parameters": {
               "FunctionName": {
                   "Fn::GetAtt": [
                       "obtenerPlanes",
                       "Arn"
                   ]
               },
               "Payload": {
                   "Input.$": "$"
               }
           },
           "Next": "Elegir planes",
           "Catch": [
               {
                   "ErrorEquals": [
                       "Error"
                   ],
                   "Next": "Lambda Error"
               }
           ]
       },
       "Elegir planes": {
           "Type": "Task",
           "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
           "TimeoutSeconds": 300,
           "Parameters": {
               "FunctionName": {
                   "Fn::GetAtt": [
                       "elegirPlanes",
                       "Arn"
                   ]
               },
               "Payload": {
                   "Input.$": "$",
                   "token.$": "$$.Task.Token"
               }
           },
           "Next": "Medios de Pago",
           "Catch": [
               {
                   "ErrorEquals": [
                       "Error"
                   ],
                   "Next": "Lambda Error"
               }
           ]
       },
       "Medios de Pago": {
           "Type": "Choice",
           "Choices": [
               {
                   "Variable": "$.medioDePago",
                   "StringEquals": "Debito",
                   "Next": "Pago Debito"
               }
           ],
           "Default": "Pago Credito"
       },
```

Estas van a ser dos states de tipo Task que van a tener como recursos lambdas (aún no creadas).

- Obtener Planes: Es un state de tipo Task que va a obtener los planes de wifi que ofrece la empresa. 
- Elegir planes : Es un state de tipo Task, qué diferencia de obtener planes, su `resource` va a agregar en el invoke la siguiente prop `.waitForTaskToken`.  Y este tipo de lambda va a necesitar un task token que se va a agregar en el apartado de `Payload`. 

```
               "Payload": {
                   "Input.$": "$",
                   "token.$": "$$.Task.Token"
               }
```

**Elegir planes** va a ser la encargada de enviar las opciones al cliente. 
Mientras espera la respuesta va a quedar en un estado de _Pending_, hasta que reciba la data necesaria para continuar.

Por eso lleva la propiedad de `TimeoutSeconds`,  para regular el consumo de memoria de nuestra máquina de estado y que no quede en pending eternamente. 

```
"TimeoutSeconds": 300,
```
Es importante cambiar el valor de la propiedad `StartAt` por **Obtener Planes** porque ahora nuestra máquina de estado comienza con un nuevo state.

Todavía no podemos hacer el deploy porque las lambdas que pasamos por parámetro no existen realmente. 


##Lambdas dentro de la step.


###Lambda ObtenerPlanes

Vamos a comenzar creando la lambda `obtenerPlanes.js`. Lo que quiero que devuelva es un **json** con los distintos planes a los que puede acceder el cliente. _Los voy a importar desde un json porque después quiero usarlos._

```
const planes = require("./../resources/planes")
 
const obtenerPlanes = () => {
 return planes
};
 
exports.handler = (event, context, callback) => {
 callback(null, obtenerPlanes());
};
```

En el `const planes` tenemos el `require`. 
Yo guarde el json en `resources => planes.js`

**planes.js**

```
exports.planes = [
   {
       "plan": "1MB",
       "precio": 1000
   },
   {
       "plan": "10MB",
       "precio": 2000
   },
   {
       "plan": "50MB",
       "precio": 5000
   },
   {
       "plan": "100MB",
       "precio": 8000
   }
]
```

###Lambda elegirPlanes 

Vamos a crear la lambda `elegirPlanes.js`. En esta vamos a tener distintos pasos. Primero debemos instalar el paquete de `aws-sdk`
Es importante instalarlo en el `devDependecies` para que no sobrecargue a la lambda. 

```
npm install –save-dev aws-sdk
```

Una vez instalado, ya lo podemos hacer importar y comenzar a trabajar en nuestra lambda.

```
const AWS = require('aws-sdk');
 
const getParameters = (event) => {
 const urlQueue = process.env.URL_SQS || '';
 console.log(urlQueue);
 if (urlQueue === '') {
   throw new Error('La URL no existe')
 }
 
 const params = {
   MessageBody: JSON.stringify({
     planes: event.Input.Payload,
     taskToken: event.token
   }),
   QueueUrl: urlQueue,
 };
 return params;
};
exports.handler = async (event) => {
 try {
   const sqs = new AWS.SQS();
   console.log('event:  ', JSON.stringify(event, null, 2));
   const params = getParameters(event);
   console.log(params);
   await sqs.sendMessage(params).promise();
   return event;
 } catch (e) {
   throw new Error(e);
 }
};
```

Lo que deseamos hacer en esta lambda, es enviar los planes al cliente mediante la cola de sqs que creamos antes. 

Vamos a instanciar el servicio de sqs con aws en el handler. 

```
const sqs = new AWS.SQS();
```

Luego, para enviar un mensaje a la cola de sqs debemos correr el siguiente código. 

```
await sqs.sendMessage(params).promise();
```

 **De dónde sale esa información?** 
De la [documentación](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/SQS.html#sendMessage-property) de aws-sdk para sqs.

Sabemos que necesitamos los parámetros para que el mensaje sea enviado. Para eso vamos a trabajar sobre la función de `getParameters() `que nos tiene que devolver estos parámetros.

```
const params = {
   MessageBody: JSON.stringify({
     planes: event.Input.Payload,
     taskToken: event.token
   }),
   QueueUrl: urlQueue,
 };
```

Los parámetros a devolver son 
- el mensaje que queremos enviar.
- el token que vamos a necesitar para referirnos a la instancia de la stepFunction.
- la url de la cola de sqs. 

La url de la cola de sqs, la podíamos importar desde el stack de cloudFormation _(tal cual lo hacemos en el archivo asl en el state final donde mandamos el mensaje)_. Pero a esa variable la vamos a importar a nivel serverless _(lo vamos a ver en unos párrafos más adelante)_. 

En la lambda lo importamos de la siguiente manera

```
const urlQueue = process.env.URL_SQS || '';
```

###Lambdas en serverless

Vamos a agregar las dos funciones junto las que ya estaban creadas. _(En el apartado functions)_

```
  obtenerPlanes:
    handler: ./src/lambdas/obtenerPlanes.handler
  elegirPlanes:
    handler: ./src/lambdas/elegirPlanes.handler
```

La parte importante está en `elegirPlanes` porque es donde debemos agregar la url de la sqs.
La agregamos en el `serverless.yml` y sobre la lambda en donde queremos importar. 
**¿Por qué?** Porque es la forma más segura de crear variables de entorno seguras, ya que evitamos que el resto de las lambdas acceda a información que no necesitan. 

```
    environment:
      URL_SQS: ${cf:contratarwifiplan-${opt:stage, 'dev'}.SendQueueURL}
```

Como ven, lo importamos desde el stack de cloudFormation como en el capítulo pasado. 

Y la lambda de elegir planes debería quedar así. 

```
  elegirPlanes:
    handler: ./src/lambdas/elegirPlanes.handler
    environment:
      URL_SQS: ${cf:contratarwifiplan-${opt:stage, 'dev'}.SendQueueURL}
```

###Lambdas fuera de la step.

Cuando enviemos la data al cliente con la lambda `elegirPlanes.js`, esta Task va a quedar pendiente esperando una respuesta. 
Para retomar el flujo de la stepFunction necesitamos de una lambda que, usando el token de único uso que le enviamos al cliente, “reviva” la stepFunction para que continúe su flujo. 

Esto lo creamos de la siguiente manera: 
Creamos la lambda llamada `recibirRespuesta.js`, la cual va a recibir la respuesta del cliente, y enviar la señal a la step function para continuar. 
Esta lambda no forma parte del flujo que escribimos en el asl. 

El código es el siguiente: 

```
const AWS = require('aws-sdk');
 
const recibirRespuesta = (event) => {
   const eventParse = JSON.parse(event.body);
 
   console.log(eventParse)
 
   return {
       output: JSON.stringify(eventParse),
       taskToken: eventParse.taskToken,
   };
};
exports.handler = async (event) => {
   const params = recibirRespuesta(event);
   try {
       const stepfunctions = new AWS.StepFunctions();
       console.log(
           `Llamando a la stepFunction con estos parametros ${JSON.stringify(
               params
           )}`
       );
       await stepfunctions.sendTaskSuccess(params).promise();
       return {
           statusCode: 200,
           body: JSON.stringify(params),
       };
   } catch (error) {
       return {
           statusCode: 500,
           body: JSON.stringify(error),
       };
   }
};
```

Parecido a la lambda `elegirPlanes.js`, necesitamos instanciar el servicio de stepFunction de aws, importando el `aws-sdk`. 

La función que se utiliza en estos casos es la de `sendTaskSucces()` que comunica el mensaje de éxito para que la step Function continúe. 

Les dejo la [documentación](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/StepFunctions.html#sendTaskSuccess-property) del aws-sdk. 

Ahora sabemos cuales son los parámetros necesarios para esta función.

```
return {
       output: JSON.stringify(eventParse),
       taskToken: eventParse.taskToken,
   };
```

En la prop `output` va a ir la data que necesitamos que la task elegiPlanes devuelva como output, y el `tasktoken` nos sirve para hacer referencia a cuál instancia de StepFunction nos estamos refiriendo. 

La razón de estos returns 

```
return {
           statusCode: 200,
           body: JSON.stringify(params),
       };
```

lo vamos a estar explicando más adelante cuando hablemos de los eventos HTTP que despiertan ciertas lambdas. 

###Lambda en serverless

Vamos a declarar la lambda en el `serverless.yml` 

```
recibirRespuesta:
   handler: ./src/lambdas/recibirRespuesta.handler
```

##Eventos HTTP.

En el capítulo anterior habíamos visto como una cola de sqs podía despertar una lambda. 
En este, vamos a ver que las lambdas también pueden ser despertadas por `eventos http`, trabajando con el servicio de `ApiGateway`. 

**¿Cómo lo configuramos?**
Vamos a querer que nuestra lambda `recibirRespuesta` sea despertada por un `POST` con los datos del servicio y método de pago que eligió el cliente. 
Debajo del `handler` de la función, vamos a agregar el siguiente código. 

```
    events:
      - http:
         path: /contratar-wifi/recibirRespuesta
         method: post
```

El método que queremos utilizar, es un post, y en el path va la ruta del endpoint. 

En el servicio de `ApiGateway` vamos a poder acceder al endpoint. Y haciendo click en pruebas se puede acceder al cuerpo del endpoint. 



![Pantalla Apigateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hz81zibyrkatw4arxywu.png)

En el cuerpo de la solicitud.  Va a ir el JSON que queremos enviarle a la lambda.


![Ruta en el ApiGateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq5e9i6lp2lfsx6dudm3.png)

Si lo notan, es el json que utilizábamos en el capítulo pasado pero con el agregado del task token. 

```
{
   "servicio": {
       "plan": "15MB",
           "precio": 1000
   },
   "medioDePago": "Debito",
       "tarjeta": {
       "cantCuotas": "06",
           "nroTarjeta": "1234567898745896"
   },
   "taskToken": "AAAAKgAAAAIAAAAAAAAAAQ9OfVcpRULG9PyaPvbJhBV2NFiha4ILZcflTahDJbdQ/gFRlyzjh7UVvijwZyvXMRz64qH1kF3aUkTX18Dh0EfJWZzMJ0zEhPemHjct6KmkWqSb0+BpFmq3x0HlpOlam9W3tXD1Flp7nnaSPs+hfN6877ele8f0721HaQujSasqrQpsNjTVYpiRxrDOL1sgIpv2UX9oflVkETfsYERnce+ijtxdEQVf/nXyizc7F+AZTzIp0AG4FBmS5yNXgSWLWD0cvNHmz2ngtx1Fv3MfhSyAY/f0hpCY1h53fyYqnuodJH3AQiwii6cDHU1Bdd3oGlMioWU5OYXXv/jrZwAuy7oH1CheD91c+b/xerKEfKmn3KM8w6yebO8wWUosq8mbfGbPvaElj8WHkg7YdEmnixFccevbyX5RrVZOuNAGKJp2zBouEa6RcaowISvMv1NMbbiXKPp1MMzx3bfo5+0S+sOjagmneER6O5Y0cZXpeiji/4vGFIcDrd1bEcHID1FNll1OXhWXO8MUb7PHWH07JxnNyV0nrrTNHE4YZZlg6rR48+gD7IaGko5Kc/pzR84CExw1UbWtLMNaYhlP1GVfMkAbJ3/LX0Zocq5kDfZhu2V50l1tHoMqhNTRGo2o824Q+g=="
}
```

Y por ultima, la **razón de los returns** en la lambda .

```
return {
           statusCode: 200,
           body: JSON.stringify(params),
       };
```
y

```
return {
           statusCode: 500,
           body: JSON.stringify(error),
       };
```

Esto sucede porque el **evento http** necesita que la respuesta cuente con una prop `statusCode` con un number, y con un `body` que contenga la data en formato string. 

**Si hacemos el return diferente, ¿va a funcionar?** 
La respuesta es **sí**, porque el `sendTaskSucces()` se envía antes que el `return`, entonces, la stepFunction va a continuar con su ejecución **PERO** la respuesta que vamos a obtener por http sera de error, por no tener el formato de respuesta correcto.

##Roles

No tenemos que olvidar que nuestros servicios a veces necesitan permisos para funcionar y estos se otorgan mediante roles. 
En este caso, vamos a necesitar **dos roles** (que vamos a escribir en `resources => LambdaRole.yml`) para dos de nuestras lambdas.

1 - El primer role, va a ser para la lambda que contiene el `WaitForTaskToken`. 
Necesitamos permisos :
- Para loguear la data de la lambda.
- Para poder enviar mensajes a la cola de SQS.

```
ElegirPlanesLambdaRole:
 Type: AWS::IAM::Role
 Properties:
   RoleName: ElegirPlanesLambdaRole
   AssumeRolePolicyDocument:
     Statement:
     - Effect: Allow
       Principal:
         Service:
           - 'lambda.amazonaws.com'
       Action:
         - 'sts:AssumeRole'
   Policies:
     - PolicyName: statePolicy
       PolicyDocument:
         Statement:
           - Effect: Allow
             Action:
               - sqs:SendMessage
             Resource:
               - Fn::GetAtt: [SendQueue, Arn]
           - Effect: Allow
             Action:
               - 'logs:CreateLogGroup'
               - 'logs:CreateLogStream'
               - 'logs:PutLogEvents'
               - 'logs:DescribeLogStreams'
             Resource:
               - 'arn:aws:logs:*:*:*'
```

2 - El segundo role, va a ser para conceder permisos a la lambda que va a `recibir la data` desde el endpoint y continuar la ejecución de la stepFunctions. 
Esos permisos son: 
- El de loguear la información de la lambda
- El de poder enviar el éxito de la ejecución. 

```
RecibirRespuestasLambdaRole:
 Type: AWS::IAM::Role
 Properties:
   RoleName: RecibirRespuestasLambdaRole
   AssumeRolePolicyDocument:
     Statement:
     - Effect: Allow
       Principal:
         Service:
           - 'lambda.amazonaws.com'
       Action:
         - 'sts:AssumeRole'
   Policies:
     - PolicyName: statePolicy
       PolicyDocument:
         Statement:
           - Effect: Allow
             Action:
               - states:SendTaskSuccess
               - states:SendTaskFailure
             Resource: "*"
           - Effect: Allow
             Action:
               - 'logs:CreateLogGroup'
               - 'logs:CreateLogStream'
               - 'logs:PutLogEvents'
               - 'logs:DescribeLogStreams'
             Resource:
               - 'arn:aws:logs:*:*:*'
```

Y por último vamos a importar, los roles en `serverless.yml` y después vamos a asignarlos en las lambdas correspondientes.

Nuestras importaciones en  resources deberían quedar de la siguiente manera, con los dos nuevos roles agregados.

```
resources: 
  Resources: 
    SendQueue: ${file(./src/resources/SQS.yml):SendQueue}
    SendQueueDLQ: ${file(./src/resources/SQS.yml):SendQueueDLQ}
    ContratarServicioWifiMachineRole: ${file(./src/resources/StepFunctionsRole.yml):ContratarServicioWifiMachineRole}
    ElegirPlanesLambdaRole: ${file(./src/resources/LambdaRole.yml):ElegirPlanesLambdaRole}
    RecibirRespuestasLambdaRole: ${file(./src/resources/LambdaRole.yml):RecibirRespuestasLambdaRole}
```

Y las lambdas deberían quedar de la siguiente manera.

```
  elegirPlanes:
    handler: ./src/lambdas/elegirPlanes.handler
    environment:
      URL_SQS: ${cf:contratarwifiplan-${opt:stage, 'dev'}.SendQueueURL}
    role:
      Fn::GetAtt: ['ElegirPlanesLambdaRole', 'Arn']
```
y 

```
  recibirRespuesta:
    handler: ./src/lambdas/recibirRespuesta.handler
    events:
      - http:
         path: /contratar-wifi/recibirRespuesta
         method: post
    role:
      Fn::GetAtt: ['RecibirRespuestasLambdaRole', 'Arn']
```

###Actualizar roles de las step
Como agregamos nuevas lambdas a nuestra step function debemos ir a el archivo `StepFunctionsRole.yml` y agregarlas tambien en el role. 

```
- !Join ['-', [ !Join [':', ['arn:aws:lambda',!Ref 'AWS::Region', !Ref 'AWS::AccountId' ,'function', !Ref 'AWS::StackName']], 'obtenerPlanes' ]]
- !Join ['-', [ !Join [':', ['arn:aws:lambda',!Ref 'AWS::Region', !Ref 'AWS::AccountId' ,'function', !Ref 'AWS::StackName']], 'elegirPlanes' ]]
```

##Agregar nuevas validaciones

Antes de terminar la máquina de estado, y poder hacer nuestras pruebas, necesitamos agregar unas nuevas validaciones en las lambdas de pagos.
Queremos asegurarnos que los planes que elige el cliente, pertenecen a la oferta de la empresa.  

En ambas lambdas debemos importar los planes ofrecidos.

```
const planes = require("./../resources/planes")
```

Y luego la función que va a validar la existencia. 

```
const validarPlan = (data) => {
 const { plan } = data.servicio;
 console.log(plan);
 console.log(planes.planes.length);
 let fueValidado = false;
 let arrayPlanes = planes.planes
 for(let i = 0; i < arrayPlanes.length; i++) {
   console.log('entro');
   console.log( arrayPlanes[i].plan + "   " + plan);
   if (arrayPlanes[i].plan == plan) {
     fueValidado = true;
     return
   }
 }
 console.log(fueValidado);
 if (!fueValidado) throw new Error('El plan no existe')
}
```

En mi caso agregue esta validación dentro de la función `pagoConDebito` y `pagoConDebito`. Debajo de `validarPago`, agrego: 

```
validarPlan(inputData)
```

Ahora sí, ya tenemos nuestra StepFunction completa. 

Y podemos correr el 

```
sls deploy
```

##Pruebas

Una vez que tengamos nuestra stepFunction en la nube vamos a comenzar con las pruebas. 

A la hora de iniciar la ejecución, el json con el que lo iniciamos no es de gran importancia.

![Nueva ejecución con json indiferente](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vcauxtxu8debv0td2gmo.png)

Una vez iniciada podemos ver como la lambda que tiene el resource `waitForTaskToken` queda _pendiente_ después de enviar la data.


![StepFunction con ejecución pendiente](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gb1qamfg36b3mjq1dosx.png)

Si vamos al historial de ejecuciones, vamos a poder sacar el token que necesitamos para hacer referencia a la misma instancia. 


![Historial de ejecución con el token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t623jya0n7dnncperd93.png)

Con esta data, vamos a ir al servicio de apiGateway así continuamos con la ejecución.

###CASO DE ERROR

En el cuerpo del endpoint debemos usar el siguiente json.

```
 {
     "servicio": {
         "plan": "15MB",
             "precio": 1000
     },
     "medioDePago": "Debito",
         "tarjeta": {
         "cantCuotas": "06",
             "nroTarjeta": "1234567898745896"
     },
     "taskToken": "AAAAKgAAAAIAAAAAAAAAAYWwkS4HEc5xR92k3T7sftkXFTOXMIE06rDrmlQ5Fr7rFSgqK+lIC6T2xB5mOydgGAdRNhjJk6zHuMhriHC1YeYmTdRVwx1m6i8t0ZpGgeD+2xDhw7oCE7uomervRzTQshROjUIgyXFuK4zP7EkqDg952/V1vFO/rw4k7eCufoKfnjkrFEwnyWj31V5cIUWSfZyjF5xe4KPrvzACqR2TZFdKu5SPpU5vikBPpmdIVyFMnSudPR1asv7j3hEvjF/ZKrYSPDok27wLjH9shaYysPncEiDbe1AysIq10bbI+YyeeUWm7kWC4xeVJcNqv5aupX2xGifWmolvvXlHFCXAjpoUTkPNpYO1jrgE2/p2QBGURzDaEWgs4ffJLxMGwdVDYeRZPK+y1EmESnbk5zys38MNy3iQVd++vvFD90EzOKAHpGGQ9iXBvp12prXbywUg/CUSxPBS/wKQCSsdYjImfLC+NXgXCDXmi8Bsc980vyXnZfVEc6Aq8h7NKE6rJTBkCb1BD34rox1Rqs4zkp31Gf57E33tC5oJSIStbNx2ltSJPMOKqOeQvaKmzI30lsfudpM56mEWnV8vEykyLfGTwxZymHj1U3RUaLhbIoKI7GzMggFDuwy9uZhDVXzak0A7rQ=="
 }
```

**Recuerden que debemos modificar el token con el valor que obtengan de su ejecución.**
Si el endpoint está construido de manera correcta y el json está correcto, el resultado debería ser el siguiente.


![Respuesta 200 por HTTP en ApiGateway](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdcpjy8j8suhneojg3m0.png)

Al volver a la ejecución, debemos notar que la stepFunction terminó con error debido a que el plan no existe en la oferta. 

![Ejecución con plan inexistente](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2bzizwxy0s9yb4i6t5pm.png)

###CASO DE ÉXITO

En el caso de éxito el json debería ser el siguiente. 

```
 {
     "servicio": {
         "plan": "1MB",
             "precio": 1000
     },
     "medioDePago": "Debito",
         "tarjeta": {
         "cantCuotas": "06",
             "nroTarjeta": "1234567898745896"
     },
     "taskToken": "AAAAKgAAAAIAAAAAAAAAAYWwkS4HEc5xR92k3T7sftkXFTOXMIE06rDrmlQ5Fr7rFSgqK+lIC6T2xB5mOydgGAdRNhjJk6zHuMhriHC1YeYmTdRVwx1m6i8t0ZpGgeD+2xDhw7oCE7uomervRzTQshROjUIgyXFuK4zP7EkqDg952/V1vFO/rw4k7eCufoKfnjkrFEwnyWj31V5cIUWSfZyjF5xe4KPrvzACqR2TZFdKu5SPpU5vikBPpmdIVyFMnSudPR1asv7j3hEvjF/ZKrYSPDok27wLjH9shaYysPncEiDbe1AysIq10bbI+YyeeUWm7kWC4xeVJcNqv5aupX2xGifWmolvvXlHFCXAjpoUTkPNpYO1jrgE2/p2QBGURzDaEWgs4ffJLxMGwdVDYeRZPK+y1EmESnbk5zys38MNy3iQVd++vvFD90EzOKAHpGGQ9iXBvp12prXbywUg/CUSxPBS/wKQCSsdYjImfLC+NXgXCDXmi8Bsc980vyXnZfVEc6Aq8h7NKE6rJTBkCb1BD34rox1Rqs4zkp31Gf57E33tC5oJSIStbNx2ltSJPMOKqOeQvaKmzI30lsfudpM56mEWnV8vEykyLfGTwxZymHj1U3RUaLhbIoKI7GzMggFDuwy9uZhDVXzak0A7rQ=="
 }
```

Al igual que en el caso de error la respuesta por http debe dar 200, pero la step function debe continuar su ejecución sin error. 


![Ejecución de step function correcta](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nmq9hpfkw1q0tjm97lv.png)

##Final

Bueno, llegamos al final de este capítulo.

![Gif llegamos al final](https://media.giphy.com/media/3o6Mbnll2gudglC3HG/giphy.gif)

Y ya tenemos una stepFunction que tiene una intervención manual por parte del cliente.  :D

Espero que les haya sido de ayuda. 
Recuerden que si quieren, pueden invitarme un cafecito. 

[![Invitame un café en cafecito.app](https://cdn.cafecito.app/imgs/buttons/button_2.svg)](https://cafecito.app/giulianaeolmos)

Y que si tienen alguna duda pueden dejarla en los comentarios.