Ir para o conteúdo

Configuração

Toda a configuração do Extrator é feita através de variáveis de ambiente em um arquivo .env na raiz do projeto.


Início rápido

O Extrator é distribuído pelo CTC como um arquivo .zip. Após recebê-lo:

# 1. Extrair o pacote
unzip ctc-extrator.zip
cd ctc-extrator

# 2. Copiar o template de configuração
cp .env.example .env

# 3. Preencher com as credenciais e parâmetros do seu ambiente
nano .env

# 4. Iniciar
docker compose up --build

Estrutura das variáveis

O .env é dividido em dois grupos:

  • Variáveis compartilhadas — sem prefixo. Valem para todos os domínios (autenticação OAuth2, URL da API, conexão Oracle padrão).
  • Variáveis por domínio — com prefixo. Configuram o agendamento, queries e, opcionalmente, uma conexão Oracle própria para aquele domínio.

Prefixos por domínio

Domínio Prefixo
Produtividade PROD
Plantio PLANT
CTT CTT
Ordem de Serviço ORDEM
Pragas PRAGAS
Clima CLIMA

Variáveis compartilhadas

Logging

Variável Obrigatório Padrão Descrição
LOG_LEVEL ❌ info Nível de log: debug, info, warn, error

Autenticação OAuth2

Credenciais fornecidas pelo CTC para autenticar no Azure Entra ID.

Variável Obrigatório Descrição
AUTH_URL ✅ URL completa do endpoint de token — ex: https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
CLIENT_ID ✅ Client ID da aplicação registrada
CLIENT_SECRET ✅ Client Secret da aplicação

API de destino

Variável Obrigatório Descrição
API_BASE_URL ✅ URL base da API CTC — ex: https://api.ctc.com.br/v1/integrations/{partnerId}

Conexão Oracle padrão

Usada por todos os domínios que não definem conexão própria (ver Cenários de banco Oracle).

Variável Obrigatório Descrição
SRC_TYPE ✅ Tipo da fonte de dados. Use oracle
SRC_CONNECTION_STR ✅ String de conexão no formato host:port/service_name
SRC_USER ✅ Usuário Oracle
SRC_PASSWORD ✅ Senha do usuário Oracle

Variáveis por domínio

Cada domínio usa o padrão {PREFIXO}_VARIAVEL. As variáveis abaixo se repetem para cada domínio ativo — substitua {PREFIXO} pelo prefixo correspondente.

Agendamento e extração

Variável Obrigatório Descrição
{PREFIXO}_CRON ✅ Expressão cron (5 campos) para agendamento
{PREFIXO}_SRC_QUERY_LIMIT ❌ Registros por batch (padrão: 100)
{PREFIXO}_QUERY_CTRL_MIN_DEFAULT ✅ Data de início da extração — usada apenas na primeira execução. Defina uma data anterior ao menor registro existente no banco
{PREFIXO}_LOG_QUERIES ❌ Loga as queries executadas (true/false)

Queries

Variável Obrigatório Descrição
{PREFIXO}_QUERY_CTRL_MAX ✅ Query que retorna o timestamp máximo disponível no banco
{PREFIXO}_QUERY_TEMPLATE ✅ Query principal de extração com suporte a paginação

Veja Queries e Templates SQL para detalhes e exemplos.

Conexão Oracle por domínio (opcional)

Quando um domínio usa um banco diferente do padrão, defina as variáveis abaixo apenas para esse domínio. As demais continuam usando as variáveis compartilhadas.

Variável Descrição
{PREFIXO}_SRC_TYPE Substitui SRC_TYPE para este domínio
{PREFIXO}_SRC_CONNECTION_STR Substitui SRC_CONNECTION_STR para este domínio
{PREFIXO}_SRC_USER Substitui SRC_USER para este domínio
{PREFIXO}_SRC_PASSWORD Substitui SRC_PASSWORD para este domínio

Cenários de banco Oracle

Cenário A — Banco único (todos os domínios no mesmo banco)

Defina apenas as variáveis compartilhadas. Todos os domínios usarão a mesma conexão automaticamente.

SRC_TYPE="oracle"
SRC_CONNECTION_STR="host:1521/service_name"
SRC_USER="usuario"
SRC_PASSWORD="senha"

Cenário B — Bancos separados por domínio

Mantenha as variáveis compartilhadas como fallback e defina as variáveis específicas apenas para os domínios que usam banco diferente.

# Banco compartilhado (fallback para todos os domínios)
SRC_TYPE="oracle"
SRC_CONNECTION_STR="host-principal:1521/DB_PRINCIPAL"
SRC_USER="usuario"
SRC_PASSWORD="senha"

# Produtividade usa banco próprio
PROD_SRC_CONNECTION_STR="host-prod:1521/DB_PROD"
PROD_SRC_USER="usuario_prod"
PROD_SRC_PASSWORD="senha_prod"

# Plantio usa outro banco
PLANT_SRC_CONNECTION_STR="host-plant:1521/DB_PLANT"
PLANT_SRC_USER="usuario_plant"
PLANT_SRC_PASSWORD="senha_plant"

Defina apenas as variáveis dos domínios que realmente usam banco diferente. Os demais herdam automaticamente as variáveis compartilhadas.


Exemplo completo de .env

LOG_LEVEL=info

# OAuth2
AUTH_URL=https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
CLIENT_ID=seu-client-id
CLIENT_SECRET=seu-client-secret

# API CTC
API_BASE_URL=https://api.ctc.com.br/v1/integrations/{partnerId}

# Oracle compartilhado
SRC_TYPE=oracle
SRC_CONNECTION_STR=meu-oracle.empresa.com.br:1521/PROD
SRC_USER=ctc_extrator
SRC_PASSWORD=senha_segura

# Produtividade
PROD_CRON=0 5 * * *
PROD_SRC_QUERY_LIMIT=1000
PROD_QUERY_CTRL_MIN_DEFAULT=2020-03-01
PROD_LOG_QUERIES=true
PROD_QUERY_CTRL_MAX=SELECT MAX(DT_DATA_REALIZADO) AS CTRL_MAX FROM VW_CTC_PRODUCAO WHERE DT_DATA_REALIZADO > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS')
PROD_QUERY_TEMPLATE=SELECT * FROM ( SELECT inner_query.*, ROWNUM rn FROM ( SELECT * FROM VW_CTC_PRODUCAO WHERE DT_DATA_REALIZADO > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS') AND DT_DATA_REALIZADO <= TO_DATE('{{ctrl_max}}', 'YYYY-MM-DD HH24:MI:SS') ORDER BY DT_DATA_REALIZADO ASC ) inner_query WHERE ROWNUM <= {{offset}} + {{limit}} ) WHERE rn > {{offset}}

# Plantio
PLANT_CRON=0 5 * * *
PLANT_SRC_QUERY_LIMIT=1000
PLANT_QUERY_CTRL_MIN_DEFAULT=2024-01-01
PLANT_LOG_QUERIES=true
PLANT_QUERY_CTRL_MAX=SELECT MAX(DT_DATA_PLANTIO) AS CTRL_MAX FROM VW_CTC_PLANTIO WHERE DT_DATA_PLANTIO > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS')
PLANT_QUERY_TEMPLATE=SELECT * FROM ( SELECT inner_query.*, ROWNUM rn FROM ( SELECT * FROM VW_CTC_PLANTIO WHERE DT_DATA_PLANTIO > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS') AND DT_DATA_PLANTIO <= TO_DATE('{{ctrl_max}}', 'YYYY-MM-DD HH24:MI:SS') ORDER BY DT_DATA_PLANTIO ASC ) inner_query WHERE ROWNUM <= {{offset}} + {{limit}} ) WHERE rn > {{offset}}

# CTT
CTT_CRON=0 5 * * *
CTT_SRC_QUERY_LIMIT=1000
CTT_QUERY_CTRL_MIN_DEFAULT=2020-01-01
CTT_LOG_QUERIES=true
CTT_QUERY_CTRL_MAX=SELECT MAX(DT_DATA_COLHEITA) AS CTRL_MAX FROM VW_CTC_CTT WHERE DT_DATA_COLHEITA > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS')
CTT_QUERY_TEMPLATE=SELECT * FROM ( SELECT inner_query.*, ROWNUM rn FROM ( SELECT * FROM VW_CTC_CTT WHERE DT_DATA_COLHEITA > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS') AND DT_DATA_COLHEITA <= TO_DATE('{{ctrl_max}}', 'YYYY-MM-DD HH24:MI:SS') ORDER BY DT_DATA_COLHEITA ASC ) inner_query WHERE ROWNUM <= {{offset}} + {{limit}} ) WHERE rn > {{offset}}

# Ordem de Serviço
ORDEM_CRON=0 5 * * *
ORDEM_SRC_QUERY_LIMIT=1000
ORDEM_QUERY_CTRL_MIN_DEFAULT=2020-01-01
ORDEM_LOG_QUERIES=true
ORDEM_QUERY_CTRL_MAX=SELECT MAX(DT_DATA_ORDEM) AS CTRL_MAX FROM VW_CTC_ORDEM WHERE DT_DATA_ORDEM > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS')
ORDEM_QUERY_TEMPLATE=SELECT * FROM ( SELECT inner_query.*, ROWNUM rn FROM ( SELECT * FROM VW_CTC_ORDEM WHERE DT_DATA_ORDEM > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS') AND DT_DATA_ORDEM <= TO_DATE('{{ctrl_max}}', 'YYYY-MM-DD HH24:MI:SS') ORDER BY DT_DATA_ORDEM ASC ) inner_query WHERE ROWNUM <= {{offset}} + {{limit}} ) WHERE rn > {{offset}}

# Pragas
PRAGAS_CRON=0 5 * * *
PRAGAS_SRC_QUERY_LIMIT=1000
PRAGAS_QUERY_CTRL_MIN_DEFAULT=2020-01-01
PRAGAS_LOG_QUERIES=true
PRAGAS_QUERY_CTRL_MAX=SELECT MAX(DT_DATA_PRAGA) AS CTRL_MAX FROM VW_CTC_PRAGAS WHERE DT_DATA_PRAGA > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS')
PRAGAS_QUERY_TEMPLATE=SELECT * FROM ( SELECT inner_query.*, ROWNUM rn FROM ( SELECT * FROM VW_CTC_PRAGAS WHERE DT_DATA_PRAGA > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS') AND DT_DATA_PRAGA <= TO_DATE('{{ctrl_max}}', 'YYYY-MM-DD HH24:MI:SS') ORDER BY DT_DATA_PRAGA ASC ) inner_query WHERE ROWNUM <= {{offset}} + {{limit}} ) WHERE rn > {{offset}}

# Clima
CLIMA_CRON=0 5 * * *
CLIMA_SRC_QUERY_LIMIT=1000
CLIMA_QUERY_CTRL_MIN_DEFAULT=2020-01-01
CLIMA_LOG_QUERIES=true
CLIMA_QUERY_CTRL_MAX=SELECT MAX(DT_DATA_CLIMA) AS CTRL_MAX FROM VW_CTC_CLIMA WHERE DT_DATA_CLIMA > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS')
CLIMA_QUERY_TEMPLATE=SELECT * FROM ( SELECT inner_query.*, ROWNUM rn FROM ( SELECT * FROM VW_CTC_CLIMA WHERE DT_DATA_CLIMA > TO_DATE('{{ctrl_min}}', 'YYYY-MM-DD HH24:MI:SS') AND DT_DATA_CLIMA <= TO_DATE('{{ctrl_max}}', 'YYYY-MM-DD HH24:MI:SS') ORDER BY DT_DATA_CLIMA ASC ) inner_query WHERE ROWNUM <= {{offset}} + {{limit}} ) WHERE rn > {{offset}}

Exemplos de cron:

PROD_CRON=0 */6 * * *    # A cada 6 horas
PROD_CRON=0 2 * * *      # Diariamente às 02:00
PROD_CRON=*/30 * * * *   # A cada 30 minutos
PROD_CRON=0 8 * * 1-5    # Segunda a sexta às 08:00

Segurança

O arquivo .env contém credenciais sensíveis e não deve ser versionado.

# Certifique-se que .env está no .gitignore
echo ".env" >> .gitignore

# Versione apenas o template
git add .env.example

Verificando a inicialização

Após docker compose up, os logs devem mostrar a inicialização de cada domínio ativo e, para os demais, o aviso de que foram pulados por falta de agendamento. O campo time vem formatado em YYYY-MM-DD HH:mm:ss, já no timezone UTC-3 (não é unix timestamp em milissegundos):

{"level":"info","time":"2026-09-25 11:32:23","msg":"Setting up data extractor configuration..."}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","libDir":"/opt/oracle/instantclient","msg":"Initializing Oracle Instant Client"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Oracle Instant Client initialized successfully"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Setting up db connection..."}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Connected to db"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Testing db connection..."}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Db connection validated"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Closing db connection..."}
{"level":"info","time":"2026-09-25 11:32:23","domain":"productivity","msg":"Db connection closed"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"planting","msg":"Domain disabled (no CRON configured), skipping"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"ctt","msg":"Domain disabled (no CRON configured), skipping"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"ordem","msg":"Domain disabled (no CRON configured), skipping"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"pragas","msg":"Domain disabled (no CRON configured), skipping"}
{"level":"info","time":"2026-09-25 11:32:23","domain":"clima","msg":"Domain disabled (no CRON configured), skipping"}

Domain disabled (no CRON configured), skipping é esperado para todo domínio sem {PREFIXO}_CRON definido no .env — não é erro, é só o Extrator pulando um domínio que não foi configurado para rodar.

Se o Oracle Instant Client não for encontrado, o Extrator faz fallback automático para Thin mode:

{"level":"warn","time":"2026-08-31 11:53:20","msg":"Failed to initialize Oracle Instant Client, falling back to Thin mode"}

Solução de problemas

Invalid environment variables — alguma variável obrigatória não foi definida ou tem formato inválido. O erro lista exatamente quais variáveis estão faltando ou erradas em fieldErrors:

Invalid environment variables
{
  "fieldErrors": {
    "AUTH_URL": ["Invalid input: expected string, received undefined"],
    "CLIENT_ID": ["Invalid input: expected string, received undefined"],
    "CLIENT_SECRET": ["Invalid input: expected string, received undefined"],
    "API_BASE_URL": ["Invalid input: expected string, received undefined"]
  }
}

Cada chave é o nome exato da variável que falta preencher no .env — compare com o .env.example.

Failed to connect to Oracle Database — verifique credenciais, string de conexão e conectividade de rede entre o container e o banco Oracle.

DPI-1047: Cannot locate a 64-bit Oracle Client library — o Oracle Instant Client não foi encontrado dentro do container (arquivos .so ausentes ou download falhou no build). Verifique:

docker exec app find /opt/oracle -name "*.so*" | head -20

Se vier vazio, refaça o build sem cache: docker compose build --no-cache.

Authentication failed — teste as credenciais OAuth2 manualmente:

curl -X POST "$AUTH_URL" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=$CLIENT_ID&client_secret=$CLIENT_SECRET&scope=.default"

NJS-138: connections not supported in Thin mode — o banco Oracle é legado e requer Thick mode. Verifique se o container está usando a imagem correta (com Instant Client incluído).

ORA-01861: literal does not match format string — a coluna de data usada em {{ctrl_min}}/{{ctrl_max}} provavelmente não é DATE/TIMESTAMP no Oracle (ou tem valores que não batem com o formato esperado). Veja Tipo das colunas de data para diagnosticar e corrigir.

Mudei {PREFIXO}_QUERY_CTRL_MIN_DEFAULT e não fez efeito — essa variável só vale na primeira execução de cada domínio; depois disso o ponto de partida vem do estado salvo localmente, não do .env. Veja Consultar e resetar o ponto de partida para forçar um novo ponto de partida.

Dados repetidos sendo enviados — a query de {PREFIXO}_QUERY_CTRL_MAX provavelmente não retorna hora/minuto/segundo, fazendo o Extrator reprocessar o mesmo dia inteiro a cada execução. Veja Precisão de timestamps.

Extrator não está enviando nada — pode ser: não há dados novos desde a última execução, o CRON ainda não chegou na hora programada, ou as credenciais OAuth2 estão erradas. Rode o checkup para isolar a causa sem precisar vasculhar logs.