Files
dtf-system/docs/historico/API.md
Cauê Faleiros ca698434a2 chore: remove the abandoned prototypes and archive what described them
portal/, kanban/ and agente/ were 2,034 lines implementing the original
Tiny-first model: token upload links, a second SQLite Kanban, a factory agent.
Nothing imported or started any of it, and several endpoints took the acting
user from the request body with no authentication at all. Their real cost was
that a reader arriving at this repository found two Kanbans and two portals and
had to work out which one was real. The root schema.sql and .env.exemplo went
with them: both code paths load local/schema.sql, and having .env.exemplo beside
.env.example differing by one letter was a trap rather than a convenience.

The documents describing that model are archived rather than deleted. They
record decisions and reasoning the current documents do not repeat, so they are
worth keeping as background, with a header saying plainly that they are not
instructions.

README.md keeps its business case — the capacity figures and the cost argument
are still the reason this project exists — but now states where the prototype
documentation begins and that the code it describes is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 16:34:20 -03:00

9.5 KiB

API — contratos

Historical prototype contracts only. The active local API is documented at localhost:8000/docs and in LOCAL_SETUP.md. CONTEXT.md overrides the legacy endpoints and workflows below.

Todos os endpoints do módulo DTF, com requisição e resposta. Base: portal/main.py e kanban/main.py.


Autenticação

Quem chama Como se autentica
Tiny (webhook) header x-token com WEBHOOK_TOKEN
Cliente token no path da URL — sem login
Agente header x-token com AGENTE_TOKEN
Kanban (aba do PCP) sessão do PCP — rede interna, sem porta para fora

PORTAL · na nuvem

POST /webhook/tiny

Chamado quando nasce um pedido de DTF. É o gatilho de tudo.

{ "numero": "48213", "cliente": "Estamparia Vitória",
  "telefone": "5516999998888", "metros": 2.4, "cliente_novo": false }

200

{ "ok": true, "link": "https://arte.dropstaratacado.com.br/arte/a7f3k9..." }

Cria o pedido, gera token de 7 dias e enfileira o WhatsApp com o link. Se o número já existir, devolve {"ok":true,"ja_existia":true} sem duplicar.

Latência importa. O link só sai depois que o pedido existe no Tiny — se a integração VNDA → Tiny demorar, o cliente espera. Medir antes de subir.


GET /arte/{token}

Página de upload. HTML. O token já sabe o pedido — o cliente não digita nada.

GET /api/arte/{token}

{ "pedido": "48213", "cliente": "Estamparia Vitória", "metros": 2.4,
  "area_util_cm": 97, "largura_max_cm": 57,
  "limite_arquivo_m": 20, "max_gb": 5, "max_arquivos": 10 }

410 se o token expirou.

POST /api/arte/{token}/url

{ "nome": "estampa.png", "bytes": 84000000 }
{ "url": "https://r2.../48213/ab12_estampa.png?X-Amz-...",
  "chave": "48213/ab12_estampa.png", "expira_em": 3600 }

Upload direto para o storage. Arquivo de 5 GB passando pelo VPS derrubaria o processo. Em partes, para arquivos grandes.

POST /api/arte/{token}/pronto

{ "chave": "48213/ab12_estampa.png", "repeticoes": 20 }
{ "arte_id": 991, "status": "processando" }

Dispara o pré-flight em background. O cliente acompanha por polling em GET /api/arte/{token}/status.

GET /api/arte/{token}/status

Aprovada:

{ "status": "aprovada", "dpi_efetivo": 300, "qualidade_pct": 100,
  "metros_totais": 48.0, "minutos_maquina": 144,
  "partes": [{"ordem":1,"metros":20,"nome":"48213_p1.png"},
             {"ordem":2,"metros":20,"nome":"48213_p2.png"},
             {"ordem":3,"metros":8,"nome":"48213_p3_carimbado.png"}],
  "avisos": [], "previsao_saida": "2026-09-02T18:00:00-03:00" }

Recusada:

{ "status": "recusada",
  "motivo": "A resolução real da arte é de 72 DPI no tamanho que você comprou. Precisamos de pelo menos 150 DPI — o ideal é 300. Reenvie em maior resolução." }

O relógio do prazo só começa quando status = aprovada. Arquivo ruim enviado às 17h não faz as horas correrem contra a casa.


GET /api/artes · o agente busca

Header x-token. Devolve o que está aprovado, com vírus liberado e ainda não baixado.

[{ "arte_id": 991, "pedido": "48213", "cliente": "Estamparia Vitória",
   "metros": 48.0, "minutos_maquina": 144, "conferir_manual": false,
   "partes": [{"ordem":1,"metros":20,"nome":"48213_p1.png",
               "url":"https://r2.../...","sha256":"a3f9..."}] }]

POST /api/artes/{id}/baixada · confirma o download

POST /api/agente/heartbeat · a cada 5 min

Sem sinal por 15 minutos, alertar o TI. Serviço silencioso parado é pior que erro barulhento: ninguém percebe até o cliente cobrar.


GET /cliente/{token_cliente} · minhas artes

Link permanente do cliente, não do pedido. Lista as artes tratadas dos últimos 12 meses.

POST /cliente/{token_cliente}/reimprimir

{ "arte_id": 812, "metros": 20 }

Abre pedido novo no Tiny com a arte já pronta. Recompra sem atrito.


KANBAN · rede interna, aba do PCP

GET /api/quadro

{ "colunas": [{"id":"rec","nome":"Arte recebida"}, ...],
  "cards": {
    "fil": [{ "arte_id": 991, "pedido": "48213", "cliente": "Estamparia Vitória",
              "metros": 48.0, "minutos_maquina": 144, "partes": 3,
              "maquina": null, "desde": "2026-09-02T14:02:00",
              "parado_seg": 4320, "conferir_manual": false }]
  },
  "maquinas": [{ "n": 1, "ocupada": true, "pedido": "48190",
                 "metros": 9.0, "rodando_seg": 720 },
               { "n": 3, "ocupada": false }] }

maquinas é o que pinta o círculo vermelho. Com seis máquinas vendo o mesmo quadro, é o que evita dois operadores no mesmo arquivo.


POST /api/puxar

{ "maquina": 4, "usuario": "alexandre" }
{ "maquina": "Maq 4", "pedido": "48197", "metros": 18.0,
  "minutos": 60, "reserva_expira_em": 3 }

Um pedido por vez e reserva de 3 minutos — ajustes pedidos pela sala. Pega sempre o mais antigo da fila. Se não entrar em imp em 3 min, volta.

409 se a máquina já estiver ocupada. 404 se a fila estiver vazia.

POST /api/devolver

{ "arte_id": 991, "usuario": "thales", "motivo": "travou no meio" }

Volta ao topo da fila, não ao fim — o pedido já esperou uma vez.

POST /api/mover

{ "arte_id": 991, "para": "fin", "usuario": "poliana", "maquina": 4 }

Grava o movimento antes de qualquer integração externa. Se o Tiny estiver fora, o job fica na fila e tenta de novo em 1, 5 e 30 min — mas a medição de tempo já está salva.

Move o arquivo entre as pastas e enfileira marcador e WhatsApp quando a coluna pedir. Ao ir para apc, pergunta o motivo da prova de cor.

PATCH /api/card/{arte_id}

{ "minutos_maquina": 90 }

Ajuste da estimativa. O sistema sugere por metros/20*60; quem trata a arte corrige só quando foge do normal. É esse número que soma a fila contra a capacidade do dia.

GET /p/{pedido}

O QR do carimbo aponta para cá. Redireciona para o card. A revisão bipa e cai na tela do pedido em vez de procurar na lista.


Retrabalho

POST /api/retrabalho

{ "pedido_origem": "48184", "metros": 2.0, "causa": "impressao",
  "aberto_por": "mayana", "evidencia": "print-whatsapp.jpg" }
{ "id": 44, "alcada": "sala", "vez": 1, "conta_na_meta": true }

A Mayana abre e classifica — conhece o cliente e a reclamação. A causa fica travada. Quem discorda contesta, não altera.

POST /api/retrabalho/{id}/autorizar

{ "usuario": "thales" }

Thales ou Alexandre autorizam, porque o retrabalho da casa entra na meta deles. Eles confirmaram achar justo.

403 se a alçada exigir financeiro ou diretoria.

POST /api/retrabalho/{id}/contestar

{ "usuario": "alexandre", "texto": "não foi impressão, a arte veio em CMYK" }

Registra a discordância sem mudar a causa. Fica com quem pediu e quem decidiu.


Relatórios

GET /api/relatorio/etapas?dias=7

[{ "coluna": "tra", "nome": "Arte tratada", "movimentos": 84,
   "media_min": 94, "pior_min": 380 }]

Responde a pergunta que decide o terceiro turno: quanto do tempo é fila e quanto é trabalho. Se a arte demora 6h e a máquina imprime em 40 min, rodar 24h só faz a fila esperar de madrugada.

GET /api/relatorio/impressao?dias=7

[{ "maquina": "Maq 1", "pedidos": 42, "media_min": 34,
   "metros": 310.5, "m_por_hora": 20.4 }]

m_por_hora valida ou derruba os 20 m/h. Todo o cálculo de capacidade — 60.480 metros/mês, R$ 148 mil de ganho — depende desse número. Se for 14, a conta muda inteira.

GET /api/relatorio/aproveitamento?dias=30

{ "metros_faturados": 11605, "metros_de_filme": 12693,
  "aproveitamento_pct": 91.4, "valor_do_ponto_mes": 980,
  "fonte": "transferências para o depósito Sala DTF no Tiny" }

Não exige apontamento novo. O consumo já é registrado quando o insumo é transferido para o depósito de impressão — a Altus faz isso porque também revende insumo.

GET /api/relatorio/retrabalho?dias=30

{ "por_causa": [{ "causa": "impressao", "descricao": "Falha de impressão",
                  "responsavel": "Thales e Alexandre", "pedidos": 9,
                  "metros": 24.5, "pct": 1.1, "meta": 0.4, "bate": false }],
  "total_casa_pct": 2.9, "meta_casa_pct": 1.6 }

⚠ A meta ainda não está decidida. A proposta era 0,4% por causa; a sala respondeu que "meta geral seria melhor". META_POR_CAUSA está isolado no código para trocar sem mexer no resto.


Códigos de erro

Código Quando
400 payload inválido, coluna inexistente, mais de 10 arquivos
401 token do webhook ou do agente errado
403 alçada insuficiente para autorizar retrabalho
404 token não existe, card não encontrado, fila vazia
409 máquina já ocupada, pedido já reservado
410 token do link expirou
413 arquivo acima de 5 GB
429 rate limit — 20 req/min por token

O que testar antes de dar por pronto

  • Upload de arquivo de 5 GB sem derrubar o VPS
  • Dois operadores clicando puxar ao mesmo tempo — só um pega
  • Reserva expirando: puxar e não mover em 3 min devolve à fila
  • Tiny fora do ar: o movimento grava e o job fica na fila
  • Agente sem internet: para, e ao voltar baixa o acumulado
  • Arquivo baixado pela metade não aparece no kanban
  • Token expirado devolve 410 com mensagem clara ao cliente