("carregando");
const [token, setToken] = useState("");
useEffect(() => {
http.get("/erp/credenciais").then(({ data }) => setEstado(data.state)).catch(() => setEstado("indisponível"));
}, [http]);
async function salvar(event: React.FormEvent) {
event.preventDefault();
const { data } = await http.put("/erp/credenciais", { token });
setToken("");
setEstado(data.state);
}
return (
);
}
```
Registre rotas e menu:
```tsx title="frontend/src/app/routes.tsx"
import { defineOonRoutes } from "@oondemand/oon-core-front/routing";
import { HomePage } from "../pages/HomePage";
export const routes = defineOonRoutes([
{ path: "/", component: HomePage },
{ path: "/despesas", lazy: () => import("../pages/DespesasPage"), permissions: ["despesas.read"] },
{ path: "/lancamentos-erp", lazy: () => import("../pages/LancamentosErpPage"), permissions: ["despesas.read"] },
{ path: "/erp/credencial", lazy: () => import("../pages/CredencialErpPage"), permissions: ["erp.credenciais"] },
]);
```
```ts title="frontend/src/app/navigation.ts"
import { defineOonNavigation } from "@oondemand/oon-core-front/routing";
export const navigation = defineOonNavigation([
{ label: "Início", href: "/", order: 10 },
{ label: "Despesas", href: "/despesas", section: "Financeiro", order: 20, permissions: ["despesas.read"] },
{ label: "Lançamentos no ERP", href: "/lancamentos-erp", section: "Financeiro", order: 30, permissions: ["despesas.read"] },
{ label: "Credencial do ERP", href: "/erp/credencial", section: "Configurações", order: 90, permissions: ["erp.credenciais"] },
]);
```
**Aprovar**, **Recusar** e **Lançar no ERP** são `apiAction`s que chamam as rotas do passo 5; aparecem só na etapa certa e para quem tem a permissão. Como em toda ação, esconder o botão é experiência: quem decide é o backend.
## 7. Rode localmente
Em `backend/.env`, além do que o scaffold já traz:
```bash title="backend/.env"
ERP_API_URL=http://127.0.0.1:4599
OON_SECRETS_KEY=<32 bytes em base64>
OON_SECRETS_INSTANCE=local:sample
OON_SECRETS_TENANT=local:tenant
APP_ENVIRONMENT=desenvolvimento
```
Use um ERP de teste ou um dublê local que responda `POST /contas-pagar` com `{ "codigo": "..." }`. Nunca use a chave local nem credenciais reais de Produção no `.env`. Na Plataforma, a chave de segredos é provisionada por ambiente na publicação e `ERP_API_URL` aponta para o ERP do ambiente.
## 8. Prove a jornada
| Cenário | Resultado esperado |
|---|---|
| Lançar uma despesa em **Rascunho** | `409` `DESPESA_NAO_LANCAVEL` |
| **Solicitante** envia `PATCH` de Em aprovação para **Aprovada** | `409` `OON_WORKFLOW_TRANSITION_INVALID` |
| **Solicitante** chama a rota de aprovação | `403` |
| **Financeiro** aprova uma despesa em aprovação | `200`; a despesa vai para **Aprovada** |
| `PATCH` de Rascunho direto para **Lançada no ERP** | `409` `OON_WORKFLOW_TRANSITION_INVALID` |
| Lançar uma despesa aprovada **sem credencial** cadastrada | `502` `ERP_CREDENCIAL_INDISPONIVEL`; o lançamento fica em **Erro** |
| ERP recusa os dados (`4xx`) | `502` `ERP_RECUSOU`; o lançamento fica em **Erro** e pode ser repetido |
| ERP grava a conta e responde `5xx` (ou não responde) | `502` `ERP_ERRO_REMOTO` / `ERP_SEM_RESPOSTA`; o lançamento fica em **Verificar no ERP** |
| Nova tentativa de um lançamento em **Verificar no ERP** | `409` `DESPESA_NAO_LANCAVEL`; nada é reenviado |
| Lançar com credencial e ERP no ar | `200` com o código do ERP; a despesa vai para **Lançada no ERP** |
| Dois cliques simultâneos em **Lançar no ERP** | Um lançamento no ERP; a outra requisição recebe `409` |
| Lançar de novo uma despesa já lançada | `409` `DESPESA_NAO_LANCAVEL` |
| Alterar o valor de uma despesa lançada | Recusado com a mensagem de campo bloqueado |
| Perfil **Solicitante** tenta lançar ou gravar em `/lancamentos-erp` | `403` |
Depois rode `npm run check` e `npm run build --prefix frontend` antes de publicar.
## Limites desta abordagem
- **Execução síncrona.** Não há runtime genérico de integração nem fila durável nesta release: o lançamento acontece na própria requisição, com timeout de 10 segundos. Para lotes grandes, lance por despesa ou avalie um processo assíncrono específico do App.
- **Reconciliação.** Lançamentos em **Verificar no ERP** — e em **Enviando**, se o processo cair no meio do envio — precisam ser conferidos no ERP pelo código de integração (o ID da despesa). Se a conta existe, registre o código; se não existe, volte o lançamento para **Erro** para permitir a nova tentativa. Faça isso com um procedimento autorizado (por exemplo, uma rota própria com permissão de administração); nunca reenvie às cegas.
- **Um ERP por vez.** O mesmo desenho serve para outros processos: cada novo processo vira uma nova model, uma nova esteira e uma nova rota ao lado do ERP, até que o que antes era customização viva nos Apps.
## Próximos passos
```cards
[
{ "title": "Segredos (core.secrets)", "href": "/docs/capabilities/segredos", "description": "Contrato, criptografia, provisionamento e recadastro." },
{ "title": "Rotas privadas", "href": "/docs/backend/rotas-privadas", "description": "Permissões, auditoria, sensitiveBody e integração externa." },
{ "title": "Central de compras", "href": "/docs/tutoriais/central-de-compras", "description": "Outro tutorial completo com esteira de aprovação." },
{ "title": "Do commit à Produção", "href": "/docs/tutoriais/do-commit-a-producao", "description": "Publique o App em Dev, Homologação e Produção." }
]
```
Fonte: https://sdk.oonapps.com/docs/tutoriais/app-ao-lado-do-erp