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

# Meu primeiro benApp

> Vamos criar um projeto de exemplo passo-a-passo 

## Entendendo a API do beneficio

Para o exemplo vamos simular um beneficio chamado "Dummy Saúde", a Dummy Saúde possui uma API com os seguintes recursos:

* `POST /customer/` - Cria um novo cliente
* `POST /customer/{:id}/plan` - Adiciona um novo cliente no plano de saude
* `GET /customer/:cpf` - Recupera os dados de um cliente
* `DELETE /customer/{:id}/plan` - Remove um cliente do plano de saude
* `DELETE /customer/{:id}` - Deleta um cliente

## Criando o projeto

**Requerimentos**
`Node 22`

**Instalação**
`npm i -g @benup/bensdk`

**Criando o projeto**
`ben init teste-app`
Será gerado uma pasta chamada teste-app

**Abra a pasta com o edito de código de sua preferencia**

## Definindo o beneficio

Abra o arquivo  `benefit-definition.ts` dentro da pasta `src`, esse arquivo é responsavel por informar ao Integrador como seu benAPP será executado, vamos entender ele abaixo.

O `benefitID` é o identificador único do seu beneficio, você receberá o seu quando iniciar a integração

```ts theme={null}
const benefitDefinition = {
	benefitID: 'TEST-APP',
	...
}
```

O `availableActions` define quais ações o benAPP manipula, podendo ser GRANT, REVOKE, RECHARGE, como o nosso beneficio "Dummy Saúde" é um plano de saúde apenas o GRANT e o REVOKE já é o suficiente.

```ts theme={null}
const benefitDefinition = {
	benefitID: 'TEST-APP',
	availableActions: ['GRANT', 'REVOKE'],
	...
}
```

## Configurando a máquina de estado

Um benAPP é composto por uma sequencia de funções chamada handlers que são executadas em uma ordem pré definida.

Essa ordem é definida dentro do `benefit-definition.ts` em um campo chamado `stateMachine` e é o ponto mais importante da nossa integração.

O projeto já gerou ela pra gente como está abaixo, vamos adaptar para nosso cenário:

```ts theme={null}
stateMachine: {
	GRANT: {
		REQUESTED_GRANT: {
			next: 'SYNC_EXISTING_GRANT'},
		SYNC_EXISTING_GRANT: {
			next: 'REQUEST_CREATE_EMPLOYEE',
			skip: 'GRANTED'},
		REQUEST_CREATE_EMPLOYEE: {
			next: 'GRANTED',
			fail: 'FAILED_TO_GRANT'}},
  
	REVOKE: {
		REQUESTED_REVOKE: {
			next: 'SYNC_EXISTING_REVOKE'},	
		SYNC_EXISTING_REVOKE: {
			next: 'REQUEST_REMOVE_EMPLOYEE',
			skip: 'REVOKED'},
			
		REQUEST_REMOVE_EMPLOYEE: {
			next: 'REVOKED',
			fail: 'FAILED_TO_REVOKE'}}
},
```

### Fluxo de GRANT

Conforme a API do nosso "Dummy Saúde" para realizar um GRANT precisamos:

* Verificar se o cliente existe realizando um  `GET /customer/:cpf`
* Caso o cliente exista encerra o fluxo.
* Caso o cliente não exista,
  * Criamos um novo cliente em `POST /customer/`
  * Adicionamos o cliente no plano em `POST /customer/:id/plan`
  * Encerramos o fluxo

Na nossa maquina de estado esse fluxo é configurado da seguinte forma:

```ts theme={null}
stateMachine: {
	GRANT: {
		REQUESTED_GRANT: {
			next: 'SYNC_EXISTING_GRANT'},
		SYNC_EXISTING_GRANT: {
			next: 'REQUEST_CREATE_EMPLOYEE',
			skip: 'GRANTED'},
		REQUEST_CREATE_EMPLOYEE: {
			next: 'REQUEST_ADD_PLAN',
			fail: 'FAILED_TO_GRANT'}
		REQUEST_ADD_PLAN: {
			next: 'GRANTED',
			fail: 'FAILED_TO_GRANT'}},
	...
},
```

### Fluxo de REVOKE

Para `REVOKE` temos o seguinte fluxo:

* Verificar se o cliente existe realizando um  `GET /customer/:cpf`
* Caso não exista, encerra o fluxo.
* Caso o cliente exista:
  * Removemos o funcionario do plano em: `DELETE /customer/{:id}/plan`
  * Desativamos o cliente em: `DELETE /customer/{:id}`
  * Encerramos o fluxo

Na nossa maquina de estado esse fluxo é definido da seguinte forma:

```ts theme={null}
stateMachine: {
	REVOKE: {
		REQUESTED_REVOKE: {
			next: 'SYNC_EXISTING_REVOKE'},
		SYNC_EXISTING_REVOKE: {
			next: 'REQUEST_DEACTIVATE_PLAN',
			skip: 'REVOKED'},
		REQUEST_DEACTIVATE_PLAN: {
			next: 'REQUEST_REMOVE_EMPLOYEE',
			skip: 'REVOKED'},
		},
		REQUEST_REMOVE_EMPLOYEE: {
			next: 'REVOKED',
			fail: 'FAILED_TO_REVOKE'}}
	...
},
```

> **Importante**
> Para cada requisição na API do beneficio, deve ser criado um novo estado

## Desenvolvendo seu benApp

### Declarando logs

Para cada estado que criamos, vamos criar um esquema de logs, os logs são importantes para auditoria, investigação de problemas e histórico.

```ts theme={null}
log: {
  GRANT: z.object({
    SYNC_EXISTING_GRANT: z.object({}),
    REQUEST_CREATE_EMPLOYEE: z.object({}),
    REQUEST_ADD_PLAN: z.object({}),
  }),

  REVOKE: z.object({
    SYNC_EXISTING_REVOKE: z.object({}),
    REQUEST_REMOVE_EMPLOYEE: z.object({}),
    REQUEST_DEACTIVATE_PLAN: z.object({})
  })
}
```

### Gerando seus handlers

Agora, iremos criar uma pasta chamada `handlers` dentro do `src` e para cada estado na maquina que não seja os iniciais e os finais iremos gerar um arquivo,

Porem não precisamos fazer isso manualmente, basta executar `npm run generate` na pasta raiz do nosso `test-app`, se o nosso `benefit-definition.ts`  estiver configurado configurado corretamente, os arquivos serão gerados.

### Primeira integração

#### Configurando o contexto

A primeira coisa que vamos fazer é mockar a API do nosso "Dummy Saúde", para isso vamos abrir o arquivo `context.config.ts` e colocar o seguinte trecho após a declaração do `apiMocks.ts`.

```ts theme={null}
apisMocks.benefitsAPI.onPost('/customer/').replyOnce(201, {
  id: '1',
  name: 'João da Silva',
  cpf: '41786828650'
});

// Adiciona um novo cliente no plano de saúde
apisMocks.benefitsAPI.onPost('/customer/1/plan').replyOnce(200, {
  message: 'Cliente adicionado ao plano com sucesso'
});

// Recupera os dados de um cliente pelo CPF
apisMocks.benefitsAPI.onGet('/customer/41786828650').replyOnce(200, {
  id: '1',
  name: 'João da Silva',
  cpf: '41786828650',
  planActive: true
});

// Remove um cliente do plano de saúde
apisMocks.benefitsAPI.onDelete('/customer/1/plan').replyOnce(200, {
  message: 'Cliente removido do plano com sucesso'
});

// Deleta um cliente
apisMocks.benefitsAPI.onDelete('/customer/1').replyOnce(200, {
  message: 'Cliente deletado com sucesso'
});
```

> **Importante**
>
> Caso você queria rodar em um ambiente de staging ou homologação, pode fazer isso mudando a benefitsAPI para uma instancia real do Axios.
>
> O `context.config.ts` é completamente ignorado pelo Integrador, ele é nosso Playground, o Integrador irá preencher ele com informações reais

##### E onde está a autenticação?

A autenticação é feita pelo integrador e não precisamos nos preocupar com isso dentro de um benApp, essa etapa será feita durante a revisão do seu beneficio.

#### Modificando o handler

Vamos começar a alterar nosso handler `sync_existing_grant.handler.ts` !

Vamos buscar o funcionario na API, perceba que a API do nosso beneficio "Dummy Saúde" já existe no contexto, pois o Integrador ja fez isso.

```ts theme={null}
ctx.logger.info('Buscando o funcionário');

const res = await ctx.benefitsAPI.get<BenefitResponse>(`/customer/${action.target.employee.cpf}`);

const { status, data } = res;
```

Em caso de erro durante a API, vamos finalizar a execução do handler dizendo para o Integrador o que ele deve fazer em seguida:

```ts theme={null}
if (status == 500) {
  ctx.logger.info('Erro ao buscar o funcionário');
  
  return {
    handlerResponse: {
      response: "UNACK",
      options:{
        maxRetries: 5, // Diz para o integrador re tentar no máximo 5 vezes
        delayBetweenRetries: 5, //Delay em segundos entre as tentativas
        // Pode ser "fixed" ou "exponential" 
        // fixed: Retenta a execuçãp a cada 5 segundos 
        // exponential: Retenta a execução em um tempo exponencial, por exemplo
        // Primeira tentativa: 5 segundos depois
        // Segunda tentativa: 25 segundos depois
        // Terceira tentativa: 625 segundos depois
        delayMode: "fixed", 
      }
    },
  };
}
```

Caso a resposta seja executada com sucesso o handler irá dizer qual o proximo handler deve ser executado:

```ts theme={null}
return {
  messageResponse: 'ACK',
  action: {
    state: data != null ? stateMachine.skip : stateMachine.next,
    ctx: {},
    logs: {
      benefitAPI: getLogsFromResponse(res)
    }
  }
};
```

#### Trabalhando com contexto

O contexto são variáveis que são passadas entre a execução dos handlers. As variáveis do contexto são declaradas no `benefit-definition.ts` dentro do `actions` vamos modificar o campo `ctx` adicionando o funcionarioId

```ts theme={null}
{
  actions: {
    ctx: z.object({
      funcionarioId: z.string().optional()
    }),
  }
}
```

Vamos preencher essa variavel na execução do `request_create_employee.handler.ts`

```ts theme={null}
ctx.logger.info('Criando o funcionário');
const res = await ctx.benefitsAPI.post<BenefitResponse>(`/customer/`, {
  data:{
    cpf: action.target.employee.cpf
  }
});

const { status, data } = res;
//////......
return {
  handlerResponse: {
    response: "ACK"
  },
  action: {
    state: stateMachine.next,
    ctx: {
      funcionarioId: data.id //Preenchendo o contexto
    },
    logs: {
      benefitAPI: getLogsFromResponse(res)
    }
  }
};
```

Para a próxima execução, no arquivo `request_add_plan.handler.ts` vamos precisar saber qual o ID do funcionario, como ele ja foi preenchido no step anterior, vamos apenas recuperar ele:

```ts theme={null}
const funcionarioID = action.ctx.funcionarioId;
ctx.logger.info('Adicionando o beneficio');
const res = await ctx.benefitsAPI.post<BenefitResponse>(`/customer/${funcionarioID}/plan`, );

const { status, data } = res;
///TRATAMENTO DE ERRROS 

//RETORNA A RESPOSTA
return {
  handlerResponse: {
    response: "ACK"
  },
  action: {
    state: stateMachine.next,
    ctx: {},
    logs: {
      benefitAPI: getLogsFromResponse(res)
    }
  }
};
```

### Testando

O projeto do benSDK ja contem uma ferramenta de desenvolvedor integrada. Para executar basta rodar o comando `npm start`.

Na seção State machine, selecione GRANT e em seguida clique em run.
