# API Interna VC Digital

Base:
- `https://icms.vcnodigital.com`

Autenticação:
- `Authorization: Bearer <token da controladora>`
- token separado por controladora
- o token da controladora só enxerga a própria carteira
- não existe header extra obrigatório hoje

Começo recomendado para o time do `app.vcnodigital.com`:
- `GET /docs/vc-digital-consumo-app.md`
- usar este arquivo como guia rápido
- voltar para `GET /docs/vc-digital.md` quando precisar da referência completa

## Arquivos auxiliares publicados

- `GET /docs/vc-digital-consumo-app.md`
- `GET /docs/vc-digital-changelog.md`
- `GET /docs/vc-digital.md`
- `GET /docs/vc-digital-readiness.md`
- `GET /docs/vc-digital-readiness.json`
- `GET /docs/vc-digital-rollout-priority.md`
- `GET /docs/vc-digital-rollout-priority.json`
- `GET /docs/vc-digital-example-response.json`
- `GET /docs/vc-digital-due-options-example.json`
- `GET /docs/vc-digital-empty-response.json`
- `GET /docs/vc-digital-error-catalog.json`
- `GET /docs/vc-digital-mark-exported-example.json`
- `GET /docs/vc-digital-mark-delivered-example.json`
- `GET /docs/vc-digital-curl-examples.txt`
- `GET /docs/vc-digital-notificacao-imediata.md`
- `GET /docs/vc-digital-vencimentos-ajuste.md`
- `GET /docs/vc-digital-vencimentos-ajuste.json`
- `GET /docs/vc-digital-automacao-vencidas.md`
- `GET /docs/vc-digital-automacao-vencidas.json`
- `GET /docs/vc-digital-sefin-scrape-schema.md`
- `GET /docs/vc-digital-sefin-scrape-schema.json`

## Configuração esperada no VC Digital

```ini
fiscalApiEnabled = true
fiscalApiBaseUrl = https://icms.vcnodigital.com
fiscalApiKey = <token-da-controladora>
fiscalApiSubdomain = ""
fiscalApiImportPath = ""
```

```json
{
  "controladoraId": "<ID_DA_CONTROLADORA_NO_ICMS>",
  "markExportedPath": "/api/internal/fiscal-documents/mark-exported",
  "markDeliveredPath": "/api/internal/fiscal-documents/mark-delivered",
  "markExportedAfterImport": true
}
```

## Endpoints publicados

- `GET /api/internal/fiscal-documents`
- `GET /api/internal/fiscal-documents/upload-events`
- `GET /api/internal/fiscal-documents/payment-events`
- `GET /api/internal/fiscal-documents/snapshots`
- `GET /api/internal/fiscal-documents/due-options`
- `POST /api/internal/fiscal-documents/mark-exported`
- `POST /api/internal/fiscal-documents/mark-delivered`
- `POST /api/internal/fiscal-documents/force-republish`

## Consumo incremental

- o VC Digital nao precisa esperar o lote completo para comecar a consumir
- assim que existir o `dare` para um `numeroDocumento`, o endpoint ja pode devolver o item
- se existir so o DARE, o array `documents[]` vira apenas com `documentType = dare`
- quando `extratoMode = embedded_in_dare`, isso nao significa pendencia: o proprio DARE ja satisfaz a memoria fiscal e o `documents[]` continua so com `dare`
- se existir so o extrato e ainda nao houver `guia_pdf`, o item continua no hub interno e o `documents[]` nao libera `extrato_dare`
- a partir de `16/03/2026`, o `ingest.php` rejeita com `422` qualquer payload que tente subir `extrato_*` sem `guia_pdf` real valido para a mesma `guia + parcela`
- o agrupamento visual pode exibir o mesmo `numeroDocumento` mais de uma vez, porque o número sozinho não é chave única suficiente
- a chave operacional correta é:
  - `guideId`, quando disponível
  - ou `companyId + numeroDocumento + parcela + documentType`
- o mesmo `numeroDocumento` pode existir em empresas diferentes e também em parcelas diferentes
- o filtro recomendado para polling e `pending_only=1`
- o endpoint principal agora tambem devolve `workflowStatus` e `delivery.status` para a VPS da Peramix auditar importacao e envio ao cliente

## Notificacao imediata para vitrine do app.vcnodigital.com

- o endpoint `GET /api/internal/fiscal-documents/upload-events` entrega cursor incremental autenticado por controladora
- assim que chegar evento com `readyForVitrine = true`, o app deve chamar imediatamente `GET /api/internal/fiscal-documents?pending_only=1`
- `documentsHint` indica o que ja pode entrar na vitrine naquele momento:
  - `dare` sempre que `guia_pdf` estiver disponivel
  - `extrato_dare` somente quando o extrato separado existir ou quando o pareamento indicar extrato nao exigivel
- o feed incremental também pode carregar `sefin_year_snapshot` e `sefin_pending_month_snapshot` como artefatos de auditoria/apoio visual
- o objetivo desse endpoint e substituir esperas longas por consumo orientado a evento, sem quebrar o polling atual
- recomendacao operacional: manter polling leve (ex.: 15-30s) no `upload-events` e importar por `pending_only=1` apenas quando houver evento novo

## Pagamento inferido e historico no app.vcnodigital.com

- o `sefin_browser.py` marca baixa no ICMS por `POST /api/internal/fiscal-documents/mark-paid`
- o motor local roda duas janelas de revisão: `D+1` útil para PIX e `D+5` útil para código de barras/boleto
- a revisão de pagamento deve ser tratada como scan de baixa: ela remove da vitrine o que foi pago, sem exigir novo download de guia
- essa chamada grava `guides.is_paid_inferred = 1`, `status_current = PAGA_INFERIDA` e um evento `SEFIN_PAYMENT_INFERRED`
- o consumidor externo nao deve chamar `mark-paid`; esse endpoint e de uso do coletor/operacao ICMS
- o app deve consumir `GET /api/internal/fiscal-documents/payment-events?after_id=<cursor>`
- ao receber evento `SEFIN_PAYMENT_INFERRED`, o app deve mover para historico os documentos pendentes equivalentes com motivo `paid`
- a equivalencia correta e `guideId` ou `companyId + numeroDocumento + parcela`; se ainda nao houver `guideId/companyId` local, usar `providerCompanyCnpj + numeroDocumento + parcela`
- nunca usar apenas `numeroDocumento` para baixa, porque o mesmo numero pode existir em empresas diferentes e parcelas diferentes
- como fallback de compatibilidade, o app ainda pode rodar `GET /api/internal/fiscal-documents?pending_only=1`; guias pagas inferidas nao saem mais nessa vitrine

## Fluxo obrigatorio para guia atualizada

- quando o VC Digital detectar guia vencida, `dueDate` suspeito ou necessidade de reimportar o mesmo `numeroDocumento + parcela`, o fluxo correto e consultar primeiro `GET /api/internal/fiscal-documents/due-options`
- se a data desejada existir em `availableDueDates[]`, criar o job `POST /api/internal/orchestrator/jobs`
- para remissao pontual, usar `commandType = fetch_numero_documento` com `numeroDocumento + parcela + targetDueDate`
- o desktop local usa a base autoritativa do proprio coletor e reconstrui o catalogo local se necessario antes do reenvio
- o acompanhamento do status pode ser feito em `GET /api/internal/orchestrator/jobs?numeroDocumento=...&parcela=...`
- a consulta em `GET /api/internal/fiscal-documents` deve acontecer depois do job local terminar
- se a mesma guia reaparecer com `artifactId` novo, o VC Digital deve substituir o arquivo anterior
- para essa substituição, usar `guideId` ou `companyId + numeroDocumento + parcela + documentType`

## Pedido direto sem ressincronizacao previa

- para uma guia especifica, o app pode disparar pedido direto mesmo que a vitrine ainda nao tenha sido ressincronizada
- esse fluxo depende de escopo fiscal suficiente: `cnpj` ou `companyId`, mais `numeroDocumento`
- `parcela` deve ser enviada sempre que possivel
- `numeroDocumento` sem `parcela` nao deve ser tratado como identidade final da obrigação
- se for reemissao, `targetDueDate` precisa pertencer a `availableDueDates[]`
- para pedido massivo por mes, ainda nao existe contrato publico unico no app; hoje isso continua no fluxo de descoberta/sincronizacao por empresa/ano e automacao do desktop

## Grade SEFIN publicada como contrato

- os campos estruturados da grade e seus derivados estao documentados em:
  - `GET /docs/vc-digital-sefin-scrape-schema.md`
  - `GET /docs/vc-digital-sefin-scrape-schema.json`
- esse contrato cobre:
  - cabeçalho da empresa na SEFIN
  - colunas da grade de débitos
  - links de DARE e extrato
  - campos derivados pelo desktop
  - mapeamento para os campos publicos do ICMS

## Automacao D+1 de guias vencidas

- o desktop local roda um monitor `D+1` para empresas com guia que venceu ontem e continuam pendentes
- esse monitor gera uma fila automática de recálculo e pode abrir `lote expresso` sem intervenção manual
- a nova guia recalculada sobe para o ICMS SaaS e passa a ser o artefato vigente da vitrine
- a API do ICMS já entrega o artefato mais novo por guia; por isso, a estratégia correta é substituir pelo mais novo, não apagar histórico bruto
- quando o VC Digital já tiver importado o documento antigo e precisar forçar refresh operacional, usar `POST /api/internal/fiscal-documents/force-republish`
- para consumo, considerar sempre:
  - `dueDateReal`
  - `vencimentoEmitido`
  - `recalculada`
  - `artifactId` mais novo

## Fila prioritaria WNS para primeira ingestao

1. `ERA COMERCIO DE ALIMENTOS LTDA` - 22 guias
2. `DROGA MIL COMERCIO DE MEDICAMENTOS LTDA` - 13 guias
3. `F DOS REIS SANTOS` - 8 guias
4. `BARAO AGROPECUARIA E MATERIAIS PARA CONSTRUCAO LTDA` - 5 guias
5. `PIANNA CLOSET LTDA` - 5 guias

Critério usado:
- mais DAREs publicados
- mais artefatos úteis já disponíveis no site
- melhor massa para o VC Digital começar a consumir imediatamente

## Query params aceitos em `GET /api/internal/fiscal-documents`

- `controladora_id`
- `cnpj`
- `numeroDocumento`
- `parcela`
- `pending_only`
- `pending_delivery_only`
- `delivery_status`
- `includeAuditArtifacts`

Semantica:
- `pending_only=1` retorna apenas itens cujo artefato vigente ainda nao foi importado pelo app VC Digital
- `pending_delivery_only=1` retorna apenas itens importados mas ainda sem confirmacao final de envio ao cliente
- `delivery_status` aceita `PENDENTE_IMPORTACAO`, `IMPORTADO_PENDENTE_ENVIO`, `PENDENTE_REENVIO`, `FALHA_ENTREGA` e `ENVIADO_CLIENTE`
- `includeAuditArtifacts=1` pode incluir `extrato_screenshot` e snapshots SEFIN (`sefin_year_snapshot`, `sefin_pending_month_snapshot`) quando existirem para a mesma guia

## Query params aceitos em `GET /api/internal/fiscal-documents/due-options`

- `controladora_id`
- `cnpj`
- `numeroDocumento`
- `parcela`
- `only_with_available_dates`
- `limit`

Campos de retorno importantes:
- `vencimentoOriginal`
- `vencimentoEmitido`
- `dueDateCurrent`
- `availableDueDates[]`
- `nextSuggestedDueDate`
- `pairing.guiaDisponivel`
- `pairing.extratoDisponivel`
- `flags.prontaParaVcDigital`

Uso recomendado:
- consultar as datas reais antes de pedir reemissão
- decidir `targetDueDate` somente dentro da lista retornada
- evitar abrir job quando a guia já estiver pronta no ICMS SaaS

## Query params aceitos em `GET /api/internal/fiscal-documents/upload-events`

- `controladora_id`
- `after_id`
- `bootstrap`
- `history`
- `ready_only`
- `limit`

Semantica:
- `bootstrap=1` devolve somente `cursor` sem eventos
- `after_id` aplica leitura incremental (somente eventos acima do cursor informado)
- `history=1` devolve historico recente ordenado pelo artefato mais novo
- `ready_only=1` reduz os eventos para guias ja publicaveis na vitrine (`readyForVitrine = true`)
- cada evento retorna `pairing`, `documentsHint` e `recommendedAction` para orientar importacao imediata

## Payload entregue pela API

### No nível da guia
- `guideId`
- `numeroDocumento`
- `parcela`
- `dueDate`
- `dueDateReal`
- `displayDueDate`
- `displayDueDateLabel`
- `showVencimentoOriginal`
- `dueDatePresentation.primary`
- `dueDatePresentation.secondary`
- `dueDateSource`
- `vencimentoOriginal`
- `vencimentoEmitido`
- `availableDueDates[]`
- `referencia`
- `referencePeriod`
- `referenceMonth`
- `referenceYear`
- `valorLancado`
- `valorAtualizado`
- `valorTotal`
- `recalculada`
- `extratoMode`
- `extratoExpected`
- `dareSelfContained`
- `extratoReusable`
- `guideContentFingerprint`
- `extratoFingerprint`
- `extratoRefreshReason`
- `externalIds.dare`
- `externalIds.extrato`
- `workflowStatus`
- `delivery.status`
- `delivery.importedAt`
- `delivery.updatedAt`
- `delivery.channel`
- `delivery.recipient`

### No nível da empresa
- `company.id`
- `company.cnpj`
- `company.inscricaoEstadual`
- `company.razaoSocial`
- `company.nomeFantasia`

### No nível da controladora
- `controladora.id`
- `controladora.nome`
- `controladora.companyId`
- `controladora.apiBaseUrl`

### No nível do documento
- `documentType`
- `artifactId`
- `externalId`
- `title`
- `dueDate`
- `dueDateReal`
- `displayDueDate`
- `displayDueDateLabel`
- `showVencimentoOriginal`
- `dueDatePresentation.primary`
- `dueDatePresentation.secondary`
- `dueDateSource`
- `vencimentoOriginal`
- `vencimentoEmitido`
- `referencia`
- `referencePeriod`
- `referenceMonth`
- `referenceYear`
- `valorLancado`
- `valorAtualizado`
- `valorTotal`
- `numeroDocumento`
- `cnpj`
- `razaoSocial`
- `inscricaoEstadual`
- `recalculada`
- `downloadUrl`
- `viewerUrl`
- `fileName`
- `mimeType`
- `transferStatus`
- `transferredAt`
- `externalDocUrl`
- `workflowStatus`
- `importedToVcDigital`
- `importedAt`
- `extratoMode`
- `extratoExpected`
- `dareSelfContained`
- `extratoReusable`
- `guideContentFingerprint`
- `extratoFingerprint`
- `extratoRefreshReason`
- `delivery.status`
- `delivery.updatedAt`
- `delivery.channel`
- `delivery.recipient`
- `delivery.messageId`
- `delivery.ticketId`
- `delivery.externalMessageUrl`
- `delivery.externalDocUrl`
- `notes`
- `documents[]` pode vir parcialmente preenchido, com apenas os documentos já disponíveis no momento
- `auditArtifacts[]` aparece apenas quando o consumidor pedir explicitamente artefatos de auditoria
- o consumidor deve usar `documents[]` como fonte de documentos finais; os blocos top-level `dare` e `extrato` sao metadados de suporte, e `includeAuditArtifacts=1` pode incluir screenshot de extrato e snapshots SEFIN anual/mensal

### No bloco `pairing`
- `pairing.status`
- `pairing.guiaDisponivel`
- `pairing.extratoDisponivel`
- `pairing.extratoEsperado`
- `pairing.extratoNaoExigivel`
- `pairing.extratoEmbutidoNoDare`
- `pairing.extratoReutilizavel`
- `pairing.extratoLiberadoParaConsumo`
- `pairing.guiaCompletaParaCliente`

## Semântica de vencimento para o VC Digital

- `vencimentoOriginal`: data capturada na lista original da SEFIN antes de qualquer recálculo
- `vencimentoEmitido`: data efetivamente selecionada no momento da emissão da guia
- `availableDueDates[]`: lista das datas realmente disponibilizadas pela SEFIN para a guia vencida
- `recalculada = true`: ocorre quando `vencimentoEmitido` existe e difere de `vencimentoOriginal`
- `dueDate` e `dueDateReal`: são a data operacional que o VC Digital deve consumir

Regra operacional publicada:
- se a guia nao foi recalculada, `dueDateReal = vencimentoOriginal`
- se a guia foi recalculada, `dueDateReal = vencimentoEmitido`
- o VC Digital deve tratar `dueDate` como alias de `dueDateReal`
- `displayDueDate` e a mesma data operacional que deve aparecer como vencimento principal no card
- `vencimentoOriginal` vira apenas historico visual quando `showVencimentoOriginal = true`
- se `availableDueDates[]` contiver o proprio `vencimentoOriginal`, essa data deve ser preferida pelo desktop local
- se a guia vier de `02/2026` e `31/03/2026` existir em `availableDueDates[]`, o desktop local publica `vencimentoEmitido = 2026-03-31`

## Semântica de extrato para o VC Digital

- `extratoMode = separate`: a guia tem ou deve ter extrato separado
- `extratoMode = embedded_in_dare`: nao esperar `extrato_dare`; a memoria fiscal ja esta no proprio DARE
- `extratoMode = not_applicable`: a guia nao exige extrato separado
- `extratoMode = unknown`: classificacao ainda nao confirmada; tratar com cautela operacional

Regra de consumo:
- o VC Digital nao deve oferecer botao de "pedir extrato" quando `extratoExpected = false`
- o card pode ser tratado como completo quando `pairing.guiaCompletaParaCliente = true`
- `pairing.extratoLiberadoParaConsumo = true` tanto para `PAR_COMPLETO` quanto para `DARE_AUTOSSUFICIENTE`

## Reuso de extrato separado

- `guideContentFingerprint` representa o conteudo fiscal da guia sem depender apenas do vencimento emitido
- `extratoFingerprint` representa a versao logica do extrato separado
- `extratoReusable = true` indica que o extrato pode ser reaproveitado entre reemissoes do mesmo conteudo
- `extratoRefreshReason = same_content` sugere reaproveitamento
- `extratoRefreshReason = content_changed` sugere baixar novo extrato
- `extratoRefreshReason = missing` indica que ainda nao ha extrato separado disponivel
- `extratoRefreshReason = embedded_in_dare` indica que nao existe dependencia de extrato separado

Ajuste operacional validado em `2026-03-16`:
- `20` guias unicas com `vencimentoOriginal = 2026-03-31` foram realinhadas no catalogo local porque estavam gravadas com datas intermediarias
- `12` guias unicas de `02/2026` ficaram comprovadas com recálculo real para `2026-03-31`
- `77` entradas do catalogo local foram corrigidas ou marcadas para republicacao

Origem declarada em `dueDateSource`:
- `LISTA_SEFIN`: veio apenas da lista original
- `EMISSAO_CONFIRMADA`: a emissão confirmou a mesma data da lista
- `EMISSAO_RECALCULADA`: houve emissão com nova data
- `EMISSAO_SEM_BASE_LISTA`: o coletor conseguiu a data emitida, mas não tinha base confiável da lista

## documentType expostos hoje

Ativos:
- `dare`
- `extrato_dare`

Reservados, ainda não expostos por esse endpoint:
- `guia_mei`
- `guia_pgdas`
- `parcelamento_pgfn`
- `parcelamento_simples_nacional`
- `parcelamento_mei`

## Regras de vínculo

O vínculo principal esperado pelo VC Digital é:
- `controladora.companyId + company.cnpj`

Por isso, cada guia/documento deve trazer sempre:
- `cnpj`
- `numeroDocumento`
- `documentType`

## Regras de exportação

## Reenvio compulsório de manutenção

Quando o VC Digital já importou um documento antigo/errado e precisa substituir pelo arquivo atual do ICMS SaaS, há dois caminhos equivalentes:

- botão web `✈ Reenviar` na tela de `Guias`
- endpoint `POST /api/internal/fiscal-documents/force-republish`

Efeito publicado:
- o ICMS SaaS clona o `guia_pdf` e o `extrato_pdf` finais mais recentes da guia filtrada
- cada clone recebe um novo `artifactId`
- o novo artefato volta para `transfer_status = PENDENTE`
- `documents[]` passa a expor esses novos `artifactId`s no polling seguinte

Importante para o consumidor:
- o VC Digital deve substituir o documento local anterior quando o mesmo `numeroDocumento + documentType` voltar com novo `artifactId`
- isso não depende de reset do lado do coletor local
- screenshots/json de auditoria não entram nessa republicação compulsória

Payload aceito:
- `controladoraId` opcional quando o token já é escopado
- `companyId` opcional
- `cnpj` opcional
- `numeroDocumento` opcional

Exemplo:

```bash
curl -X POST \
  -H "Authorization: Bearer <token-da-controladora>" \
  -H "Content-Type: application/json" \
  -d '{
    "controladoraId": 1,
    "companyId": 3,
    "numeroDocumento": "20261200047276"
  }' \
  "https://icms.vcnodigital.com/api/internal/fiscal-documents/force-republish"
```

Resposta esperada:
- `guidesMatched`
- `guidesRepublished`
- `guidePdfRepublished`
- `extratoPdfRepublished`
- `artifacts[]` com os novos `artifactId`s
- `warnings[]` para guias que não tinham PDF final disponível

- chamar `mark-exported` apenas depois que o VC Digital importar o artefato com sucesso
- chamar `mark-delivered` apenas depois que a VPS da Peramix realmente enviar ou registrar falha de envio ao cliente no WhatsApp
- payload esperado:
  - `artifactId` obrigatório
  - `externalDocUrl` opcional
- `artifactId` é único por artefato e estável
- chamada repetida mantém o artefato como exportado e atualiza `transferred_at` e `externalDocUrl`
- status interno usado pelo ICMS SaaS:
  - `PENDENTE`
  - `ENVIADO_VCNO`
- payload aceito em `mark-delivered`:
  - `artifactId` obrigatório
  - `deliveryStatus` obrigatório
  - `channel` opcional
  - `recipient` opcional
  - `messageId` opcional
  - `ticketId` opcional
  - `externalMessageUrl` opcional
  - `externalDocUrl` opcional
  - `notes` opcional
- `deliveryStatus` aceitos em `mark-delivered`:
  - `ENVIADO_CLIENTE`
  - `FALHA_ENTREGA`
  - `PENDENTE_REENVIO`

## Arquivos

- os arquivos principais expostos hoje são PDF
- padrão nominal atual para novos arquivos finais:
  - `DARE-<numeroDocumento>-<parcela>.pdf`
  - `EXTRATO-<numeroDocumento>-<parcela>.pdf`
  - quando houver recalculo/data diferente do original:
    - `DARE-<numeroDocumento>-<parcela>-31_03_2026R.pdf`
    - `EXTRATO-<numeroDocumento>-<parcela>-31_03_2026R.pdf`
- a fonte autoritativa do coletor local e sempre a pasta da propria empresa no desktop (`guia_pdf` e `extrato_pdf`)
- o catalogo local do coletor funciona como indice para acelerar reuso, sync e reconciliacao, mas nao substitui a verificacao do arquivo real em disco
- o sincronizador local agora exige `guia_pdf` real com cabecalho `%PDF-` e compatibilidade de `guia + parcela`; arquivo ambiguo ou so `extrato_*` fica bloqueado no desktop
- `extrato_dare` só é liberado para consumo quando o mesmo `numeroDocumento` já possui `guia_pdf` disponível no ICMS SaaS
- se existir apenas extrato sem guia correspondente, ele continua no hub interno para auditoria, mas não entra em `documents[]`
- por padrão, `extrato_screenshot` e `extrato_json` ficam apenas no hub interno do ICMS SaaS para auditoria e nao entram no endpoint consumido pelo VC Digital
- se o consumidor quiser apoio de auditoria, pode chamar `GET /api/internal/fiscal-documents?includeAuditArtifacts=1`
- hoje o único artefato opcional de auditoria publicado nesse modo é `extrato_screenshot`
- `auditArtifacts[]` nunca substitui `documents[]`; ele é apenas complementar
- faixa observada no acervo atual: 96 KB a 269 KB
- `downloadUrl` hoje não exige o mesmo token
- `downloadUrl` entrega `Content-Disposition` com o nome canônico do artefato final (`DARE-...` / `EXTRATO-...`), mesmo quando o arquivo histórico em disco tiver nome legado
- `viewerUrl` pode ser usado diretamente no navegador

## Reconciliacao e republicacao

- o coletor local pode reindexar as pastas das empresas e republicar `guia_pdf` e `extrato_pdf` quando encontrar arquivo real em disco e detectar divergencia entre pasta, catalogo local e ICMS SaaS
- republicacao automatica ficou mais rigida: se a parcela no arquivo nao bater com a parcela da guia, ou se nao houver `guia_pdf` real, o item nao e reenviado ao ICMS SaaS
- quando um PDF e republicado pelo coletor, o registro remoto do artefato pode ser reinserido com `transfer_status = PENDENTE`
- isso e intencional: permite que o VC Digital reconsuma o artefato correto quando houve correcao, backfill ou reset operacional
- `json/png` de auditoria nao sao promovidos a documento fiscal final do cliente por esse processo
- por isso, o consumidor deve sempre tratar `artifactId` como identificador do artefato atual exposto pela API, e nao assumir que um artefato antigo permanece o mesmo apos uma republicacao operacional

## Modo opcional de auditoria

Uso recomendado:
- fluxo normal do VC Digital: consumir só `documents[]` com PDFs
- fluxo de conferência/auditoria: chamar com `includeAuditArtifacts=1`

Exemplo:

```bash
curl -H "Authorization: Bearer <token-da-controladora>" \
  "https://icms.vcnodigital.com/api/internal/fiscal-documents?cnpj=60789911000171&pending_only=1&includeAuditArtifacts=1"
```

Regra:
- `documents[]` continua trazendo apenas documentos finais para cliente
- `extrato_dare` em `documents[]` depende de `guia_pdf` já disponível para a mesma guia
- `auditArtifacts[]` pode trazer apoio visual, como screenshot do extrato
- o outro sistema deve tratar `auditArtifacts[]` como opcional e nunca como documento fiscal final enviado ao cliente

## Erros e consistência

- `401` sem token ou token inválido
- `403` token sem acesso à controladora ou ao artefato
- `404` artefato inexistente no `mark-exported`
- `404` artefato inexistente no `mark-delivered`
- `422` payload inválido, filtros insuficientes ou tentativa de enviar `extrato_*` sem `guia_pdf` real válido
- `500` falha interna ao marcar exportação
- quando não houver documentos ou o CNPJ não existir, o retorno é `200` com `total = 0` e `data = []`

## Orquestrador reverso (VC Digital -> ICMS -> SEFIN local)

O ICMS SaaS já expõe uma fila reversa para o VC Digital solicitar novas buscas sem falar direto com o desktop.

Fluxo:
1. o VC Digital cria um job
2. o `sefin_browser.py` em uma máquina autorizada faz `claim` do próximo job da controladora
3. o desktop executa a busca na SEFIN
4. ao final, o desktop marca o job como `CONCLUIDO` ou `FALHA`
5. os documentos novos entram no endpoint fiscal normal

### Endpoints do orquestrador

- `GET /api/internal/orchestrator/jobs`
- `POST /api/internal/orchestrator/jobs`
- `POST /api/internal/orchestrator/jobs/claim`
- `POST /api/internal/orchestrator/jobs/complete`
- `POST /api/internal/orchestrator/jobs/fail`

### Regras de autenticação

- usa o mesmo `Authorization: Bearer <token da controladora>`
- `claim`, `complete` e `fail` exigem token escopado por controladora

### Payload para criar job

Campos aceitos:
- `controladoraId`
- `commandType`
- `companyId` opcional
- `cnpj` opcional
- `companyKey` opcional
- `year` opcional
- `numeroDocumento` opcional
- `parcela` opcional
- `targetDueDate` opcional
- `overdueOnly` opcional
- `overdueGuideLimit` opcional
- `consumeAvailableDueDates` opcional
- `priority` opcional
- `force` opcional
- `requestedBy` opcional
- `notes` opcional

`commandType` aceitos hoje:
- `fetch_company_documents`
- `fetch_numero_documento`

Exemplo:

```json
{
  "controladoraId": 1,
  "commandType": "fetch_numero_documento",
  "companyId": 97,
  "cnpj": "0000006559719",
  "numeroDocumento": "20261600483103",
  "parcela": "00",
  "year": 2026,
  "targetDueDate": "2026-03-31",
  "consumeAvailableDueDates": true,
  "priority": 80,
  "requestedBy": "VC Digital",
  "force": true,
  "notes": "Reemissao pontual para envio ao cliente"
}
```

Exemplo alternativo para lote vencido:

```json
{
  "controladoraId": 1,
  "commandType": "fetch_company_documents",
  "companyKey": "00000000913821 - RONDONIA COMERCIO DE FERRO E ACO LTDA",
  "overdueOnly": true,
  "overdueGuideLimit": 2,
  "consumeAvailableDueDates": true,
  "priority": 70,
  "requestedBy": "VC Digital",
  "force": false
}
```

Exemplo alternativo para descoberta mensal:

```json
{
  "controladoraId": 1,
  "commandType": "fetch_company_documents",
  "companyKey": "00000000285056 - DROGA MIL COMERCIO DE MEDICAMENTOS LTDA",
  "dueMonth": "2026-04",
  "priority": 75,
  "requestedBy": "VC Digital",
  "force": false
}
```

### Automação de guias vencidas

Contrato operacional publicado para o desktop:

- a empresa deve ser resolvida por `companyId`, `cnpj` ou `companyKey`
- quando `commandType = fetch_numero_documento`, o desktop entra em `single-document mode`
- nesse modo ele filtra `numeroDocumento + parcela`, nao varre lote inteiro e para assim que concluir a guia pedida
- `targetDueDate` so e aceito se existir em `availableDueDates[]`
- quando `overdueOnly = true`, o desktop ignora guias em dia
- quando `overdueGuideLimit = 1` ou `2`, o desktop emite só essa quantidade de guias vencidas
- quando `consumeAvailableDueDates = true`, o desktop usa apenas a lista real `availableDueDates` oferecida pela SEFIN
- quando `dueMonth = YYYY-MM`, o desktop reduz a descoberta para as linhas cuja `vencimento_lista` pertença a esse mês
- o desktop não inventa nova data para guia não vencida
- ao concluir, o ICMS SaaS recebe os PDFs atualizados e o endpoint fiscal passa a expor a nova data em `dueDateReal`

### Deduplicação aplicada

Antes de criar um job novo, o ICMS verifica:
- job equivalente já `PENDENTE` ou `EM_EXECUCAO`
- empresa já com lock em outro job ativo
- documento já disponível no hub
- empresa já sincronizada/revisada recentemente

Nesses casos, a API responde `200` com:
- `created = false`
- `reason` informando o motivo (`duplicate_active`, `company_locked`, `already_available`, `already_fresh`)

### Claim do worker

O desktop faz:

```json
{
  "workerId": "host-local-1234"
}
```

Resposta:
- `claimed = true` com o job para executar
- ou `claimed = false` quando não houver pendência

### Conclusão e falha

`complete`:

```json
{
  "jobId": 10,
  "result": {
    "message": "Job concluído",
    "jobs": []
  }
}
```

`fail`:

```json
{
  "jobId": 10,
  "errorMessage": "Empresa não disponível no perfil",
  "result": {
    "message": "Falha operacional"
  }
}
```
