From f3e46cca4ce9f18efce4a9ffbde294e77fc0a800 Mon Sep 17 00:00:00 2001 From: gabrielb Date: Tue, 19 May 2026 18:09:33 -0300 Subject: [PATCH 01/20] =?UTF-8?q?feat(portal):=20simplificar=20fluxo=20de?= =?UTF-8?q?=20primeiro=20acesso=20para=20Op=C3=A7=C3=A3o=20B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove etapas OTP (canal, código, senha). Novo fluxo: identificar (CPF/CNPJ) → confirmar (mostra nome + email mascarado) → sucesso (email enviado via Keycloak). Co-Authored-By: Claude Sonnet 4.6 --- src/pages/primeiro-acesso.vue | 201 ++++++++------------------ src/services/primeiroAcessoService.js | 9 ++ 2 files changed, 71 insertions(+), 139 deletions(-) diff --git a/src/pages/primeiro-acesso.vue b/src/pages/primeiro-acesso.vue index 53e3075..71e25c9 100644 --- a/src/pages/primeiro-acesso.vue +++ b/src/pages/primeiro-acesso.vue @@ -1,10 +1,8 @@ diff --git a/src/pages/index.vue b/src/pages/index.vue index 8cabcb5..dce28ec 100644 --- a/src/pages/index.vue +++ b/src/pages/index.vue @@ -267,6 +267,7 @@ async function continuar() { class="w-full" size="large" :loading="carregando" + :disabled="carregando" @click="continuar" /> diff --git a/src/pages/portal/certidoes.vue b/src/pages/portal/certidoes.vue index 1e5fa65..2fd75fe 100644 --- a/src/pages/portal/certidoes.vue +++ b/src/pages/portal/certidoes.vue @@ -119,7 +119,7 @@ const statusMap = { size="small" outlined :loading="carregandoPdf === cert.id" - :disabled="cert.status === 'CANCELADA'" + :disabled="cert.status === 'CANCELADA' || !!carregandoPdf" @click="reemitir(cert)" /> diff --git a/src/pages/portal/dados.vue b/src/pages/portal/dados.vue index 964ad90..ecc9af0 100644 --- a/src/pages/portal/dados.vue +++ b/src/pages/portal/dados.vue @@ -172,7 +172,7 @@ function formatarTelefone(e) {

diff --git a/src/pages/portal/debitos.vue b/src/pages/portal/debitos.vue index b8f15fb..f39bc1b 100644 --- a/src/pages/portal/debitos.vue +++ b/src/pages/portal/debitos.vue @@ -221,6 +221,7 @@ function limparFiltros() { outlined class="whitespace-nowrap" :loading="carregandoGuia === debito.id" + :disabled="!!carregandoGuia" @click="emitirGuia(debito)" /> diff --git a/src/pages/portal/pagamentos.vue b/src/pages/portal/pagamentos.vue index 7629017..f95927b 100644 --- a/src/pages/portal/pagamentos.vue +++ b/src/pages/portal/pagamentos.vue @@ -136,6 +136,7 @@ const formaPagMap = { size="small" text :loading="carregandoComprovante === pag.id" + :disabled="!!carregandoComprovante" @click="baixarComprovante(pag)" /> diff --git a/src/pages/servicos/certidao.vue b/src/pages/servicos/certidao.vue index ec1e886..ac27097 100644 --- a/src/pages/servicos/certidao.vue +++ b/src/pages/servicos/certidao.vue @@ -201,6 +201,7 @@ function reiniciar() { icon="pi pi-download" class="flex-1" :loading="carregandoEmissao" + :disabled="carregandoEmissao" @click="emitir" /> diff --git a/src/pages/servicos/iptu.vue b/src/pages/servicos/iptu.vue index 869f300..edeecd0 100644 --- a/src/pages/servicos/iptu.vue +++ b/src/pages/servicos/iptu.vue @@ -213,6 +213,7 @@ function formatarMoeda(valor) { size="small" outlined :loading="carregandoPdf === `carne-${imovelSelecionado.inscricaoImobiliaria}`" + :disabled="!!carregandoPdf" @click="emitirCarne(imovelSelecionado)" /> @@ -239,6 +240,7 @@ function formatarMoeda(valor) { text aria-label="Emitir boleto" :loading="carregandoPdf === `boleto-${debito.id}`" + :disabled="!!carregandoPdf" @click="emitirBoleto(debito)" /> -- 2.43.0 From 8b5c37abe97c7cfaa448ffa0e2470cbe1db4e6be Mon Sep 17 00:00:00 2001 From: gabrielb Date: Tue, 19 May 2026 20:58:14 -0300 Subject: [PATCH 03/20] =?UTF-8?q?feat(api):=20adicionar=20tratamento=20de?= =?UTF-8?q?=20erros=20e=20logging=20nas=20requisi=C3=A7=C3=B5es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/api/proxy/[...path].ts | 31 +++++++++++++++++++++++++++++-- src/composables/useApi.ts | 22 +++++++++++++++------- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/server/api/proxy/[...path].ts b/server/api/proxy/[...path].ts index 592fb87..d71fdc1 100644 --- a/server/api/proxy/[...path].ts +++ b/server/api/proxy/[...path].ts @@ -66,8 +66,35 @@ export default defineEventHandler(async (event) => { } catch (err: unknown) { const fetchErr = err as { response?: { status?: number; _data?: unknown } } if (fetchErr.response) { - setResponseStatus(event, fetchErr.response.status ?? 500) - return fetchErr.response._data + const status = fetchErr.response.status ?? 500 + const raw = fetchErr.response._data + + // responseType: 'stream' faz _data ser ReadableStream mesmo em erros — + // lemos o stream e parseamos como JSON para que o cliente veja o envelope de erro + let body: unknown = raw + if (raw && typeof (raw as ReadableStream).getReader === 'function') { + const reader = (raw as ReadableStream).getReader() + const chunks: Uint8Array[] = [] + for (;;) { + const { value, done } = await reader.read() + if (done) break + if (value) chunks.push(value) + } + const totalLen = chunks.reduce((n, c) => n + c.length, 0) + const merged = new Uint8Array(totalLen) + let offset = 0 + for (const c of chunks) { merged.set(c, offset); offset += c.length } + const text = new TextDecoder().decode(merged) + try { body = JSON.parse(text) } catch { body = text } + } + + if (import.meta.dev) { + console.error(`[proxy] ERRO ${status} ← ${url}`, body) + } + + setResponseStatus(event, status) + setResponseHeader(event, 'content-type', 'application/json; charset=utf-8') + return body } throw err } diff --git a/src/composables/useApi.ts b/src/composables/useApi.ts index 21d67b5..1569be7 100644 --- a/src/composables/useApi.ts +++ b/src/composables/useApi.ts @@ -18,13 +18,21 @@ export function useApi() { } async function request(path: string, options: FetchOptions = {}): Promise { - return await $fetch(buildUrl(path), { - ...options, - headers: { - 'X-Requested-With': 'fetch', - ...(options.headers ?? {}), - }, - }) + try { + return await $fetch(buildUrl(path), { + ...options, + headers: { + 'X-Requested-With': 'fetch', + ...(options.headers ?? {}), + }, + }) + } catch (err: unknown) { + if (import.meta.dev) { + const e = err as { status?: number; data?: unknown } + console.error(`[api] ${(options.method ?? 'GET').toUpperCase()} ${path} → ${e.status}`, e.data) + } + throw err + } } return { -- 2.43.0 From b638af1a3914c4bd4402ddb72b5892463b0cecc7 Mon Sep 17 00:00:00 2001 From: gabrielb Date: Tue, 19 May 2026 21:10:29 -0300 Subject: [PATCH 04/20] fix(prefeitura): garantir que o cache do Redis seja utilizado corretamente e evitar falhas --- server/utils/prefeitura.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/server/utils/prefeitura.ts b/server/utils/prefeitura.ts index 56b3dc4..9eda259 100644 --- a/server/utils/prefeitura.ts +++ b/server/utils/prefeitura.ts @@ -17,13 +17,18 @@ export async function fetchPrefeituraInfo(dominio: string): Promise {}) + } return info } catch (err) { console.error(`[prefeitura] lookup falhou para '${dominio}':`, (err as Error).message) -- 2.43.0 From 3fb5cdb6a84fe6193097b37d0261d243578fcb8e Mon Sep 17 00:00:00 2001 From: gabrielb Date: Wed, 20 May 2026 00:13:10 -0300 Subject: [PATCH 05/20] =?UTF-8?q?feat(portal):=20adicionar=20navega=C3=A7?= =?UTF-8?q?=C3=A3o=20e=20mock=20de=20dados=20para=20certid=C3=B5es,=20alva?= =?UTF-8?q?r=C3=A1s=20e=20pagamentos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/portal.vue | 149 ++++++++++++++++++++++++++++---- src/pages/entrar.vue | 5 ++ src/pages/portal/alvaras.vue | 63 ++++++++++++-- src/pages/portal/certidoes.vue | 35 +++++++- src/pages/portal/dados.vue | 8 +- src/pages/portal/debitos.vue | 124 ++++++++++++++++++-------- src/pages/portal/pagamentos.vue | 41 ++++++++- src/pages/portal/painel.vue | 23 +++-- src/pages/primeiro-acesso.vue | 4 +- src/pages/servicos/certidao.vue | 12 ++- src/pages/servicos/iptu.vue | 12 ++- 11 files changed, 399 insertions(+), 77 deletions(-) create mode 100644 src/pages/entrar.vue diff --git a/src/layouts/portal.vue b/src/layouts/portal.vue index b9cea00..b6012ae 100644 --- a/src/layouts/portal.vue +++ b/src/layouts/portal.vue @@ -1,44 +1,82 @@ - -

- Última atualização: {{ alv.ultimaAtualizacao }} -

diff --git a/src/pages/portal/certidoes.vue b/src/pages/portal/certidoes.vue index 2fd75fe..89b0649 100644 --- a/src/pages/portal/certidoes.vue +++ b/src/pages/portal/certidoes.vue @@ -7,6 +7,28 @@ definePageMeta({ middleware: 'auth', }) +// ─── MOCKS APRESENTAÇÃO — remover antes do deploy ───────────────────────── +const MOCK_ATIVO = true +const CERTIDOES_MOCK = [ + { + id: 1, + tipo: 'Certidão Negativa de Débitos', + numero: '2024/0042', + dataEmissao: '2024-03-10', + dataValidade: '2025-03-10', + status: 'ATIVA', + }, + { + id: 2, + tipo: 'Certidão de Regularidade Fiscal', + numero: '2023/0187', + dataEmissao: '2023-06-15', + dataValidade: '2024-06-15', + status: 'VENCIDA', + }, +] +// ────────────────────────────────────────────────────────────────────────── + const router = useRouter() const certidoes = ref([]) const carregando = ref(true) @@ -20,9 +42,16 @@ async function carregar() { mensagemErro.value = '' try { const res = await portalService.getCertidoes() - certidoes.value = res.data?.content ?? [] + certidoes.value = res.data ?? [] + if (MOCK_ATIVO && certidoes.value.length === 0) { + certidoes.value = CERTIDOES_MOCK + } } catch (e) { mensagemErro.value = e?.data?.description ?? 'Não foi possível carregar as certidões.' + if (MOCK_ATIVO) { + certidoes.value = CERTIDOES_MOCK + mensagemErro.value = '' + } } finally { carregando.value = false } @@ -59,7 +88,7 @@ const statusMap = {

Certidões

Suas certidões emitidas e disponíveis para reemissão.

-