# Session Log — 2026-06-09

Resumo técnico das mudanças realizadas nesta sessão de trabalho no projeto Klavo.

---

## Contexto inicial

O backend estava em produção no Railway (`https://klavo-production.up.railway.app/api/v1`) com 500 em todos os endpoints principais (`GET /tenant`, `GET /invoices`, `POST /invoices`, `POST /invoices/sync`).

---

## Problema 1 — Redis/BullMQ derrubando o app inteiro

### Causa
O `AppModule` registrava `BullModule.forRootAsync` globalmente. Sem Redis disponível no Railway, a inicialização falhava e **nenhum endpoint funcionava**.

### Módulos afetados
| Módulo | O que tinha |
|--------|-------------|
| `app.module.ts` | `BullModule.forRootAsync` (registro global Redis) |
| `fiscal.module.ts` | `BullModule.registerQueue({ name: 'fiscal' })` + `FiscalProcessor` |
| `payments.module.ts` | `BullModule.registerQueue({ name: 'fiscal' })` |
| `saas.module.ts` | `BullModule.registerQueue({ name: 'saas-usage' })` + `SaasUsageProcessor` |

### Solução
Removido Bull/BullMQ completamente. Processamento passou a ser síncrono (inline):

- `FiscalService.issueInvoice` → chama `processEmission` diretamente
- `FiscalService.retryInvoice` → chama `processEmission` diretamente
- `SaasService.recordUsage` → salva evento diretamente no `usageEventRepo`
- `app.module.ts` → removido `BullModule.forRootAsync` e import do `@nestjs/bull`

**Commit:** `75a4d67` — `fix: remove Bull/Redis dependency — process queues inline`

---

## Problema 2 — `GET /invoices` retornando 500 após correção do Redis

### Causa
A tabela `invoices` havia sido criada manualmente em produção a partir de uma versão anterior do entity. Colunas adicionadas depois não existiam no banco:

```
consulta_fail_count  SMALLINT NOT NULL DEFAULT 0
queued_at            TIMESTAMPTZ
abrasf_protocol      VARCHAR(255)
email_sent_at        TIMESTAMPTZ
cancel_xml           TEXT
cnae_code            VARCHAR(15)
iss_exigibility      SMALLINT NOT NULL DEFAULT 1
```

### Solução
Migration `008_invoices_missing_columns.sql` aplicada no Neon:

```sql
ALTER TABLE invoices
  ADD COLUMN IF NOT EXISTS consulta_fail_count SMALLINT     NOT NULL DEFAULT 0,
  ADD COLUMN IF NOT EXISTS queued_at           TIMESTAMPTZ,
  ADD COLUMN IF NOT EXISTS abrasf_protocol     VARCHAR(255),
  ADD COLUMN IF NOT EXISTS email_sent_at       TIMESTAMPTZ,
  ADD COLUMN IF NOT EXISTS cancel_xml          TEXT,
  ADD COLUMN IF NOT EXISTS cnae_code           VARCHAR(15),
  ADD COLUMN IF NOT EXISTS iss_exigibility     SMALLINT     NOT NULL DEFAULT 1;
```

**Commit:** `f3df165` — `fix: add missing columns to invoices table (migration 008)`

---

## Problema 3 — Erro [EL83] na emissão de NFS-e para tomadores de outra UF

### Causa
O XML da NFS-e sempre incluía o bloco `<Endereco>` do tomador. Quando o tomador é de uma UF diferente do prestador, a prefeitura retornava:

> `[EL83] Não é possível alterar o endereço do tomador jurídico de fora para um endereço dentro do município`

### Solução
Em `AbrasXmlBuilder.buildRps` (`fiscal.module.ts`), antes de montar o XML:

1. Deriva a UF do prestador a partir dos 2 primeiros dígitos do `serviceCityCode` (IBGE) via mapa estático `ibgeUfMap`
2. Se `borrowerUf` estiver preenchido e for diferente de `issuerUf`, define `skipAddress = true`
3. Condição do bloco `<Endereco>` mudou de `v.borrowerAddress ?` para `(v.borrowerAddress && !skipAddress) ?`

```typescript
const ibgeUfMap: Record<string, string> = {
  '11':'RO','12':'AC','13':'AM','14':'RR','15':'PA','16':'AP','17':'TO',
  '21':'MA','22':'PI','23':'CE','24':'RN','25':'PB','26':'PE','27':'AL','28':'SE','29':'BA',
  '31':'MG','32':'ES','33':'RJ','35':'SP',
  '41':'PR','42':'SC','43':'RS',
  '50':'MS','51':'MT','52':'GO','53':'DF',
};
const issuerUf = ibgeUfMap[v.serviceCityCode?.substring(0, 2) ?? ''];
const skipAddress = !!(v.borrowerUf && issuerUf && v.borrowerUf !== issuerUf);
```

**Commit:** `1fff5b1` — `fix: skip <Endereco> in NFS-e XML when tomador is from a different UF`

---

## Melhoria — Erro completo da prefeitura na tela NFS-e

### Antes
- Tabela: erro truncado em 120 caracteres com reticências
- Modal de emissão em lote: mostrava apenas a palavra "Erro" (com tooltip ao passar o mouse)

### Depois
- Tabela: `errorMessage` exibido completo com `whitespace-pre-wrap break-words`
- Modal de emissão em lote: texto do erro exibido inline abaixo de "Erro"

Arquivo: `frontend/src/app/(dashboard)/notas-fiscais/page.tsx`

---

## Migrations aplicadas em produção (Neon)

| Arquivo | O que faz |
|---------|-----------|
| `007_tenant_smtp.sql` | Adiciona colunas SMTP à tabela `tenants` |
| `008_invoices_missing_columns.sql` | Adiciona colunas faltantes à tabela `invoices` |

---

## Resultado dos testes pós-correção

```
GET  /tenant         → 200 ✓
GET  /invoices       → 200 [] ✓
POST /invoices/sync  → 400 (esperado — URL NFS-e não configurada) ✓
```

---

## Estado ao final da sessão

- Todos os endpoints da API respondem corretamente
- Backend sem dependência de Redis
- NFS-e de tomadores de outra UF não causa mais [EL83]
- Erros da prefeitura exibidos por completo na tela NFS-e
- README atualizado (removidas referências a Redis/BullMQ)
