Carregando...

⌨️ Atalhos de Teclado

CtrlF
Abrir filtros
CtrlK
Busca rápida global
G
Ir para coordenadas
Home
Extensão inicial
Backspace
Extensão anterior
CtrlE
Modo edição
CtrlS
Salvar alterações
I
Identificar feição
Esc
Cancelar operação
?
Mostrar atalhos

B
Buffer/Raio
H
Extensão inicial (Home)
P
Capturar mapa (Print)
C
Copiar coordenadas
L
Histórico de ações

M
Medir linha (com Snap)
ShiftA
Medir área (com Snap)
Esc
Cancelar medição
🖱️ Dir.
Finalizar medição

📍 Ir para Coordenadas

🎯 Desenhar Buffer/Raio

💡 Clique no mapa para definir o centro do buffer.

📜 Histórico de Alterações

0 registros

Carregando histórico...

Online
👤 Usuário
🗂️Camadas do Mapa
Mapas Base
Camadas
SIGWEB
Lat: - , Lng: -

📏 Calcular Área

Informações do Imóvel

0
Imóveis
R$ 0
Arrecadado
R$ 0
A Receber
0%
Adimplência

🔍 Consulta IPTU

🖨️ Impressão em Lote de Carnês
Pago Parcial Pendente Vencido
� Resultados 0
🔍

Use os filtros para pesquisar

💰 ITBI - Imposto Transmissão Bens Imóveis

🏠 Dados do Imóvel

Ou busque por nome do proprietário ou quadra/lote abaixo

👤 Transmitente (Vendedor) - Proprietário Atual

👤 Adquirente (Comprador)

💵 Dados da Transação

📝 Observações

🔍 Consultar Guia ITBI

📋 Histórico de Transações do Imóvel

Busque um imóvel na aba "Nova Guia" para ver o histórico de transações.

📐 Memorial Descritivo

💡

Instruções: Clique no botão abaixo para selecionar um lote no mapa. Os campos em azul serão preenchidos automaticamente.

Nenhum lote selecionado

🏠 Identificação do Imóvel

📏 Áreas e Dimensões

📋 Dados de Registro

👤 Proprietário

🔧 Responsável Técnico

📍 Tabela de Vértices

Os vértices são calculados automaticamente a partir da geometria do lote selecionado. As coordenadas são apresentadas no sistema UTM e os azimutes em graus.

Vértice Coordenada E (m) Coordenada N (m) Lado Azimute (°) Distância (m)
📍

Selecione um lote no mapa para visualizar os vértices

📝 Observações

📋 Gestão de Alvarás e Licenças

0
Total de Alvarás
0
Vigentes
0
Vencendo em 30 dias
0
Vencidos
📈 Distribuição por Tipo de Alvará
⚡ Ações Rápidas
1 📍 Identificar o Imóvel
A lista será atualizada com as camadas disponíveis no mapa
OU
1. Imóvel
2. Dados
3. Confirmar
🔍 Buscar Alvarás
🔍

Use a busca acima para encontrar alvarás

⚠️
Central de Alertas

Alvarás próximos do vencimento ou já vencidos

Nenhum alerta no momento

📝 Gerar Relatório para Fiscalização

Gere uma lista de alvarás para fiscalização em campo.

© SIGWEB • Cleriston Barbosa • 2025
Guia ITBI - ${t.numero_guia}

GUIA DE RECOLHIMENTO - ITBI

Imposto sobre Transmissão de Bens Imóveis Inter Vivos

${t.numero_guia}
${new Date(t.data_registro).toLocaleDateString('pt-BR')}
${t.vencimento ? new Date(t.vencimento).toLocaleDateString('pt-BR') : '-'}
Dados do Imóvel
${t.inscricao}
${t.quadra}
${t.lote}
${t.endereco || ''}
${t.bairro || ''}
${(t.area_terreno || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${(t.area_construcao || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${t.matricula || '-'}
Transmitente (Vendedor)
${t.transmitente?.nome || ''}
${t.transmitente?.cpf_cnpj || ''}
Adquirente (Comprador)
${t.adquirente?.nome || ''}
${t.adquirente?.cpf_cnpj || ''}
Valores
R$ ${(t.valor_venal || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
R$ ${(t.valor_transacao || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
R$ ${(t.calculo?.base_calculo || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
R$ ${(t.valor_itbi || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
`; } // Gerar HTML do Boleto ITBI para PDF function gerarHtmlBoletoItbi(t) { const boleto = t.boleto || {}; return ` Boleto ITBI - ${t.numero_guia}
BANCO
001-9
${boleto.linha_digitavel || 'Linha digitável não disponível'}
Prefeitura Municipal - CNPJ: ${boleto.beneficiario?.cnpj || '00.000.000/0001-00'}
0001 / 123456-7
${new Date(t.data_registro).toLocaleDateString('pt-BR')}
${t.numero_guia}
DM
N
${new Date().toLocaleDateString('pt-BR')}
${boleto.nosso_numero || t.numero_guia}
17
R$
${t.vencimento ? new Date(t.vencimento).toLocaleDateString('pt-BR') : '-'}
ITBI - Imposto sobre Transmissão de Bens Imóveis
Inscrição: ${t.inscricao} | Q.${t.quadra} L.${t.lote}
Transmitente: ${t.transmitente?.nome || ''}
Adquirente: ${t.adquirente?.nome || ''}
R$ ${(t.valor_itbi || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${t.adquirente?.nome || ''} - CPF/CNPJ: ${t.adquirente?.cpf_cnpj || ''}
${t.adquirente?.endereco || ''}
R$ ${(t.valor_itbi || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${boleto.linha_digitavel || 'Aguardando geração'}

Representação gráfica do código de barras

• Pagável em qualquer banco até o vencimento

• Após o vencimento, cobrar multa de 2% + juros de 1% ao mês

• Este boleto é válido somente para a transação especificada

✂️ RECIBO DO PAGADOR
Prefeitura Municipal
${t.numero_guia}
${t.vencimento ? new Date(t.vencimento).toLocaleDateString('pt-BR') : '-'}
R$ ${(t.valor_itbi || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
`; } // Download Guia ITBI como HTML (para impressão/PDF) async function downloadGuiaItbi(numeroGuia) { try { const backendUrl = window.BACKEND_URL || 'http://localhost:5000'; const response = await fetch(`${backendUrl}/api/itbi/consultar/${encodeURIComponent(numeroGuia)}`); const data = await response.json(); if (!data.success || !data.transacao) { showError('❌ Guia não encontrada'); return; } const html = gerarHtmlGuiaItbi(data.transacao); const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Guia_ITBI_${numeroGuia}.html`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showError('✅ Guia ITBI baixada! Abra o arquivo e use Ctrl+P para salvar como PDF.'); } catch (error) { console.error('Erro ao baixar guia:', error); showError('❌ Erro ao baixar guia: ' + error.message); } } // Download Boleto ITBI como HTML (para impressão/PDF) async function downloadBoletoItbi(numeroGuia) { try { const backendUrl = window.BACKEND_URL || 'http://localhost:5000'; const response = await fetch(`${backendUrl}/api/itbi/consultar/${encodeURIComponent(numeroGuia)}`); const data = await response.json(); if (!data.success || !data.transacao) { showError('❌ Boleto não encontrado'); return; } const html = gerarHtmlBoletoItbi(data.transacao); const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Boleto_ITBI_${numeroGuia}.html`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); showError('✅ Boleto ITBI baixado! Abra o arquivo e use Ctrl+P para salvar como PDF.'); } catch (error) { console.error('Erro ao baixar boleto:', error); showError('❌ Erro ao baixar boleto: ' + error.message); } } // Download automático da Guia e Boleto (chamado ao gerar) function downloadAutomaticoItbi(transacao) { const html_guia = gerarHtmlGuiaItbi(transacao); const blob_guia = new Blob([html_guia], { type: 'text/html' }); const url_guia = URL.createObjectURL(blob_guia); const a_guia = document.createElement('a'); a_guia.href = url_guia; a_guia.download = `Guia_ITBI_${transacao.numero_guia}.html`; document.body.appendChild(a_guia); a_guia.click(); document.body.removeChild(a_guia); URL.revokeObjectURL(url_guia); // Pequeno delay para o segundo download setTimeout(() => { const html_boleto = gerarHtmlBoletoItbi(transacao); const blob_boleto = new Blob([html_boleto], { type: 'text/html' }); const url_boleto = URL.createObjectURL(blob_boleto); const a_boleto = document.createElement('a'); a_boleto.href = url_boleto; a_boleto.download = `Boleto_ITBI_${transacao.numero_guia}.html`; document.body.appendChild(a_boleto); a_boleto.click(); document.body.removeChild(a_boleto); URL.revokeObjectURL(url_boleto); }, 500); } // Imprimir boleto ITBI async function imprimirBoletoItbi(numeroGuia) { try { const backendUrl = window.BACKEND_URL || 'http://localhost:5000'; const response = await fetch(`${backendUrl}/api/itbi/consultar/${encodeURIComponent(numeroGuia)}`); const data = await response.json(); if (!data.success || !data.transacao) { showError('❌ Guia não encontrada'); return; } const t = data.transacao; // Gerar HTML do boleto para impressão const printWindow = window.open('', '_blank', 'width=800,height=1000'); printWindow.document.write(` ITBI - Guia ${t.numero_guia}

GUIA DE RECOLHIMENTO - ITBI

Imposto sobre Transmissão de Bens Imóveis Inter Vivos

${t.numero_guia}
${new Date(t.data_registro).toLocaleDateString('pt-BR')}
${t.vencimento ? new Date(t.vencimento).toLocaleDateString('pt-BR') : '-'}
DADOS DO IMÓVEL
${t.inscricao}
${t.quadra}
${t.lote}
${t.endereco || ''}
${t.bairro || ''}
${(t.area_terreno || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${(t.area_construcao || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${t.matricula || '-'}
TRANSMITENTE (VENDEDOR)
${t.transmitente?.nome || ''}
${t.transmitente?.cpf_cnpj || ''}
${t.transmitente?.endereco || ''}
${t.transmitente?.telefone || ''}
ADQUIRENTE (COMPRADOR)
${t.adquirente?.nome || ''}
${t.adquirente?.cpf_cnpj || ''}
${t.adquirente?.endereco || ''}
${t.adquirente?.telefone || ''}
VALORES
${t.tipo_transacao === 'padrao' ? 'Compra e Venda' : t.tipo_transacao}
R$ ${(t.valor_venal || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
R$ ${(t.valor_transacao || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${((t.calculo?.aliquota || 0.02) * 100).toFixed(1)}%
R$ ${(t.calculo?.base_calculo || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
R$ ${(t.valor_itbi || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
${t.boleto?.linha_digitavel ? `
${t.boleto.linha_digitavel}
` : `

⚠️ Boleto será gerado após integração com API bancária

Guia para fins de TESTE - Realizar pagamento na tesouraria municipal

`}
setTimeout(() => { window.print(); }, 500); `); printWindow.document.close(); showError('🖨️ Guia ITBI aberta para impressão'); } catch (error) { console.error('Erro ao imprimir:', error); showError('Erro ao imprimir: ' + error.message); } } // Consultar guia ITBI async function consultarGuiaItbi() { const numeroGuia = document.getElementById('itbiConsultaGuia').value.trim(); if (!numeroGuia) { showError('Informe o número da guia'); return; } try { const backendUrl = window.BACKEND_URL || 'http://localhost:5000'; const response = await fetch(`${backendUrl}/api/itbi/consultar/${encodeURIComponent(numeroGuia)}`); const data = await response.json(); if (data.success && data.transacao) { const t = data.transacao; const statusClass = t.status === 'pago' ? 'pago' : (t.status === 'vencido' ? 'vencido' : 'pendente'); document.getElementById('itbiConsultaResultado').style.display = 'block'; document.getElementById('itbiConsultaResultado').innerHTML = `

📄 Guia ${t.numero_guia}

${t.status.toUpperCase()}
${t.status === 'pendente' || t.status === 'vencido' ? `
` : ''}
`; } else { document.getElementById('itbiConsultaResultado').style.display = 'block'; document.getElementById('itbiConsultaResultado').innerHTML = `

❌ ${data.error || 'Guia não encontrada'}

`; } } catch (error) { console.error('Erro ao consultar guia:', error); showError('Erro ao consultar: ' + error.message); } } // Registrar pagamento ITBI async function registrarPagamentoItbi(numeroGuia, valorItbi) { if (!confirm(`Confirma o registro de pagamento de R$ ${formatarMoedaValor(valorItbi)} para a guia ${numeroGuia}?`)) { return; } try { const backendUrl = window.BACKEND_URL || 'http://localhost:5000'; const response = await fetch(`${backendUrl}/api/itbi/registrar-pagamento`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ numero_guia: numeroGuia, valor_pago: valorItbi, data_pagamento: new Date().toISOString().split('T')[0] }) }); const result = await response.json(); if (result.success) { showError('✅ Pagamento registrado com sucesso!'); // Recarregar consulta consultarGuiaItbi(); } else { showError('❌ Erro: ' + (result.error || '')); } } catch (error) { showError('Erro ao registrar pagamento: ' + error.message); } } // Renderizar histórico de transações function renderizarHistoricoItbi() { const container = document.getElementById('itbiHistoricoLista'); if (!itbiHistoricoTransacoes || itbiHistoricoTransacoes.length === 0) { container.innerHTML = '

Nenhuma transação encontrada para este imóvel.

'; return; } let html = ''; itbiHistoricoTransacoes.forEach(t => { const statusClass = t.status === 'pago' ? 'pago' : (t.status === 'vencido' ? 'vencido' : 'pendente'); html += `
${t.numero_guia}
${new Date(t.data).toLocaleDateString('pt-BR')} | ${t.transmitente} → ${t.adquirente}
Transação: R$ ${formatarMoedaValor(t.valor_transacao)} | ITBI: R$ ${formatarMoedaValor(t.valor_itbi)}
${t.status.toUpperCase()}
`; }); container.innerHTML = html; } // Limpar formulário ITBI function limparFormularioItbi() { itbiImovelSelecionado = null; itbiHistoricoTransacoes = []; // Limpar todos os campos const campos = ['itbiQuadra', 'itbiLote', 'itbiInscricao', 'itbiEndereco', 'itbiNumero', 'itbiBairro', 'itbiCep', 'itbiAreaTerreno', 'itbiAreaConstruida', 'itbiValorVenal', 'itbiTransmitenteNome', 'itbiTransmitenteCpf', 'itbiTransmitenteEndereco', 'itbiTransmitenteTelefone', 'itbiTransmitenteEnderecoImovel', 'itbiAdquirenteEnderecoImovel', 'itbiAdquirenteNome', 'itbiAdquirenteCpf', 'itbiAdquirenteEndereco', 'itbiAdquirenteTelefone', 'itbiValorTransacao', 'itbiValorSFH', 'itbiMatricula', 'itbiObservacoes', 'itbiBuscaNome']; campos.forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; }); document.getElementById('itbiTipoTransacao').value = 'padrao'; document.getElementById('itbiCampoSFH').style.display = 'none'; document.getElementById('itbiCalculoBox').style.display = 'none'; document.getElementById('itbiHistoricoLista').innerHTML = ''; document.getElementById('itbiResultadosBusca').style.display = 'none'; document.getElementById('itbiStatusSelecao').innerHTML = 'Ou busque por nome do proprietário ou quadra/lote abaixo'; // Resetar checkboxes de endereço document.getElementById('itbiTransmitenteMesmoEndereco').checked = true; document.getElementById('itbiAdquirenteMesmoEndereco').checked = false; toggleEnderecoTransmitente(); toggleEnderecoAdquirente(); // Remover resultado se existir const resultBox = document.querySelector('.itbi-result-box'); if (resultBox) resultBox.remove(); showError('🧹 Formulário limpo'); } // ============================================ // FUNÇÕES DO MEMORIAL DESCRITIVO // ============================================ // Dados do lote selecionado para o Memorial let memorialLoteSelecionado = null; let memorialVerticesMarkers = []; let memorialSelecionandoLote = false; // Função para selecionar lote para o Memorial function selecionarLoteParaMemorial() { memorialSelecionandoLote = true; // Capturar escala e zoom do mapa principal ANTES de selecionar if (window.map) { memorialZoomDoMapaPrincipal = window.map.getZoom(); const lat = window.map.getCenter().lat; const metersPerPixel = 156543.03392 * Math.cos(lat * Math.PI / 180) / Math.pow(2, memorialZoomDoMapaPrincipal); memorialEscalaDoMapaPrincipal = Math.round(metersPerPixel * 96 / 0.0254); console.log(`📐 Escala capturada antes da seleção: 1:${memorialEscalaDoMapaPrincipal} (Zoom: ${memorialZoomDoMapaPrincipal})`); } // Minimizar modal const modal = document.getElementById('memorialModal'); if (modal) { modal.style.display = 'none'; } // Ativar modo identificar para a camada Lote identifySelectedKey = 'Lote'; identifyActive = true; map.getContainer().classList.add('identify-cursor'); // Atualizar status showError('🖱️ Clique em um LOTE no mapa para selecioná-lo'); console.log('📐 Modo seleção de lote para Memorial ativado'); } // ===================================================================== // ====================== MÓDULO DE ALVARÁS ============================ // ===================================================================== // Variáveis de controle do Alvará let alvaraSelecionandoLote = false; let alvaraLoteSelecionado = null; let alvaraChartTipos = null; // Abrir modal do Alvará function openAlvaraModal() { const modal = document.getElementById('alvaraModal'); if (modal) { modal.style.display = 'flex'; console.log('📋 Modal Alvarás aberto'); // Carregar dashboard carregarDashboardAlvaras(); // Atualizar lista de camadas disponíveis atualizarCamadasAlvara(); } } // Fechar modal do Alvará function closeAlvaraModal() { const modal = document.getElementById('alvaraModal'); if (modal) { modal.style.display = 'none'; } alvaraSelecionandoLote = false; } // Trocar abas do Alvará function switchAlvaraTab(tabName) { // Remover active de todas as tabs document.querySelectorAll('.alvara-tab').forEach(tab => tab.classList.remove('active')); document.querySelectorAll('.alvara-tab-content').forEach(content => content.classList.remove('active')); // Ativar tab selecionada const tabBtn = document.querySelector(`.alvara-tab[onclick*="${tabName}"]`); const tabContent = document.getElementById(`alvaraTab${tabName.charAt(0).toUpperCase() + tabName.slice(1)}`); if (tabBtn) tabBtn.classList.add('active'); if (tabContent) tabContent.classList.add('active'); // Carregar dados específicos da aba if (tabName === 'dashboard') carregarDashboardAlvaras(); if (tabName === 'alertas') carregarAlertasAlvaras(); } // Atualizar lista de camadas para seleção function atualizarCamadasAlvara() { const select = document.getElementById('alvaraCamadaSelecionada'); if (!select) return; select.innerHTML = ''; // Adicionar outras camadas disponíveis if (window.overlayLayers) { Object.keys(window.overlayLayers).forEach(key => { if (key !== 'Lote') { const opt = document.createElement('option'); opt.value = key; opt.textContent = key; select.appendChild(opt); } }); } } // Atualizar placeholder da busca function atualizarPlaceholderBuscaAlvara() { const tipo = document.getElementById('alvaraTipoBusca')?.value; const input = document.getElementById('alvaraBuscaInput'); if (!input) return; const placeholders = { 'nome': 'Digite o nome do proprietário...', 'cpf': 'Digite o CPF (000.000.000-00)...', 'cnpj': 'Digite o CNPJ (00.000.000/0000-00)...', 'inscricao': 'Digite a inscrição imobiliária...', 'quadra_lote': 'Digite quadra/lote (ex: 001/015)...' }; input.placeholder = placeholders[tipo] || 'Digite para buscar...'; } // Selecionar lote no mapa para Alvará function selecionarLoteParaAlvara() { alvaraSelecionandoLote = true; // Minimizar modal const modal = document.getElementById('alvaraModal'); if (modal) modal.style.display = 'none'; // Ativar modo identificar para a camada selecionada const camadaSelecionada = document.getElementById('alvaraCamadaSelecionada')?.value || 'Lote'; identifySelectedKey = camadaSelecionada; identifyActive = true; map.getContainer().classList.add('identify-cursor'); showError(`🖱️ Clique em um ${camadaSelecionada} no mapa para selecionar para o Alvará`); console.log('📋 Modo seleção de lote para Alvará ativado - Camada:', camadaSelecionada); } // Callback quando lote é selecionado no mapa function atualizarAlvaraComLote(feature) { if (alvaraSelecionandoLote) { alvaraSelecionandoLote = false; alvaraLoteSelecionado = feature; // Preencher formulário if (feature && feature.properties) { preencherAlvaraComLote(feature.properties); } // Reabrir modal const modal = document.getElementById('alvaraModal'); if (modal) modal.style.display = 'flex'; console.log('📋 Lote selecionado para Alvará'); } } // Armazenar dados do lote selecionado para uso posterior var _alvaraLoteSelecionado = null; // Preencher formulário com dados do lote function preencherAlvaraComLote(props) { if (!props) return; console.log('📋 Preenchendo Alvará com props:', props); // Guardar props para uso posterior _alvaraLoteSelecionado = props; const quadra = props.Quadra || props.quadra || props.QUADRA || props.qd || props.QD || ''; const lote = props.Lote || props.lote || props.LOTE || props.lt || props.LT || ''; const q = String(quadra).padStart(4, '0'); const l = String(lote).padStart(4, '0'); // Buscar Inscrição Imobiliária (múltiplas variantes) let inscricaoImob = props['Inscricao imobiliaria'] || props.Inscricao_imobiliaria || props.inscricao_imobiliaria || props.Inscricao || props.inscricao || props.INSCRICAO || props['Inscrição'] || props.Inscrição || props.cod_imovel || props.codigo_imovel || props.property_id || props.inscricao_municipal || props.insc_imob || ''; // Se não encontrou, gerar baseado em quadra/lote if (!inscricaoImob && quadra && lote) { inscricaoImob = `01.01.${q}.${l}.001`; } // Preencher campos do imóvel const inscricaoEl = document.getElementById('alvaraInscricao'); const quadraLoteEl = document.getElementById('alvaraQuadraLote'); const enderecoEl = document.getElementById('alvaraEndereco'); const areaEl = document.getElementById('alvaraArea'); const proprietarioEl = document.getElementById('alvaraProprietario'); if (inscricaoEl) inscricaoEl.value = inscricaoImob; if (quadraLoteEl) quadraLoteEl.value = quadra && lote ? `${quadra} / ${lote}` : ''; // Endereço (múltiplas variantes) const endereco = props.Endereco || props.endereco || props.ENDERECO || props.Logradouro || props.logradouro || props.LOGRADOURO || props.rua || props.Rua || props.RUA || props.end_completo || props.endereco_completo || ''; if (enderecoEl) enderecoEl.value = endereco; // Área do terreno - BUSCA EXTENSIVA em TODOS os campos possíveis let area = null; // Lista de campos possíveis para área (ordem de prioridade) const camposArea = [ 'Area', 'area', 'AREA', 'Area_m2', 'area_m2', 'AREA_M2', 'areaterreno', 'AreaTerreno', 'area_terreno', 'AREATERRENO', 'Área', 'área', 'ar_lote', 'area_lote', 'arealote', 'm2', 'M2', 'metros', 'metragem', 'shape_area', 'Shape_Area', 'SHAPE_AREA', 'st_area', 'ST_Area', 'ST_AREA', 'area_geom', 'area_geometria', 'area_total', 'areaTotal', 'superficie', 'Superficie', 'SUPERFICIE', 'sup', 'areat', 'area_t', 'ar_terreno', 'arealot', 'area_lot', 'Area_Lote', 'area_calc', 'areacalc', 'areacalculada', 'area_m', 'areametros', 'area_metros', 'aream2' ]; // Tentar encontrar valor de área for (const campo of camposArea) { if (props[campo] !== undefined && props[campo] !== null && props[campo] !== '') { area = props[campo]; console.log(`📏 Área encontrada em "${campo}":`, area); break; } } // Se não encontrou, tentar calcular da geometria if (!area && props._feature) { try { const feature = props._feature; if (feature.feature && feature.feature.geometry) { area = calcularAreaGeometria(feature.feature.geometry); console.log('📏 Área calculada da geometria:', area); } else if (feature.getBounds) { // Aproximação usando bounds const bounds = feature.getBounds(); const ne = bounds.getNorthEast(); const sw = bounds.getSouthWest(); // Calcular área aproximada em m² const latDiff = Math.abs(ne.lat - sw.lat); const lngDiff = Math.abs(ne.lng - sw.lng); const latMeters = latDiff * 111320; // metros por grau de latitude const lngMeters = lngDiff * 111320 * Math.cos((ne.lat + sw.lat) / 2 * Math.PI / 180); area = latMeters * lngMeters * 0.7; // Fator de correção para forma irregular console.log('📏 Área aproximada dos bounds:', area); } } catch (e) { console.warn('⚠️ Não foi possível calcular área da geometria:', e); } } // Preencher o campo de área if (areaEl) { if (area) { const areaNum = parseFloat(String(area).replace(',', '.').replace(/[^\d.]/g, '')); if (!isNaN(areaNum) && areaNum > 0) { areaEl.value = `${areaNum.toFixed(2)} m²`; } else { areaEl.value = String(area); } } else { areaEl.value = ''; console.warn('⚠️ Área não encontrada nos atributos'); // Mostrar alerta showError('⚠️ Área do terreno não encontrada. Preencha manualmente.'); } } // Proprietário (múltiplas variantes) const proprietario = props.Proprietario || props.proprietario || props.PROPRIETARIO || props.Nome || props.nome || props.NOME || props.nm_proprie || props.nome_proprietario || props.NomeProprietario || props.titular || props.Titular || props.TITULAR || ''; if (proprietarioEl) proprietarioEl.value = proprietario; // CPF do proprietário const cpfProprietario = props.CPF || props.cpf || props.Cpf || props.cpf_propri || props.cpf_proprietario || props.CPF_PROPRIETARIO || props.documento || props.doc || ''; // CNPJ (se tiver no lote) const cnpjLote = props.CNPJ || props.cnpj || props.Cnpj || props.cnpj_empresa || props.CNPJ_EMPRESA || ''; // Email do proprietário const emailProprietario = props.Email || props.email || props.EMAIL || props.email_proprietario || props.e_mail || ''; // Telefone do proprietário const telefoneProprietario = props.Telefone || props.telefone || props.TELEFONE || props.tel || props.Tel || props.TEL || props.fone || props.Fone || props.FONE || props.celular || props.Celular || props.CELULAR || ''; console.log('👤 Dados do proprietário:', { nome: proprietario, cpf: cpfProprietario, cnpj: cnpjLote, email: emailProprietario, tel: telefoneProprietario }); // Preencher campos da Etapa 2 automaticamente const nomeReqEl = document.getElementById('alvaraNomeRequerente'); const cpfEl = document.getElementById('alvaraCpf'); const cnpjEl = document.getElementById('alvaraCnpj'); const emailEl = document.getElementById('alvaraEmail'); const telefoneEl = document.getElementById('alvaraTelefone'); if (nomeReqEl && proprietario) nomeReqEl.value = proprietario; if (cpfEl && cpfProprietario) cpfEl.value = cpfProprietario; if (emailEl && emailProprietario) emailEl.value = emailProprietario; if (telefoneEl && telefoneProprietario) telefoneEl.value = telefoneProprietario; // Se tem CNPJ no lote, guardar para uso quando trocar para PJ if (cnpjLote) { window._alvaraCnpjDoLote = cnpjLote; } // Mostrar campos e status document.getElementById('alvaraImovelCampos').style.display = 'block'; const status = document.getElementById('alvaraLoteStatus'); if (status) { status.style.display = 'block'; status.style.background = '#dcfce7'; status.style.border = '1px solid #86efac'; status.style.color = '#166534'; status.innerHTML = '✅ Imóvel selecionado com sucesso!'; } } // Calcular área de uma geometria GeoJSON function calcularAreaGeometria(geom) { if (!geom || !geom.type) return null; try { if (geom.type === 'Polygon' && geom.coordinates) { return calcularAreaPoligono(geom.coordinates[0]); } else if (geom.type === 'MultiPolygon' && geom.coordinates) { let areaTotal = 0; for (const poly of geom.coordinates) { areaTotal += calcularAreaPoligono(poly[0]); } return areaTotal; } } catch (e) { console.warn('Erro ao calcular área:', e); } return null; } // Calcular área de um polígono usando fórmula de Shoelace adaptada para coordenadas geográficas function calcularAreaPoligono(coords) { if (!coords || coords.length < 3) return 0; // Converter para metros (aproximação) const toMeters = (lng, lat) => ({ x: lng * 111320 * Math.cos(lat * Math.PI / 180), y: lat * 111320 }); // Calcular centroide para referência let sumLat = 0, sumLng = 0; for (const c of coords) { sumLng += c[0]; sumLat += c[1]; } const centerLat = sumLat / coords.length; // Converter coordenadas para metros const mCoords = coords.map(c => toMeters(c[0], c[1])); // Fórmula de Shoelace para área let area = 0; for (let i = 0; i < mCoords.length - 1; i++) { area += mCoords[i].x * mCoords[i + 1].y; area -= mCoords[i + 1].x * mCoords[i].y; } return Math.abs(area / 2); } // Avançar/Voltar etapas function avancarEtapaAlvara(etapa) { // Ocultar todas as etapas for (let i = 1; i <= 3; i++) { const el = document.getElementById(`alvaraEtapa${i}`); if (el) el.style.display = i === etapa ? 'block' : 'none'; } // Atualizar barra de progresso for (let i = 1; i <= 3; i++) { const step = document.getElementById(`alvaraStep${i}`); if (step) { step.classList.remove('active', 'completed'); if (i < etapa) step.classList.add('completed'); if (i === etapa) step.classList.add('active'); } } // Montar resumo na etapa 3 if (etapa === 3) montarResumoAlvara(); } function voltarEtapaAlvara(etapa) { avancarEtapaAlvara(etapa); } // Toggle campos PF/PJ function toggleCamposPessoaAlvara() { const tipo = document.getElementById('alvaraTipoPessoa')?.value; const isPJ = tipo === 'juridica'; document.getElementById('alvaraCampoCpf').style.display = isPJ ? 'none' : 'block'; document.getElementById('alvaraCampoCnpj').style.display = isPJ ? 'block' : 'none'; document.getElementById('alvaraCampoFantasia').style.display = isPJ ? 'block' : 'none'; document.getElementById('alvaraCampoAtividade').style.display = isPJ ? 'block' : 'none'; const labelNome = document.getElementById('alvaraLabelNome'); if (labelNome) labelNome.textContent = isPJ ? 'Razão Social *' : 'Nome Completo *'; // Se trocou para PJ e tem CNPJ do lote, preencher e buscar dados if (isPJ && window._alvaraCnpjDoLote) { const cnpjEl = document.getElementById('alvaraCnpj'); if (cnpjEl) { cnpjEl.value = window._alvaraCnpjDoLote; // Buscar dados automaticamente setTimeout(() => buscarCnpjReceita(), 300); } } } // Toggle campos específicos por tipo de alvará function toggleCamposEspecificosAlvara() { const tipo = document.getElementById('alvaraTipo')?.value; const camposResponsavel = document.getElementById('alvaraCamposResponsavelTecnico'); const camposEspecificos = document.getElementById('alvaraCamposEspecificos'); const gridEspecificos = document.getElementById('alvaraCamposEspecificosGrid'); // Tipos que exigem responsável técnico (ART/RRT) const tiposObra = ['construcao', 'reforma', 'ampliacao', 'demolicao', 'habite_se', 'muro', 'regularizacao']; const precisaResponsavel = tiposObra.includes(tipo); if (camposResponsavel) { camposResponsavel.style.display = precisaResponsavel ? 'block' : 'none'; } // Campos específicos por tipo let camposHtml = ''; if (tipo === 'construcao' || tipo === 'reforma' || tipo === 'ampliacao') { camposHtml = `
`; } else if (tipo === 'demolicao') { camposHtml = `
`; } else if (tipo === 'eventos') { camposHtml = `
`; } else if (tipo === 'publicidade') { camposHtml = `
`; } else if (tipo === 'habite_se') { camposHtml = `
`; } if (gridEspecificos && camposEspecificos) { if (camposHtml) { gridEspecificos.innerHTML = camposHtml; camposEspecificos.style.display = 'block'; } else { camposEspecificos.style.display = 'none'; } } // Atualizar taxas baseado no tipo atualizarValorTaxaAlvara(tipo); } // Atualizar valor da taxa na interface function atualizarValorTaxaAlvara(tipo) { const taxas = { 'funcionamento': 150.00, 'funcionamento_provisorio': 80.00, 'localizacao': 60.00, 'ambulante': 50.00, 'construcao': 250.00, 'reforma': 180.00, 'ampliacao': 200.00, 'demolicao': 120.00, 'habite_se': 350.00, 'muro': 80.00, 'regularizacao': 400.00, 'sanitario': 180.00, 'ambiental': 200.00, 'bombeiros': 150.00, 'publicidade': 100.00, 'eventos': 300.00, 'certidao_uso': 50.00, 'parcelamento': 500.00, 'desmembramento': 300.00, 'remembramento': 300.00 }; const taxa = taxas[tipo] || 150.00; // Mostrar prévia da taxa (se existir elemento) const taxaPreviewEl = document.getElementById('alvaraTaxaPreview'); if (taxaPreviewEl) { taxaPreviewEl.textContent = `R$ ${taxa.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`; } } // Buscar CNPJ na Receita Federal async function buscarCnpjReceita() { const cnpjEl = document.getElementById('alvaraCnpj'); const cnpj = cnpjEl?.value?.replace(/\D/g, ''); const statusEl = document.getElementById('alvaraCnpjStatus'); if (!cnpj || cnpj.length !== 14) { if (statusEl) statusEl.innerHTML = '⚠️ Digite um CNPJ válido com 14 dígitos'; return; } if (statusEl) { statusEl.innerHTML = '🔄 Consultando Receita Federal...'; statusEl.style.background = '#e0f2fe'; statusEl.style.padding = '10px'; statusEl.style.borderRadius = '6px'; } try { // Tentar BrasilAPI primeiro (mais confiável) let data = null; try { const response = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${cnpj}`); if (response.ok) { data = await response.json(); } } catch (e) { console.warn('BrasilAPI falhou, tentando backend...'); } // Fallback para backend if (!data) { try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/consultar-cnpj?cnpj=${cnpj}`); if (response.ok) { const result = await response.json(); data = result.dados || result; } } catch (e) { console.warn('Backend também falhou'); } } if (data) { console.log('📋 Dados CNPJ recebidos:', data); // Preencher campos automaticamente - campos da BrasilAPI const nomeEl = document.getElementById('alvaraNomeRequerente'); const fantasiaEl = document.getElementById('alvaraNomeFantasia'); const atividadeEl = document.getElementById('alvaraAtividade'); const telefoneEl = document.getElementById('alvaraTelefone'); const emailEl = document.getElementById('alvaraEmail'); // Razão Social if (nomeEl) { nomeEl.value = data.razao_social || data.nome_empresarial || ''; } // Nome Fantasia if (fantasiaEl) { fantasiaEl.value = data.nome_fantasia || ''; } // Atividade Principal (CNAE) if (atividadeEl) { // BrasilAPI retorna cnae_fiscal_descricao const atividade = data.cnae_fiscal_descricao || (data.cnaes_secundarios && data.cnaes_secundarios[0]?.descricao) || data.atividade_principal || ''; atividadeEl.value = atividade; } // Telefone - BrasilAPI retorna ddd_telefone_1 como string "DDNNNNNNNNN" if (telefoneEl) { let telefone = ''; if (data.ddd_telefone_1) { // Formatar telefone: "1133445566" -> "(11) 3344-5566" const tel = String(data.ddd_telefone_1).replace(/\D/g, ''); if (tel.length >= 10) { const ddd = tel.substring(0, 2); const parte1 = tel.substring(2, tel.length - 4); const parte2 = tel.substring(tel.length - 4); telefone = `(${ddd}) ${parte1}-${parte2}`; } else { telefone = tel; } } else if (data.telefone) { telefone = data.telefone; } else if (data.ddd_telefone_2) { const tel = String(data.ddd_telefone_2).replace(/\D/g, ''); if (tel.length >= 10) { const ddd = tel.substring(0, 2); const parte1 = tel.substring(2, tel.length - 4); const parte2 = tel.substring(tel.length - 4); telefone = `(${ddd}) ${parte1}-${parte2}`; } } telefoneEl.value = telefone; } // Email if (emailEl) { emailEl.value = data.email || ''; } // Mostrar informações completas no status const situacao = data.descricao_situacao_cadastral || data.situacao || 'Não informada'; const situacaoAtiva = situacao.toLowerCase().includes('ativa'); const cnaeCode = data.cnae_fiscal || data.cnae || ''; const abertura = data.data_inicio_atividade || data.data_abertura || ''; const porte = data.porte || data.descricao_porte || ''; const natureza = data.natureza_juridica || data.descricao_natureza_juridica || ''; // Endereço da empresa const endEmpresa = [ data.descricao_tipo_logradouro, data.logradouro, data.numero, data.complemento, data.bairro ].filter(Boolean).join(' '); const cidadeUf = [data.municipio, data.uf].filter(Boolean).join('/'); if (statusEl) { statusEl.innerHTML = `
${data.razao_social || 'Empresa encontrada'} ${data.nome_fantasia ? `
"${data.nome_fantasia}"` : ''}

📍 Endereço:
${endEmpresa || 'N/A'}
🏙️ Cidade:
${cidadeUf || 'N/A'}
📋 CNAE:
${cnaeCode} - ${data.cnae_fiscal_descricao || ''}
📊 Situação:
${situacao}
📅 Abertura:
${abertura || 'N/A'}
🏢 Porte:
${porte || 'N/A'}
📞 Telefone:
${telefoneEl?.value || 'N/A'}
📧 Email:
${data.email || 'N/A'}
`; statusEl.style.background = situacaoAtiva ? '#dcfce7' : '#fef3c7'; statusEl.style.padding = '12px'; statusEl.style.borderRadius = '8px'; statusEl.style.border = `1px solid ${situacaoAtiva ? '#86efac' : '#fcd34d'}`; } // Guardar dados completos para impressão window._alvaraCnpjData = data; } else { if (statusEl) { statusEl.innerHTML = '⚠️ CNPJ não encontrado na Receita Federal. Verifique se está correto.'; statusEl.style.background = '#fef3c7'; statusEl.style.padding = '10px'; statusEl.style.borderRadius = '6px'; } } } catch (error) { console.error('Erro ao buscar CNPJ:', error); if (statusEl) { statusEl.innerHTML = '❌ Erro na consulta. Verifique sua conexão com a internet.'; statusEl.style.background = '#fee2e2'; statusEl.style.padding = '10px'; statusEl.style.borderRadius = '6px'; } } } // Imprimir Cartão CNPJ function imprimirCartaoCnpj() { const data = window._alvaraCnpjData; if (!data) { showError('❌ Consulte um CNPJ primeiro'); return; } // Formatar CNPJ const cnpj = data.cnpj || document.getElementById('alvaraCnpj')?.value || ''; const cnpjFormatado = cnpj.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '$1.$2.$3/$4-$5'); // Endereço completo const endereco = [ data.descricao_tipo_logradouro, data.logradouro, data.numero, data.complemento ].filter(Boolean).join(' '); const html = ` Cartão CNPJ - ${cnpjFormatado}

REPÚBLICA FEDERATIVA DO BRASIL

CADASTRO NACIONAL DA PESSOA JURÍDICA

${cnpjFormatado}
NOME EMPRESARIAL: ${data.razao_social || ''}
NOME FANTASIA: ${data.nome_fantasia || '**********'}
LOGRADOURO: ${endereco || ''}
BAIRRO/DISTRITO: ${data.bairro || ''}
CEP: ${data.cep || ''}
MUNICÍPIO: ${data.municipio || ''}
UF: ${data.uf || ''}
TELEFONE: ${data.ddd_telefone_1 || ''}
EMAIL: ${data.email || ''}
NATUREZA JURÍDICA: ${data.natureza_juridica || ''}
ATIVIDADE PRINCIPAL: ${data.cnae_fiscal || ''} - ${data.cnae_fiscal_descricao || ''}
DATA DE ABERTURA: ${data.data_inicio_atividade || ''}
SITUAÇÃO CADASTRAL: ${data.descricao_situacao_cadastral || ''}
DATA SITUAÇÃO CADASTRAL: ${data.data_situacao_cadastral || ''}
PORTE: ${data.porte || data.descricao_porte || ''}
CAPITAL SOCIAL: R$ ${(data.capital_social || 0).toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
`; // Abrir em nova janela const win = window.open('', '_blank', 'width=800,height=900'); win.document.write(html); win.document.close(); } // Montar resumo antes de salvar function montarResumoAlvara() { const resumoEl = document.getElementById('alvaraResumo'); if (!resumoEl) return; const tipo = document.getElementById('alvaraTipo')?.selectedOptions[0]?.text || ''; const validade = document.getElementById('alvaraValidade')?.value || '12'; const inscricao = document.getElementById('alvaraInscricao')?.value || ''; const endereco = document.getElementById('alvaraEndereco')?.value || ''; const nome = document.getElementById('alvaraNomeRequerente')?.value || ''; const tipoPessoa = document.getElementById('alvaraTipoPessoa')?.value || 'fisica'; const doc = tipoPessoa === 'juridica' ? document.getElementById('alvaraCnpj')?.value : document.getElementById('alvaraCpf')?.value; const dataEmissao = new Date(); const dataVencimento = new Date(); dataVencimento.setMonth(dataVencimento.getMonth() + parseInt(validade)); resumoEl.innerHTML = `
📋 Tipo:

${tipo}

📅 Validade:

${validade} meses (até ${dataVencimento.toLocaleDateString('pt-BR')})

🏠 Inscrição:

${inscricao}

📍 Endereço:

${endereco}

👤 Requerente:

${nome}

📄 ${tipoPessoa === 'juridica' ? 'CNPJ' : 'CPF'}:

${doc || 'Não informado'}

`; } // Salvar novo alvará async function salvarNovoAlvara() { console.log('💾 Iniciando salvamento de alvará...'); // Validar campos obrigatórios const tipo = document.getElementById('alvaraTipo')?.value; const inscricao = document.getElementById('alvaraInscricao')?.value; const nome = document.getElementById('alvaraNomeRequerente')?.value; const tipoPessoa = document.getElementById('alvaraTipoPessoa')?.value; if (!tipo) { showError('❌ Selecione o tipo de alvará'); avancarEtapaAlvara(1); return; } if (!inscricao) { showError('❌ Selecione um imóvel primeiro'); avancarEtapaAlvara(1); return; } if (!nome) { showError('❌ Informe o nome do requerente'); avancarEtapaAlvara(2); return; } // Validar CPF/CNPJ const cpf = document.getElementById('alvaraCpf')?.value?.replace(/\D/g, ''); const cnpj = document.getElementById('alvaraCnpj')?.value?.replace(/\D/g, ''); if (tipoPessoa === 'fisica' && (!cpf || cpf.length !== 11)) { showError('❌ Informe um CPF válido (11 dígitos)'); avancarEtapaAlvara(2); return; } if (tipoPessoa === 'juridica' && (!cnpj || cnpj.length !== 14)) { showError('❌ Informe um CNPJ válido (14 dígitos)'); avancarEtapaAlvara(2); return; } // Mostrar loading const btnSalvar = document.querySelector('#alvaraEtapa3 .alvara-btn-success'); const btnOriginalHtml = btnSalvar?.innerHTML; if (btnSalvar) { btnSalvar.innerHTML = ' Processando...'; btnSalvar.disabled = true; } const validade = parseInt(document.getElementById('alvaraValidade')?.value || '12'); const dataEmissao = new Date(); const dataVencimento = new Date(); dataVencimento.setMonth(dataVencimento.getMonth() + validade); // Verificar se precisa de responsável técnico const tiposObra = ['construcao', 'reforma', 'ampliacao', 'demolicao', 'habite_se', 'muro', 'regularizacao']; const precisaResponsavel = tiposObra.includes(tipo); const dados = { tipo: tipo, inscricao: inscricao, quadra_lote: document.getElementById('alvaraQuadraLote')?.value, endereco: document.getElementById('alvaraEndereco')?.value, area: document.getElementById('alvaraArea')?.value?.replace(/[^\d.,]/g, ''), tipo_pessoa: tipoPessoa, cpf: tipoPessoa === 'fisica' ? cpf : '', cnpj: tipoPessoa === 'juridica' ? cnpj : '', nome: nome, nome_fantasia: document.getElementById('alvaraNomeFantasia')?.value || '', atividade: document.getElementById('alvaraAtividade')?.value || '', telefone: document.getElementById('alvaraTelefone')?.value || '', email: document.getElementById('alvaraEmail')?.value || '', observacoes: document.getElementById('alvaraObs')?.value || '', data_emissao: dataEmissao.toISOString().split('T')[0], data_vencimento: dataVencimento.toISOString().split('T')[0], validade_meses: validade, status: 'vigente' }; // Adicionar dados do responsável técnico se necessário if (precisaResponsavel) { dados.responsavel_tecnico = { nome: document.getElementById('alvaraResponsavelNome')?.value || '', crea_cau: document.getElementById('alvaraResponsavelCrea')?.value || '', art_rrt: document.getElementById('alvaraResponsavelArt')?.value || '', telefone: document.getElementById('alvaraResponsavelTelefone')?.value || '' }; } // Adicionar dados específicos por tipo if (tipo === 'construcao' || tipo === 'reforma' || tipo === 'ampliacao') { dados.dados_construcao = { area_construir: document.getElementById('alvaraAreaConstruir')?.value || '', pavimentos: document.getElementById('alvaraPavimentos')?.value || '1', uso: document.getElementById('alvaraUsoConstrucao')?.value || '', num_projeto: document.getElementById('alvaraNumProjeto')?.value || '' }; } else if (tipo === 'demolicao') { dados.dados_demolicao = { area_demolir: document.getElementById('alvaraAreaDemolir')?.value || '', tipo_demolicao: document.getElementById('alvaraTipoDemolicao')?.value || '', motivo: document.getElementById('alvaraMotivoDemolicao')?.value || '' }; } else if (tipo === 'eventos') { dados.dados_evento = { data_evento: document.getElementById('alvaraDataEvento')?.value || '', tipo_evento: document.getElementById('alvaraTipoEvento')?.value || '', capacidade: document.getElementById('alvaraCapacidadeEvento')?.value || '', horario: document.getElementById('alvaraHorarioEvento')?.value || '' }; } else if (tipo === 'publicidade') { dados.dados_publicidade = { tipo_publicidade: document.getElementById('alvaraTipoPublicidade')?.value || '', dimensoes: document.getElementById('alvaraDimensoesPublicidade')?.value || '', quantidade: document.getElementById('alvaraQtdPublicidade')?.value || '1' }; } else if (tipo === 'habite_se') { dados.dados_habitese = { num_alvara_construcao: document.getElementById('alvaraNumAlvaraConstrucao')?.value || '', area_total_construida: document.getElementById('alvaraAreaTotalConstruida')?.value || '', data_conclusao: document.getElementById('alvaraDataConclusao')?.value || '' }; } console.log('📋 Dados do alvará:', dados); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/criar`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token') || ''}` }, body: JSON.stringify(dados) }); const result = await response.json(); console.log('📋 Resposta do servidor:', result); if (response.ok && (result.success || result.id || result.numero)) { const numeroAlvara = result.numero || result.id || 'NOVO'; const taxaValor = result.taxa || result.valor || calcularTaxaAlvara(tipo); // Mostrar modal de sucesso com opções de pagamento mostrarAlvaraCriado(numeroAlvara, dados, taxaValor, result); } else { showError(`❌ ${result.error || result.message || 'Erro ao criar alvará'}`); } } catch (error) { console.error('❌ Erro ao salvar alvará:', error); showError('❌ Erro de conexão com o servidor. Verifique se o backend está rodando.'); } finally { // Restaurar botão if (btnSalvar) { btnSalvar.innerHTML = btnOriginalHtml || '✅ Salvar e Emitir Alvará'; btnSalvar.disabled = false; } } } // Calcular taxa do alvará baseado no tipo function calcularTaxaAlvara(tipo) { const taxas = { 'funcionamento': 150.00, 'funcionamento_provisorio': 80.00, 'localizacao': 60.00, 'ambulante': 50.00, 'construcao': 250.00, 'reforma': 180.00, 'ampliacao': 200.00, 'demolicao': 120.00, 'habite_se': 350.00, 'muro': 80.00, 'regularizacao': 400.00, 'sanitario': 180.00, 'ambiental': 200.00, 'bombeiros': 150.00, 'publicidade': 100.00, 'eventos': 300.00, 'certidao_uso': 50.00, 'parcelamento': 500.00, 'desmembramento': 300.00, 'remembramento': 300.00 }; return taxas[tipo] || 150.00; } // Mostrar modal de alvará criado com sucesso function mostrarAlvaraCriado(numero, dados, taxa, result) { const tipoNome = { 'funcionamento': 'Alvará de Funcionamento', 'funcionamento_provisorio': 'Alvará Provisório', 'localizacao': 'Alvará de Localização', 'ambulante': 'Licença para Ambulante', 'construcao': 'Alvará de Construção', 'reforma': 'Alvará de Reforma', 'ampliacao': 'Alvará de Ampliação', 'demolicao': 'Alvará de Demolição', 'habite_se': 'Habite-se', 'muro': 'Alvará p/ Construção de Muro', 'regularizacao': 'Regularização de Edificação', 'sanitario': 'Licença Sanitária', 'ambiental': 'Licença Ambiental', 'bombeiros': 'AVCB (Corpo de Bombeiros)', 'publicidade': 'Alvará de Publicidade', 'eventos': 'Alvará de Eventos', 'certidao_uso': 'Certidão de Uso do Solo', 'parcelamento': 'Aprovação de Parcelamento', 'desmembramento': 'Desmembramento', 'remembramento': 'Remembramento' }; const resumoEl = document.getElementById('alvaraResumo'); if (resumoEl) { resumoEl.innerHTML = `

Alvará Criado com Sucesso!

Nº ${numero}

${tipoNome[dados.tipo] || dados.tipo}

💰 Taxa de Licenciamento

R$ ${taxa.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}

Vencimento: ${new Date(Date.now() + 30*24*60*60*1000).toLocaleDateString('pt-BR')}

📄 Opções de Pagamento e Impressão

`; } // Guardar dados para uso nas funções window._alvaraAtual = { numero, dados, taxa, result }; } // Gerar boleto do alvará async function gerarBoletoAlvara(numero) { showError('🔄 Gerando boleto...'); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/${numero}/boleto`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); if (response.ok) { const blob = await response.blob(); const url = URL.createObjectURL(blob); window.open(url, '_blank'); showError('✅ Boleto gerado! Abra a nova aba.'); } else { // Gerar boleto local se backend não suportar gerarBoletoLocal(); } } catch (e) { console.error('Erro ao gerar boleto:', e); gerarBoletoLocal(); } } // Gerar boleto local (fallback) function gerarBoletoLocal() { const alvara = window._alvaraAtual; if (!alvara) return; const html = ` Boleto - Alvará ${alvara.numero}
PREFEITURA MUNICIPAL Taxa de Licenciamento
23793.38128 60000.000001 00000.${alvara.numero}0 1 ${Math.floor(Date.now()/1000).toString().slice(-10)}
PREFEITURA MUNICIPAL
${new Date(Date.now() + 30*24*60*60*1000).toLocaleDateString('pt-BR')}
${alvara.dados.nome}
${alvara.dados.cpf || alvara.dados.cnpj}
Alvará ${alvara.numero} - ${alvara.dados.tipo?.toUpperCase()}
R$ ${alvara.taxa.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}

Inscrição Imobiliária: ${alvara.dados.inscricao}

Endereço: ${alvara.dados.endereco}

`; const win = window.open('', '_blank'); win.document.write(html); win.document.close(); showError('✅ Boleto gerado!'); } // Gerar PIX do alvará async function gerarPixAlvara(numero, valor) { showError('📱 Gerando PIX...'); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/${numero}/pix`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); if (response.ok) { const data = await response.json(); mostrarQrCodePix(data.qrcode || data.pix_copia_cola, valor); } else { // PIX local mostrarQrCodePix(gerarPixCopiaColaLocal(valor), valor); } } catch (e) { mostrarQrCodePix(gerarPixCopiaColaLocal(valor), valor); } } // Gerar PIX copia e cola local function gerarPixCopiaColaLocal(valor) { // PIX copia e cola simplificado (seria necessário integração real com PSP) const chave = 'prefeitura@cidade.gov.br'; // Chave PIX da prefeitura const nome = 'PREFEITURA MUNICIPAL'; const cidade = 'CIDADE'; return `00020126580014BR.GOV.BCB.PIX0136${chave}5204000053039865406${valor.toFixed(2).replace('.', '')}5802BR5913${nome}6008${cidade}62070503***6304`; } // Mostrar QR Code do PIX function mostrarQrCodePix(pixCopiaCola, valor) { const alvara = window._alvaraAtual; const html = `

📱 Pagamento via PIX

R$ ${valor.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}

Alvará Nº ${alvara?.numero || ''}

PIX Copia e Cola:

${pixCopiaCola}
`; const resumoEl = document.getElementById('alvaraResumo'); if (resumoEl) { resumoEl.innerHTML += html; } // Gerar QR Code se a biblioteca estiver disponível if (typeof QRCode !== 'undefined') { setTimeout(() => { const container = document.getElementById('qrcodeContainer'); if (container) { new QRCode(container, { text: pixCopiaCola, width: 200, height: 200 }); } }, 100); } showError('✅ PIX gerado! Escaneie o QR Code ou copie o código.'); } // Imprimir alvará async function imprimirAlvara(numero) { showError('🖨️ Preparando impressão...'); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/${numero}/pdf`); if (response.ok) { const blob = await response.blob(); const url = URL.createObjectURL(blob); const win = window.open(url, '_blank'); if (win) win.print(); } else { gerarAlvaraPdfLocal(); } } catch (e) { gerarAlvaraPdfLocal(); } } // Baixar PDF do alvará async function baixarPdfAlvara(numero) { showError('📥 Gerando PDF...'); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/${numero}/pdf`); if (response.ok) { const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `alvara_${numero}.pdf`; a.click(); showError('✅ PDF baixado!'); } else { gerarAlvaraPdfLocal(); } } catch (e) { gerarAlvaraPdfLocal(); } } // Gerar alvará PDF local (fallback) function gerarAlvaraPdfLocal() { const alvara = window._alvaraAtual; if (!alvara) { showError('❌ Dados do alvará não disponíveis'); return; } const tipoNome = { 'funcionamento': 'ALVARÁ DE FUNCIONAMENTO', 'construcao': 'ALVARÁ DE CONSTRUÇÃO', 'sanitario': 'LICENÇA SANITÁRIA', 'ambiental': 'LICENÇA AMBIENTAL', 'demolicao': 'ALVARÁ DE DEMOLIÇÃO', 'publicidade': 'ALVARÁ DE PUBLICIDADE', 'eventos': 'ALVARÁ DE EVENTOS', 'habite_se': 'HABITE-SE', 'localizacao': 'ALVARÁ DE LOCALIZAÇÃO' }; const html = ` Alvará ${alvara.numero}
🏛️
PREFEITURA MUNICIPAL
Secretaria de Planejamento e Desenvolvimento Urbano
${tipoNome[alvara.dados.tipo] || alvara.dados.tipo}
${alvara.numero}

📋 Dados do Requerente

${alvara.dados.tipo_pessoa === 'juridica' ? 'Razão Social:' : 'Nome:'} ${alvara.dados.nome}
${alvara.dados.nome_fantasia ? `
Nome Fantasia: ${alvara.dados.nome_fantasia}
` : ''}
${alvara.dados.tipo_pessoa === 'juridica' ? 'CNPJ:' : 'CPF:'} ${alvara.dados.cnpj || alvara.dados.cpf}
${alvara.dados.atividade ? `
Atividade: ${alvara.dados.atividade}
` : ''}

🏠 Dados do Imóvel

Inscrição Imobiliária: ${alvara.dados.inscricao}
Quadra/Lote: ${alvara.dados.quadra_lote}
Endereço: ${alvara.dados.endereco}
Área: ${alvara.dados.area} m²
📅 Válido até ${new Date(alvara.dados.data_vencimento).toLocaleDateString('pt-BR')}
(${alvara.dados.validade_meses} meses)
${alvara.dados.observacoes ? `

📝 Observações

${alvara.dados.observacoes}

` : ''}

Secretário(a) de Planejamento

`; const win = window.open('', '_blank'); win.document.write(html); win.document.close(); showError('✅ Alvará gerado!'); } // Finalizar e voltar ao dashboard function finalizarNovoAlvara() { // Limpar formulário document.getElementById('formNovoAlvara')?.reset(); document.getElementById('alvaraImovelCampos').style.display = 'none'; const status = document.getElementById('alvaraLoteStatus'); if (status) status.style.display = 'none'; // Voltar ao início avancarEtapaAlvara(1); // Atualizar dashboard e mudar aba carregarDashboardAlvaras(); switchAlvaraTab('dashboard'); showError('✅ Alvará finalizado com sucesso!'); } // Carregar Dashboard async function carregarDashboardAlvaras() { try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/estatisticas`); if (response.ok) { const data = await response.json(); document.getElementById('alvaraTotalCount').textContent = data.total || 0; document.getElementById('alvaraVigentesCount').textContent = data.vigentes || 0; document.getElementById('alvaraVencendoCount').textContent = data.vencendo || 0; document.getElementById('alvaraVencidosCount').textContent = data.vencidos || 0; // Atualizar gráfico if (data.por_tipo) { atualizarGraficoAlvaras(data.por_tipo); } } } catch (error) { console.log('Dashboard em modo offline'); } } // Atualizar gráfico de tipos function atualizarGraficoAlvaras(porTipo) { const ctx = document.getElementById('alvaraChartTipos'); if (!ctx) return; if (alvaraChartTipos) { alvaraChartTipos.destroy(); } const labels = Object.keys(porTipo); const values = Object.values(porTipo); const cores = ['#22c55e', '#3b82f6', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899']; alvaraChartTipos = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ data: values, backgroundColor: cores.slice(0, labels.length), borderWidth: 2, borderColor: '#fff' }] }, options: { responsive: true, plugins: { legend: { position: 'right' } } } }); } // Buscar alvarás async function buscarAlvaras() { const tipo = document.getElementById('alvaraConsultaTipoBusca')?.value || 'nome'; const termo = document.getElementById('alvaraConsultaInput')?.value || ''; const tipoAlvara = document.getElementById('alvaraConsultaTipo')?.value || ''; const status = document.getElementById('alvaraConsultaStatus')?.value || ''; const params = new URLSearchParams(); if (termo) params.append(tipo, termo); if (tipoAlvara) params.append('tipo', tipoAlvara); if (status) params.append('status', status); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/listar?${params}`); if (response.ok) { const alvaras = await response.json(); renderizarListaAlvaras(alvaras); } } catch (error) { console.error('Erro ao buscar alvarás:', error); } } // Renderizar lista de alvarás function renderizarListaAlvaras(alvaras) { const container = document.getElementById('alvaraListaContainer'); if (!container) return; if (!alvaras || alvaras.length === 0) { container.innerHTML = `
📭

Nenhum alvará encontrado

`; return; } const tiposIcones = { 'funcionamento': '🏪', 'construcao': '🏗️', 'sanitario': '🏥', 'ambiental': '🌳', 'demolicao': '🔨', 'publicidade': '📢', 'eventos': '🎉', 'habite_se': '🏠' }; container.innerHTML = alvaras.map(a => `
${tiposIcones[a.tipo] || '📋'} ${a.nome || 'Sem nome'}
${a.inscricao || ''} • ${a.endereco || ''}
Vencimento: ${a.data_vencimento ? new Date(a.data_vencimento).toLocaleDateString('pt-BR') : 'N/A'}
${a.status?.toUpperCase() || 'N/A'}
`).join(''); } // Carregar alertas async function carregarAlertasAlvaras() { try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/alertas`); if (response.ok) { const alertas = await response.json(); renderizarAlertasAlvaras(alertas); } } catch (error) { console.log('Alertas em modo offline'); } } // Renderizar alertas function renderizarAlertasAlvaras(alertas) { const container = document.getElementById('alvaraAlertasContainer'); if (!container) return; if (!alertas || alertas.length === 0) { container.innerHTML = `

Nenhum alerta no momento

`; return; } container.innerHTML = alertas.map(a => `
${a.nome || 'Sem nome'}
${a.endereco || ''}
${a.status?.toUpperCase()}
${a.dias_para_vencer > 0 ? `Vence em ${a.dias_para_vencer} dias` : `Vencido há ${Math.abs(a.dias_para_vencer)} dias`}
`).join(''); } // Destacar alvarás vencendo no mapa async function destacarAlvarasVencendoNoMapa() { try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/alertas`); if (response.ok) { const alertas = await response.json(); // Minimizar modal closeAlvaraModal(); // Destacar cada lote com alerta showError(`🗺️ Destacando ${alertas.length} alvarás com alertas no mapa...`); // TODO: Implementar destaque visual no mapa baseado nas inscrições console.log('Alvarás para destacar:', alertas); } } catch (error) { console.error('Erro ao destacar alvarás:', error); } } // Exportar relatório async function exportarRelatorioAlvaras() { showError('📄 Gerando relatório de alvarás...'); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/relatorio-fiscal?formato=pdf`); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `relatorio_alvaras_${new Date().toISOString().split('T')[0]}.pdf`; a.click(); window.URL.revokeObjectURL(url); showError('✅ Relatório PDF gerado com sucesso!'); } else { showError('❌ Erro ao gerar relatório'); } } catch (error) { console.error('Erro:', error); showError('❌ Erro de conexão'); } } // Destacar alvarás no mapa async function destacarAlvarasNoMapa() { closeAlvaraModal(); showError('🗺️ Carregando alvarás para destacar no mapa...'); try { // Buscar alvarás com alertas const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/alertas?dias=30`); if (response.ok) { const data = await response.json(); const alertas = data.alertas || []; if (alertas.length === 0) { showError('✅ Nenhum alvará com alerta no momento'); return; } // Criar camada de destaques se não existir if (!window.alvarasDestaqueLayer) { window.alvarasDestaqueLayer = L.layerGroup().addTo(map); } else { window.alvarasDestaqueLayer.clearLayers(); } // Adicionar marcadores para cada alvará alertas.forEach(alerta => { const lat = alerta.latitude || alerta.imovel?.latitude; const lng = alerta.longitude || alerta.imovel?.longitude; if (lat && lng) { const cor = alerta.nivel === 'critico' ? '#F44336' : alerta.nivel === 'urgente' ? '#FF9800' : alerta.nivel === 'atencao' ? '#FFEB3B' : '#4CAF50'; const marker = L.circleMarker([lat, lng], { radius: 15, fillColor: cor, color: '#fff', weight: 3, opacity: 1, fillOpacity: 0.8 }).addTo(window.alvarasDestaqueLayer); marker.bindPopup(` ⚠️ ${alerta.numero || 'Alvará'}
${alerta.tipo_nome || alerta.tipo}
${alerta.dias_restantes} dias para vencer
${alerta.imovel?.endereco || ''} `); } }); showError(`🗺️ ${alertas.length} alvarás destacados no mapa!`); } } catch (error) { console.error('Erro ao destacar alvarás:', error); showError('❌ Erro ao carregar alvarás'); } } // Gerar relatório fiscal async function gerarRelatorioFiscal() { showError('📝 Gerando relatório para fiscalização...'); // Coletar filtros const tipo = document.getElementById('fiscalTipoAlvara')?.value || ''; const status = document.getElementById('fiscalStatus')?.value || ''; const bairro = document.getElementById('fiscalBairro')?.value || ''; try { const params = new URLSearchParams(); if (tipo) params.append('tipo', tipo); if (status) params.append('status', status); if (bairro) params.append('bairro', bairro); params.append('formato', 'pdf'); const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/relatorio-fiscal?${params}`); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `relatorio_fiscal_${new Date().toISOString().split('T')[0]}.pdf`; a.click(); window.URL.revokeObjectURL(url); showError('✅ Relatório fiscal gerado com sucesso!'); } else { showError('❌ Erro ao gerar relatório'); } } catch (error) { console.error('Erro:', error); showError('❌ Erro de conexão'); } } // Exportar Excel fiscal async function exportarRelatorioFiscalExcel() { showError('📊 Exportando para Excel...'); const tipo = document.getElementById('fiscalTipoAlvara')?.value || ''; const status = document.getElementById('fiscalStatus')?.value || ''; const bairro = document.getElementById('fiscalBairro')?.value || ''; try { const params = new URLSearchParams(); if (tipo) params.append('tipo', tipo); if (status) params.append('status', status); if (bairro) params.append('bairro', bairro); params.append('formato', 'excel'); const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/relatorio-fiscal?${params}`); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `relatorio_fiscal_${new Date().toISOString().split('T')[0]}.xlsx`; a.click(); window.URL.revokeObjectURL(url); showError('✅ Excel exportado com sucesso!'); } else { showError('❌ Erro ao exportar Excel'); } } catch (error) { console.error('Erro:', error); showError('❌ Erro de conexão'); } } // Visualizar rota fiscal async function visualizarRotaFiscal() { closeAlvaraModal(); showError('🗺️ Gerando rota de fiscalização...'); const status = document.getElementById('fiscalStatus')?.value || 'vencido'; try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/relatorio-fiscal?status=${status}&formato=json`); if (response.ok) { const data = await response.json(); const itens = data.relatorio?.itens || []; if (itens.length === 0) { showError('Nenhum alvará encontrado para gerar rota'); return; } // Filtrar apenas itens com coordenadas const pontos = itens.filter(i => i.latitude && i.longitude); if (pontos.length === 0) { showError('Nenhum alvará com coordenadas para gerar rota'); return; } // Criar camada de rota se não existir if (!window.rotaFiscalLayer) { window.rotaFiscalLayer = L.layerGroup().addTo(map); } else { window.rotaFiscalLayer.clearLayers(); } // Adicionar marcadores numerados const bounds = L.latLngBounds(); const coordsRota = []; pontos.forEach((ponto, index) => { const latLng = [ponto.latitude, ponto.longitude]; bounds.extend(latLng); coordsRota.push(latLng); // Marcador numerado const marker = L.marker(latLng, { icon: L.divIcon({ className: 'rota-fiscal-marker', html: `
${index + 1}
`, iconSize: [28, 28], iconAnchor: [14, 14] }) }).addTo(window.rotaFiscalLayer); marker.bindPopup(` ${index + 1}. ${ponto.numero || 'Alvará'}
${ponto.tipo || ''}
${ponto.endereco || ''}, ${ponto.numero_imovel || ''}
${ponto.requerente || ''}
${ponto.situacao_vencimento || ''} `); }); // Linha conectando os pontos if (coordsRota.length > 1) { L.polyline(coordsRota, { color: '#E91E63', weight: 3, opacity: 0.7, dashArray: '10, 10' }).addTo(window.rotaFiscalLayer); } // Zoom para a rota map.fitBounds(bounds, { padding: [50, 50] }); showError(`🗺️ Rota com ${pontos.length} pontos gerada!`); } } catch (error) { console.error('Erro:', error); showError('❌ Erro ao gerar rota'); } } // Autocomplete para busca de lotes no Alvarás let alvaraAutocompleteTimeout = null; async function buscarAlvaraAutocomplete() { const input = document.getElementById('alvaraBuscaInput'); const tipoSelect = document.getElementById('alvaraTipoBusca'); const tipo = tipoSelect?.value || 'proprietario'; const termo = input?.value?.trim() || ''; const listEl = document.getElementById('alvaraAutocompleteList'); console.log('🔍 [Alvará] Autocomplete:', { tipo, termo }); if (termo.length < 2) { if (listEl) listEl.classList.remove('show'); return; } clearTimeout(alvaraAutocompleteTimeout); alvaraAutocompleteTimeout = setTimeout(async () => { try { // Mapear tipo de busca para coluna do banco const colunaMap = { 'nome': 'proprietario,nm_proprie,nome,titular', 'proprietario': 'proprietario,nm_proprie,nome,titular', 'inscricao': 'inscricao,inscricao_imobiliaria,cod_imovel,insc_imob', 'cpf': 'cpf,cpf_propri,cpf_proprietario,documento', 'endereco': 'endereco,logradouro,end_completo,rua' }; const colunas = colunaMap[tipo] || tipo; // Buscar em camadas de lotes cadastrados const camadas = ['lotes', 'imoveis', 'cadastro', 'lotes_urbanos', 'propriedades']; let resultados = []; // Tentar via endpoint autocomplete do sistema for (const camada of camadas) { if (resultados.length >= 10) break; for (const coluna of colunas.split(',')) { if (resultados.length >= 10) break; try { const url = `${window.BACKEND_URL || 'http://localhost:5000'}/api/autocomplete_values?layer=${camada}&column=${coluna}&q=${encodeURIComponent(termo)}&limit=10`; const response = await fetch(url); if (response.ok) { const data = await response.json(); console.log(`📋 [Alvará] Autocomplete ${camada}.${coluna}:`, data); if (data.values && data.values.length > 0) { // Converter valores em objetos com estrutura esperada for (const val of data.values) { if (resultados.length >= 10) break; if (!resultados.some(r => r.nome === val || r.inscricao === val)) { resultados.push({ nome: tipo === 'inscricao' ? '' : val, inscricao: tipo === 'inscricao' ? val : '', proprietario: tipo === 'nome' || tipo === 'proprietario' ? val : '', endereco: tipo === 'endereco' ? val : '', _layer: camada, _column: coluna, _value: val }); } } } } } catch (e) { /* continua tentando */ } } } // Se não encontrou via autocomplete, buscar nos dados locais do mapa if (resultados.length === 0) { resultados = buscarLotesNoMapaLocal(tipo, termo); } if (resultados.length > 0) { renderizarAutocompleteAlvara(resultados); } else { if (listEl) { listEl.innerHTML = '
Nenhum resultado encontrado
'; listEl.classList.add('show'); } } } catch (error) { console.error('❌ [Alvará] Erro no autocomplete:', error); } }, 350); } // Buscar lotes diretamente nas camadas carregadas no mapa function buscarLotesNoMapaLocal(tipo, termo) { const resultados = []; const termoLower = termo.toLowerCase(); // Percorrer todas as camadas no mapa if (window.map) { window.map.eachLayer(layer => { if (resultados.length >= 10) return; // Verificar se é uma camada GeoJSON if (layer.feature || (layer.getLayers && layer.getLayers().length > 0)) { const features = layer.getLayers ? layer.getLayers() : [layer]; for (const feature of features) { if (resultados.length >= 10) break; const props = feature.feature?.properties || feature.options?.properties || {}; if (!props || Object.keys(props).length === 0) continue; // Verificar se algum campo bate com o termo let match = false; let campoMatch = ''; // Propriedades para buscar por tipo const propsToSearch = { 'nome': ['proprietario', 'nm_proprie', 'nome', 'titular', 'Proprietario', 'Nome'], 'proprietario': ['proprietario', 'nm_proprie', 'nome', 'titular', 'Proprietario', 'Nome'], 'inscricao': ['inscricao', 'inscricao_imobiliaria', 'cod_imovel', 'Inscricao_imobiliaria', 'insc_imob'], 'cpf': ['cpf', 'cpf_propri', 'CPF', 'documento'], 'endereco': ['endereco', 'logradouro', 'Endereco', 'Logradouro', 'rua', 'Rua'] }; const campos = propsToSearch[tipo] || Object.keys(props); for (const campo of campos) { if (props[campo] && String(props[campo]).toLowerCase().includes(termoLower)) { match = true; campoMatch = campo; break; } } if (match) { // Extrair dados relevantes const quadra = props.Quadra || props.quadra || props.QUADRA || ''; const lote = props.Lote || props.lote || props.LOTE || ''; const nome = props.Proprietario || props.proprietario || props.nome || props.nm_proprie || ''; const inscricao = props.Inscricao_imobiliaria || props.inscricao || props.cod_imovel || ''; const endereco = props.Endereco || props.endereco || props.Logradouro || props.logradouro || ''; // Calcular centro da geometria para zoom let lat = null, lng = null; if (feature.getBounds) { const center = feature.getBounds().getCenter(); lat = center.lat; lng = center.lng; } else if (feature.getLatLng) { const ll = feature.getLatLng(); lat = ll.lat; lng = ll.lng; } resultados.push({ ...props, nome: nome, inscricao: inscricao || `${quadra}/${lote}`, endereco: endereco, lat: lat, lng: lng, _feature: feature }); } } } }); } console.log('📍 [Alvará] Busca local encontrou:', resultados.length, 'resultados'); return resultados; } function renderizarAutocompleteAlvara(results) { const list = document.getElementById('alvaraAutocompleteList'); if (!list) return; if (!results || results.length === 0) { list.classList.remove('show'); return; } // Guardar resultados globalmente para evitar problemas com JSON em onclick window._alvaraAutocompleteResults = results; list.innerHTML = results.map((r, idx) => `
${r.nome || r.proprietario || r.inscricao || 'Lote'} ${r.inscricao || ''} ${r.endereco ? '• ' + r.endereco : ''}
`).join(''); list.classList.add('show'); } function selecionarAutocompleteAlvara(idx) { const list = document.getElementById('alvaraAutocompleteList'); if (list) list.classList.remove('show'); const item = window._alvaraAutocompleteResults?.[idx]; if (!item) { console.error('❌ Item não encontrado no índice:', idx); return; } console.log('✅ [Alvará] Selecionado:', item); // Preencher campos com dados do item preencherAlvaraComLote(item); // Dar zoom no lote se tiver coordenadas if (item.lat && item.lng) { if (window.map) { window.map.setView([item.lat, item.lng], 19); // Destacar o lote L.circleMarker([item.lat, item.lng], { radius: 15, fillColor: '#22c55e', color: '#fff', weight: 3, fillOpacity: 0.8 }).addTo(window.map); } } // Se tiver a feature original, destacar no mapa if (item._feature && item._feature.setStyle) { item._feature.setStyle({ color: '#22c55e', weight: 4, fillOpacity: 0.3 }); if (item._feature.getBounds) { window.map?.fitBounds(item._feature.getBounds(), { maxZoom: 19 }); } } // Limpar input const input = document.getElementById('alvaraBuscaInput'); if (input) input.value = item.nome || item.proprietario || item.inscricao || ''; } function alvaraBuscaKeydown(event) { if (event.key === 'Escape') { document.getElementById('alvaraAutocompleteList').classList.remove('show'); } } // Executar busca manual function executarBuscaAlvara() { const input = document.getElementById('alvaraBuscaInput'); if (input?.value) { buscarAlvaraAutocomplete(); } } // Consulta autocomplete function buscarAlvarasConsulta() { // Similar ao buscarAlvaraAutocomplete mas para a aba consulta const input = document.getElementById('alvaraConsultaInput'); if (input?.value?.length >= 2) { buscarAlvaras(); } } // Zoom para alvará no mapa async function zoomParaAlvara(inscricao) { closeAlvaraModal(); showError(`🔍 Localizando ${inscricao} no mapa...`); try { // Buscar o lote pela inscrição const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/imoveis/buscar?inscricao=${inscricao}`); if (response.ok) { const data = await response.json(); if (data.lat && data.lng) { map.setView([data.lat, data.lng], 19); // Destacar o lote L.circleMarker([data.lat, data.lng], { radius: 20, fillColor: '#4CAF50', color: '#fff', weight: 3, fillOpacity: 0.7 }).addTo(map).bindPopup(`📋 ${inscricao}`).openPopup(); showError(`✅ Lote ${inscricao} localizado!`); } else { showError('⚠️ Lote sem coordenadas cadastradas'); } } else { showError('⚠️ Lote não encontrado'); } } catch (error) { console.error('Erro:', error); showError('❌ Erro ao localizar lote'); } } // Visualizar detalhes do alvará async function visualizarAlvara(id) { showError(`📋 Carregando alvará...`); try { const response = await fetch(`${window.BACKEND_URL || 'http://localhost:5000'}/api/alvara/buscar/${id}`); if (response.ok) { const data = await response.json(); const alvara = data.alvara; if (alvara) { // Mostrar detalhes em alert ou modal const detalhes = ` ALVARÁ Nº ${alvara.numero} ━━━━━━━━━━━━━━━━━━━━━━━━ Tipo: ${alvara.tipo_nome || alvara.tipo} Status: ${alvara.status} Protocolo: ${alvara.protocolo} 📍 IMÓVEL Inscrição: ${alvara.imovel?.inscricao || ''} Endereço: ${alvara.imovel?.endereco || ''} 👤 REQUERENTE Nome: ${alvara.requerente?.nome || alvara.razao_social || ''} CPF/CNPJ: ${alvara.requerente?.cpf_cnpj || ''} Telefone: ${alvara.requerente?.telefone || ''} 📅 DATAS Solicitação: ${alvara.data_solicitacao?.split('T')[0] || ''} Emissão: ${alvara.data_emissao?.split('T')[0] || ''} Validade: ${alvara.data_validade || 'Permanente'} 💰 TAXA Valor: R$ ${(alvara.taxa_valor || 0).toFixed(2)} Pago: ${alvara.taxa_paga ? '✅ Sim' : '❌ Não'} `; alert(detalhes); } } else { showError('❌ Alvará não encontrado'); } } catch (error) { console.error('Erro:', error); showError('❌ Erro ao carregar alvará'); } } // ===================================================================== // =================== FIM DO MÓDULO DE ALVARÁS ======================== // ===================================================================== // Abrir modal do Memorial Descritivo function openMemorialModal() { const modal = document.getElementById('memorialModal'); if (modal) { modal.style.display = 'flex'; console.log('📐 Modal Memorial Descritivo aberto'); // Capturar escala e zoom do mapa principal ANTES de abrir o modal if (window.map) { memorialZoomDoMapaPrincipal = window.map.getZoom(); // Calcular escala baseada no zoom (aproximação para latitude média) const lat = window.map.getCenter().lat; const metersPerPixel = 156543.03392 * Math.cos(lat * Math.PI / 180) / Math.pow(2, memorialZoomDoMapaPrincipal); // Assumindo 96 DPI (pixels por polegada) memorialEscalaDoMapaPrincipal = Math.round(metersPerPixel * 96 / 0.0254); console.log(`📐 Escala capturada do mapa principal: 1:${memorialEscalaDoMapaPrincipal} (Zoom: ${memorialZoomDoMapaPrincipal})`); } // Inicializar mapa do memorial se ainda não existe setTimeout(() => { initMemorialMap(); // Atualizar camadas WMS para refletir camadas ativas do mapa principal setTimeout(() => addMemorialWMSLayers(), 200); }, 300); // Se já há um lote selecionado no mapa, carregar dados if (window.loteAtual && window.loteAtual.feature) { carregarDadosMemorial(window.loteAtual.feature); document.getElementById('memorialStatusSelecao').innerHTML = '✅ Lote carregado com sucesso!'; } } } // ========== MAPA INTERATIVO DO MEMORIAL ========== let memorialMap = null; let memorialLoteLayer = null; let memorialVerticesLayer = null; let memorialDistanciasLayer = null; let memorialSatelliteLayer = null; let memorialUsingSatellite = false; let memorialEscalaDoMapaPrincipal = null; // Escala capturada do mapa principal let memorialZoomDoMapaPrincipal = null; // Zoom capturado do mapa principal function initMemorialMap() { const container = document.getElementById('memorialMapPreview'); if (!container) return; // Se já existe, apenas atualizar e aplicar zoom do mapa principal if (memorialMap) { memorialMap.invalidateSize(); // Aplicar zoom do mapa principal se disponível if (memorialZoomDoMapaPrincipal) { memorialMap.setZoom(memorialZoomDoMapaPrincipal); } return; } console.log('🗺️ Inicializando mapa do Memorial...'); // Usar zoom do mapa principal ou padrão const zoomInicial = memorialZoomDoMapaPrincipal || 18; console.log(`🗺️ Usando zoom inicial: ${zoomInicial} (do mapa principal: ${memorialZoomDoMapaPrincipal || 'não capturado'})`); // Criar mapa memorialMap = L.map('memorialMapPreview', { zoomControl: true, attributionControl: false }).setView([-22.95, -47.35], zoomInicial); // Camada base (Google Hybrid) const googleHybrid = L.tileLayer('https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', { maxZoom: 22 }).addTo(memorialMap); // Camada satélite alternativa memorialSatelliteLayer = L.tileLayer('https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', { maxZoom: 22 }); // Adicionar camadas WMS do GeoServer (mesmas do mapa principal) addMemorialWMSLayers(); // Atualizar info de zoom/escala memorialMap.on('zoomend moveend', updateMemorialMapInfo); updateMemorialMapInfo(); console.log('🗺️ Mapa do Memorial inicializado'); } // Armazenar camadas adicionadas ao mapa memorial para limpeza let memorialWMSLayersAdded = []; function addMemorialWMSLayers() { if (!memorialMap) return; // Limpar camadas anteriores memorialWMSLayersAdded.forEach(layer => { try { memorialMap.removeLayer(layer); } catch(e) {} }); memorialWMSLayersAdded = []; // CLONAR CAMADAS ATIVAS DO MAPA PRINCIPAL const overlays = window.overlayLayers || {}; const mainMap = window.map; if (!mainMap) { logWarning('⚠️ Mapa principal não disponível'); return; } console.log('🗺️ Clonando camadas ativas do mapa principal...'); Object.keys(overlays).forEach(name => { const layer = overlays[name]; // Verificar se a camada está ativa no mapa principal if (layer && mainMap.hasLayer(layer)) { // Obter parâmetros WMS da camada const wmsParams = layer.wmsParams || layer.options; const url = layer._url || 'http://localhost:8080/geoserver/sigweb/wms'; console.log(` ✅ Clonando camada: ${name} (${wmsParams.layers})`); // Criar nova camada WMS com mesmos parâmetros const clonedLayer = L.tileLayer.wms(url, { layers: wmsParams.layers, format: wmsParams.format || 'image/png', transparent: true, version: wmsParams.version || '1.1.1', maxZoom: 24, maxNativeZoom: 22 }); clonedLayer.addTo(memorialMap); memorialWMSLayersAdded.push(clonedLayer); } }); console.log(`🗺️ Total de ${memorialWMSLayersAdded.length} camadas clonadas para o mapa memorial`); } function updateMemorialMapInfo() { if (!memorialMap) return; const zoom = memorialMap.getZoom(); const scale = Math.round(591657550.5 / Math.pow(2, zoom) / 96 * 39.37); document.getElementById('memorialMapZoom').textContent = zoom; document.getElementById('memorialMapScale').textContent = scale.toLocaleString(); // Mostrar escala de origem (do mapa principal) const origemEl = document.getElementById('memorialEscalaOrigem'); if (origemEl && memorialEscalaDoMapaPrincipal) { origemEl.textContent = `(Mapa: 1:${memorialEscalaDoMapaPrincipal.toLocaleString()})`; } } function memorialMapFitLote() { if (!memorialMap || !memorialLoteSelecionado) { showError('⚠️ Selecione um lote primeiro'); return; } if (memorialLoteSelecionado.geometry) { const coords = memorialLoteSelecionado.geometry.type === 'Polygon' ? memorialLoteSelecionado.geometry.coordinates[0] : memorialLoteSelecionado.geometry.coordinates[0][0]; let minLat = Infinity, maxLat = -Infinity, minLng = Infinity, maxLng = -Infinity; coords.forEach(c => { if (c[1] < minLat) minLat = c[1]; if (c[1] > maxLat) maxLat = c[1]; if (c[0] < minLng) minLng = c[0]; if (c[0] > maxLng) maxLng = c[0]; }); // Calcular centro do lote const centerLat = (minLat + maxLat) / 2; const centerLng = (minLng + maxLng) / 2; // Usar zoom do mapa principal se disponível, senão calcular if (memorialZoomDoMapaPrincipal) { memorialMap.setView([centerLat, centerLng], memorialZoomDoMapaPrincipal); console.log(`🎯 Centralizando no lote com zoom do mapa principal: ${memorialZoomDoMapaPrincipal}`); } else { // Fallback: usar fitBounds const pad = 0.5; const padLat = (maxLat - minLat) * pad; const padLng = (maxLng - minLng) * pad; memorialMap.fitBounds([ [minLat - padLat, minLng - padLng], [maxLat + padLat, maxLng + padLng] ]); } } } function memorialMapToggleSatellite() { if (!memorialMap) return; memorialUsingSatellite = !memorialUsingSatellite; if (memorialUsingSatellite) { memorialMap.eachLayer(layer => { if (layer._url && layer._url.includes('lyrs=y')) { memorialMap.removeLayer(layer); } }); memorialSatelliteLayer.addTo(memorialMap); } else { memorialMap.removeLayer(memorialSatelliteLayer); L.tileLayer('https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', { maxZoom: 22 }).addTo(memorialMap); } } function atualizarMapaMemorial() { if (!memorialMap || !memorialLoteSelecionado) return; console.log('🗺️ Atualizando mapa do Memorial com lote selecionado...'); // Remover camadas anteriores if (memorialLoteLayer) memorialMap.removeLayer(memorialLoteLayer); if (memorialVerticesLayer) memorialMap.removeLayer(memorialVerticesLayer); if (memorialDistanciasLayer) memorialMap.removeLayer(memorialDistanciasLayer); // Desenhar polígono do lote if (memorialLoteSelecionado.geometry) { memorialLoteLayer = L.geoJSON(memorialLoteSelecionado.geometry, { style: { color: '#ff0000', weight: 3, fillColor: '#ffff00', fillOpacity: 0.3 } }).addTo(memorialMap); // Calcular vértices e distâncias const coords = memorialLoteSelecionado.geometry.type === 'Polygon' ? memorialLoteSelecionado.geometry.coordinates[0] : memorialLoteSelecionado.geometry.coordinates[0][0]; // Remover último ponto (repetido) const vertices = coords.slice(0, -1); // Adicionar marcadores de vértices memorialVerticesLayer = L.layerGroup(); vertices.forEach((coord, i) => { const marker = L.circleMarker([coord[1], coord[0]], { radius: 8, color: '#ffffff', weight: 2, fillColor: '#ff0000', fillOpacity: 1 }); // Label do vértice marker.bindTooltip(`V${i + 1}`, { permanent: true, direction: 'top', className: 'vertice-label', offset: [0, -10] }); memorialVerticesLayer.addLayer(marker); }); memorialVerticesLayer.addTo(memorialMap); // Adicionar labels de distância memorialDistanciasLayer = L.layerGroup(); for (let i = 0; i < vertices.length; i++) { const v1 = vertices[i]; const v2 = vertices[(i + 1) % vertices.length]; // Ponto médio const midLat = (v1[1] + v2[1]) / 2; const midLng = (v1[0] + v2[0]) / 2; // Calcular distância (aproximada em metros) const dist = calcularDistanciaMetros(v1[1], v1[0], v2[1], v2[0]); // Criar label const label = L.marker([midLat, midLng], { icon: L.divIcon({ className: 'distancia-label', html: `
${dist.toFixed(2)}m
`, iconSize: [60, 20], iconAnchor: [30, 10] }) }); memorialDistanciasLayer.addLayer(label); } memorialDistanciasLayer.addTo(memorialMap); // Centralizar no lote memorialMapFitLote(); } } function calcularDistanciaMetros(lat1, lng1, lat2, lng2) { const R = 6371000; // Raio da Terra em metros const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng/2) * Math.sin(dLng/2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return R * c; } // Fechar modal do Memorial Descritivo function closeMemorialModal() { const modal = document.getElementById('memorialModal'); if (modal) { modal.style.display = 'none'; } // Limpar marcadores de vértices do mapa limparVerticesMapa(); } // Limpar marcadores de vértices do mapa function limparVerticesMapa() { memorialVerticesMarkers.forEach(marker => { if (map.hasLayer(marker)) { map.removeLayer(marker); } }); memorialVerticesMarkers = []; } // Carregar dados do lote no modal Memorial function carregarDadosMemorial(feature) { if (!feature || !feature.properties) { logWarning('⚠️ Feature sem propriedades para o Memorial'); return; } memorialLoteSelecionado = feature; const props = feature.properties; console.log('📐 Carregando dados do Memorial:', props); console.log('📐 Todas as propriedades disponíveis:', Object.keys(props)); // Preencher campos do formulário document.getElementById('memorialLote').value = props.lote || props.Lote || props.LOTE || props.num_lote || ''; document.getElementById('memorialQuadra').value = props.quadra || props.Quadra || props.QUADRA || props.num_quadra || ''; document.getElementById('memorialBairro').value = props.bairro || props.Bairro || props.BAIRRO || props.nm_bairro || ''; document.getElementById('memorialEndereco').value = props.endereco || props.Endereco || props.ENDERECO || props.logradouro || props.Logradouro || props.nm_lograd || ''; // Município e UF - buscar do lote (priorizar campo 'cidade'), da prefeitura logada ou deixar editável let municipio = props.cidade || props.Cidade || props.CIDADE || props.municipio || props.Municipio || props.MUNICIPIO || props.nm_municipio || ''; let uf = props.uf || props.UF || props.estado || props.Estado || props.sigla_uf || ''; console.log('📐 Município encontrado no lote:', municipio); // Se não veio do lote, tentar pegar do localStorage (dados do usuário logado) if (!municipio) { try { const userInfo = JSON.parse(localStorage.getItem('sigweb_user') || '{}'); const prefeituraNome = userInfo.prefeitura_nome || userInfo.prefeitura || ''; if (prefeituraNome) { municipio = prefeituraNome.replace('Prefeitura ', '').replace('de ', '').trim(); } // Tentar extrair UF do nome da prefeitura (ex: "Monte Mor - SP") if (prefeituraNome.includes(' - ')) { const parts = prefeituraNome.split(' - '); if (parts.length > 1) uf = parts[1].trim(); } console.log('📐 Município detectado da prefeitura:', municipio, uf); } catch(e) { logWarning('Erro ao buscar prefeitura:', e); } } document.getElementById('memorialMunicipio').value = municipio; document.getElementById('memorialUF').value = uf || 'SP'; // Default SP se não definido // Áreas - buscar em vários campos possíveis let areaTerreno = props.area_lote || props.Area_Lote || props.AREA_LOTE || props.area || props.Area || props.AREA || props.areacalc || props.Areacalc || props.AREACALC || props.area_terreno || props.Area_Terreno || props.AREA_TERRENO || props.st_area || props.ST_Area || ''; let areaConstruida = props.area_edifi || props.Area_Edifi || props.AREA_EDIFI || props.area_construida || props.Area_Construida || props.areacalc_e || props.area_const || ''; let perimetro = props.perimetro || props.Perimetro || props.PERIMETRO || props.perimetroc || props.Perimetroc || props.st_perimeter || props.ST_Perimeter || ''; console.log('📐 Áreas encontradas - Terreno:', areaTerreno, '| Construída:', areaConstruida, '| Perímetro:', perimetro); document.getElementById('memorialAreaTerreno').value = areaTerreno ? parseFloat(areaTerreno).toFixed(2) : ''; document.getElementById('memorialAreaConstruida').value = areaConstruida ? parseFloat(areaConstruida).toFixed(2) : ''; document.getElementById('memorialPerimetro').value = perimetro ? parseFloat(perimetro).toFixed(2) : ''; // Registro - Inscrição Imobiliária (múltiplas variantes de nome de campo) let inscricaoImob = props['Inscricao imobiliaria'] || props.Inscricao_imobiliaria || props.inscricao_imobiliaria || props.Inscricao || props.inscricao || props.INSCRICAO || props['Inscrição'] || props.Inscrição || props.cod_imovel || props.codigo_imovel || props.property_id || ''; console.log('📋 Inscrição Imobiliária encontrada:', inscricaoImob); document.getElementById('memorialInscricao').value = inscricaoImob; // Proprietário document.getElementById('memorialProprietario').value = props.proprietario || props.Proprietario || props.PROPRIETARIO || props.nome || props.nm_proprie || ''; document.getElementById('memorialCpf').value = props.cpf || props.Cpf || props.CPF || props.cnpj || props.cpf_propri || ''; // Calcular e mostrar vértices calcularVerticesMemorial(feature); // Atualizar mapa do memorial com o lote selecionado setTimeout(() => atualizarMapaMemorial(), 500); } // Converter coordenadas geográficas para UTM function convertToUTM(lng, lat) { // Calcular zona UTM baseada na longitude const zone = Math.floor((lng + 180) / 6) + 1; const hemisphere = lat >= 0 ? 'N' : 'S'; // Definir projeção UTM const utmProj = `+proj=utm +zone=${zone} +${hemisphere === 'S' ? 'south' : 'north'} +ellps=WGS84 +datum=WGS84 +units=m +no_defs`; // Usar proj4 se disponível if (typeof proj4 !== 'undefined') { const result = proj4('EPSG:4326', utmProj, [lng, lat]); return { E: result[0], N: result[1], zone: zone, hemisphere: hemisphere }; } else { // Fallback: conversão simplificada logWarning('⚠️ proj4 não disponível, usando conversão aproximada'); return { E: lng * 111320 * Math.cos(lat * Math.PI / 180), N: lat * 110574, zone: zone, hemisphere: hemisphere }; } } // Calcular azimute entre dois pontos (em graus) function calcularAzimute(p1, p2) { const dE = p2.E - p1.E; const dN = p2.N - p1.N; let azimute = Math.atan2(dE, dN) * (180 / Math.PI); if (azimute < 0) azimute += 360; return azimute; } // Calcular distância entre dois pontos UTM (em metros) function calcularDistancia(p1, p2) { const dE = p2.E - p1.E; const dN = p2.N - p1.N; return Math.sqrt(dE * dE + dN * dN); } // Calcular vértices do polígono function calcularVerticesMemorial(feature) { const tbody = document.getElementById('memorialVerticesBody'); if (!feature || !feature.geometry) { tbody.innerHTML = `
⚠️

Geometria não encontrada

`; return; } // Obter coordenadas do polígono let coords = []; const geomType = feature.geometry.type; if (geomType === 'Polygon') { coords = feature.geometry.coordinates[0]; // Primeiro anel } else if (geomType === 'MultiPolygon') { coords = feature.geometry.coordinates[0][0]; // Primeiro polígono, primeiro anel } else { tbody.innerHTML = `
⚠️

Tipo de geometria não suportado: ${geomType}

`; return; } // Remover o último ponto se for igual ao primeiro (fechamento do polígono) if (coords.length > 1) { const first = coords[0]; const last = coords[coords.length - 1]; if (first[0] === last[0] && first[1] === last[1]) { coords = coords.slice(0, -1); } } // Converter para UTM e calcular azimutes/distâncias const vertices = coords.map((coord, index) => { const lng = coord[0]; const lat = coord[1]; return { index: index + 1, lng: lng, lat: lat, utm: convertToUTM(lng, lat) }; }); // Gerar tabela let html = ''; vertices.forEach((v, i) => { const nextV = vertices[(i + 1) % vertices.length]; const azimute = calcularAzimute(v.utm, nextV.utm); const distancia = calcularDistancia(v.utm, nextV.utm); html += ` V${v.index} ${v.utm.E.toFixed(3)} ${v.utm.N.toFixed(3)} V${v.index} → V${nextV.index} ${azimute.toFixed(4)}° ${distancia.toFixed(3)} `; }); tbody.innerHTML = html; // Armazenar vértices para uso posterior memorialLoteSelecionado.vertices = vertices; // Calcular área e perímetro a partir da geometria se não estiverem preenchidos const areaInput = document.getElementById('memorialAreaTerreno'); const perimInput = document.getElementById('memorialPerimetro'); if (!areaInput.value || areaInput.value === '' || areaInput.value === '0') { // Calcular área usando fórmula de Gauss (Shoelace) let area = 0; for (let i = 0; i < vertices.length; i++) { const j = (i + 1) % vertices.length; area += vertices[i].utm.E * vertices[j].utm.N; area -= vertices[j].utm.E * vertices[i].utm.N; } area = Math.abs(area) / 2; areaInput.value = area.toFixed(2); console.log('📐 Área calculada automaticamente:', area.toFixed(2), 'm²'); } if (!perimInput.value || perimInput.value === '' || perimInput.value === '0') { // Calcular perímetro let perimetro = 0; for (let i = 0; i < vertices.length; i++) { const nextV = vertices[(i + 1) % vertices.length]; perimetro += calcularDistancia(vertices[i].utm, nextV.utm); } perimInput.value = perimetro.toFixed(2); console.log('📐 Perímetro calculado automaticamente:', perimetro.toFixed(2), 'm'); } console.log(`📍 ${vertices.length} vértices calculados para o Memorial`); } // Mostrar vértices numerados no mapa function mostrarVerticesNoMapa() { if (!memorialLoteSelecionado || !memorialLoteSelecionado.vertices) { alert('⚠️ Selecione um lote primeiro para visualizar os vértices.'); return; } // Limpar marcadores anteriores limparVerticesMapa(); const vertices = memorialLoteSelecionado.vertices; // Criar marcadores para cada vértice vertices.forEach((v, i) => { const icon = L.divIcon({ className: 'memorial-vertice-marker', html: `
${v.index}
`, iconSize: [24, 24], iconAnchor: [12, 12] }); const marker = L.marker([v.lat, v.lng], { icon: icon }) .addTo(map) .bindPopup(`
Vértice ${v.index}
E: ${v.utm.E.toFixed(3)} m
N: ${v.utm.N.toFixed(3)} m
Zona UTM: ${v.utm.zone}${v.utm.hemisphere}
`); memorialVerticesMarkers.push(marker); }); // Zoom para o polígono if (memorialLoteSelecionado.geometry) { const bounds = L.geoJSON(memorialLoteSelecionado).getBounds(); map.fitBounds(bounds, { padding: [50, 50] }); } console.log(`📍 ${vertices.length} marcadores de vértices adicionados ao mapa`); // Fechar modal para visualizar closeMemorialModal(); // Mostrar notificação showError(`📍 ${vertices.length} vértices exibidos no mapa`); } // Gerar PDF do Memorial Descritivo async function gerarMemorialPDF() { if (!memorialLoteSelecionado) { alert('⚠️ Selecione um lote primeiro para gerar o Memorial Descritivo.'); return; } // Preparar vértices no formato correto para o backend [lon, lat] let verticesFormatados = []; if (memorialLoteSelecionado.vertices && memorialLoteSelecionado.vertices.length > 0) { verticesFormatados = memorialLoteSelecionado.vertices.map(v => [v.lng, v.lat]); } else if (memorialLoteSelecionado.geometry) { // Extrair diretamente da geometria const geomType = memorialLoteSelecionado.geometry.type; if (geomType === 'Polygon') { verticesFormatados = memorialLoteSelecionado.geometry.coordinates[0].slice(0, -1); } else if (geomType === 'MultiPolygon') { verticesFormatados = memorialLoteSelecionado.geometry.coordinates[0][0].slice(0, -1); } } // Coletar dados do formulário const dados = { lote: document.getElementById('memorialLote').value, quadra: document.getElementById('memorialQuadra').value, bairro: document.getElementById('memorialBairro').value, municipio: document.getElementById('memorialMunicipio').value, uf: document.getElementById('memorialUF').value, endereco: document.getElementById('memorialEndereco').value, area_terreno: parseFloat(document.getElementById('memorialAreaTerreno').value) || 0, area_edificacao: parseFloat(document.getElementById('memorialAreaConstruida').value) || 0, perimetro: parseFloat(document.getElementById('memorialPerimetro').value) || 0, matricula: document.getElementById('memorialMatricula').value, comarca: document.getElementById('memorialComarca').value, cartorio: document.getElementById('memorialCartorio').value, inscricao: document.getElementById('memorialInscricao').value, proprietario: document.getElementById('memorialProprietario').value, cpf: document.getElementById('memorialCpf').value, rtNome: document.getElementById('memorialRTNome').value, rtFormacao: document.getElementById('memorialRTFormacao').value, rtRegistro: document.getElementById('memorialRTRegistro').value, observacoes: document.getElementById('memorialObservacoes').value, vertices: verticesFormatados }; console.log('📄 Gerando Memorial Descritivo PDF:', dados); console.log(`📐 Enviando ${verticesFormatados.length} vértices para o backend`); try { showError('📄 Gerando PDF do Memorial Descritivo...'); const response = await fetch(`${backendUrl}/api/memorial-descritivo/gerar-pdf`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('sigweb_token')}`, ...window.ngrokHeaders }, body: JSON.stringify(dados) }); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Memorial_Descritivo_Lote_${dados.lote || 'sem_numero'}_Quadra_${dados.quadra || 'sem_numero'}.pdf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); showError('✅ Memorial Descritivo gerado com sucesso!'); } else { const error = await response.json(); throw new Error(error.message || 'Erro ao gerar PDF'); } } catch (error) { console.error('❌ Erro ao gerar Memorial:', error); showError(`❌ Erro: ${error.message}`); } } // Gerar PDF da Planta de Situação async function gerarPlantaSituacaoPDF() { if (!memorialLoteSelecionado) { alert('⚠️ Selecione um lote primeiro para gerar a Planta de Situação.'); return; } // Preparar vértices no formato correto para o backend [lon, lat] let verticesFormatados = []; if (memorialLoteSelecionado.vertices && memorialLoteSelecionado.vertices.length > 0) { verticesFormatados = memorialLoteSelecionado.vertices.map(v => [v.lng, v.lat]); } else if (memorialLoteSelecionado.geometry) { const geomType = memorialLoteSelecionado.geometry.type; if (geomType === 'Polygon') { verticesFormatados = memorialLoteSelecionado.geometry.coordinates[0].slice(0, -1); } else if (geomType === 'MultiPolygon') { verticesFormatados = memorialLoteSelecionado.geometry.coordinates[0][0].slice(0, -1); } } // ========== CAPTURAR MAPA DO MEMORIAL ========== let mapaBase64 = null; let mapBounds = null; try { showError('📸 Capturando mapa do preview...'); // Usar o mapa do memorial (já está configurado pelo usuário) const mapContainer = document.getElementById('memorialMapPreview'); if (mapContainer && memorialMap && typeof html2canvas !== 'undefined') { // Obter bounds atuais do mapa memorial const bounds = memorialMap.getBounds(); mapBounds = { minLat: bounds.getSouth(), maxLat: bounds.getNorth(), minLng: bounds.getWest(), maxLng: bounds.getEast() }; console.log('📸 Capturando mapa do memorial...', mapBounds); // Aguardar tiles carregarem await new Promise(resolve => setTimeout(resolve, 500)); const screenshot = await html2canvas(mapContainer, { backgroundColor: '#ffffff', scale: 2, useCORS: true, allowTaint: true, logging: false, ignoreElements: (element) => { return element.classList.contains('leaflet-control') || element.classList.contains('leaflet-control-zoom') || element.id === 'memorialMapInfo'; } }); mapaBase64 = screenshot.toDataURL('image/png'); console.log('📸 Mapa do memorial capturado! Tamanho:', screenshot.width, 'x', screenshot.height); } else { logWarning('📸 Mapa do memorial não disponível, usando fallback'); } } catch (err) { logWarning('⚠️ Erro ao capturar mapa do memorial:', err); } // Coletar TODOS os dados do formulário para a planta // Calcular escala atual do mapa memorial let escalaAtual = memorialEscalaDoMapaPrincipal; if (memorialMap) { const zoom = memorialMap.getZoom(); const lat = memorialMap.getCenter().lat; const metersPerPixel = 156543.03392 * Math.cos(lat * Math.PI / 180) / Math.pow(2, zoom); escalaAtual = Math.round(metersPerPixel * 96 / 0.0254); } console.log(`📐 Escala para o PDF: 1:${escalaAtual}`); const dados = { lote: document.getElementById('memorialLote').value, quadra: document.getElementById('memorialQuadra').value, bairro: document.getElementById('memorialBairro').value, municipio: document.getElementById('memorialMunicipio').value, uf: document.getElementById('memorialUF').value, endereco: document.getElementById('memorialEndereco').value, proprietario: document.getElementById('memorialProprietario').value, area_terreno: parseFloat(document.getElementById('memorialAreaTerreno').value) || 0, perimetro: parseFloat(document.getElementById('memorialPerimetro').value) || 0, inscricao: document.getElementById('memorialInscricao').value, rtNome: document.getElementById('memorialRTNome').value, rtFormacao: document.getElementById('memorialRTFormacao').value, rtRegistro: document.getElementById('memorialRTRegistro').value, vertices: verticesFormatados, mapaBase64: mapaBase64, mapBounds: mapBounds, escala: escalaAtual // Escala capturada do mapa }; console.log('🗺️ Gerando Planta de Situação PDF:', dados); try { showError('🗺️ Gerando PDF da Planta de Situação...'); const response = await fetch(`${backendUrl}/api/memorial-descritivo/gerar-planta`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('sigweb_token')}`, ...window.ngrokHeaders }, body: JSON.stringify(dados) }); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Planta_Situacao_Lote_${dados.lote || 'sem_numero'}_Quadra_${dados.quadra || 'sem_numero'}.pdf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); showError('✅ Planta de Situação gerada com sucesso!'); } else { const error = await response.json(); throw new Error(error.message || 'Erro ao gerar PDF'); } } catch (error) { console.error('❌ Erro ao gerar Planta:', error); showError(`❌ Erro: ${error.message}`); } } // Função para carregar dados do memorial quando um lote é selecionado no mapa function atualizarMemorialComLote(feature) { // Armazenar para uso posterior window.loteAtual = { feature: feature }; // Se estava no modo de seleção para o Memorial, reabrir o modal if (memorialSelecionandoLote) { memorialSelecionandoLote = false; // Reabrir o modal do Memorial const modal = document.getElementById('memorialModal'); if (modal) { modal.style.display = 'flex'; carregarDadosMemorial(feature); // Atualizar mapa do memorial com zoom do mapa principal setTimeout(() => { if (memorialMap && memorialZoomDoMapaPrincipal) { memorialMap.invalidateSize(); addMemorialWMSLayers(); } }, 300); document.getElementById('memorialStatusSelecao').innerHTML = '✅ Lote carregado com sucesso!'; showError('✅ Lote selecionado! Dados carregados no Memorial.'); // Mostrar escala capturada console.log(`📐 Escala mantida do mapa principal: 1:${memorialEscalaDoMapaPrincipal}`); } } else if (document.getElementById('memorialModal').style.display === 'flex') { carregarDadosMemorial(feature); document.getElementById('memorialStatusSelecao').innerHTML = '✅ Lote carregado com sucesso!'; } } // ============================================ // FUNÇÕES DE CONFIGURAÇÃO DA PGV // ============================================ // Toggle entre zona única e múltiplas zonas fiscais function toggleZonasFiscais() { const usarZonas = document.getElementById('pgvUsarZonas')?.checked; const zonaUnicaWrap = document.getElementById('pgvZonaUnicaWrap'); const zonasMultiplasWrap = document.getElementById('pgvZonasMultiplasWrap'); const campoZonaWrap = document.getElementById('pgvCampoZonaWrap'); if (usarZonas) { if (zonaUnicaWrap) zonaUnicaWrap.style.display = 'none'; if (zonasMultiplasWrap) zonasMultiplasWrap.style.display = 'block'; if (campoZonaWrap) campoZonaWrap.style.display = 'block'; } else { if (zonaUnicaWrap) zonaUnicaWrap.style.display = 'block'; if (zonasMultiplasWrap) zonasMultiplasWrap.style.display = 'none'; if (campoZonaWrap) campoZonaWrap.style.display = 'none'; } } // Abrir modal de configuração da PGV async function abrirConfigPGV() { const modal = document.getElementById('pgvModal'); if (!modal) return; modal.style.display = 'flex'; // Carregar configurações atuais await carregarConfigPGV(); } // Fechar modal de configuração da PGV function fecharConfigPGV() { const modal = document.getElementById('pgvModal'); if (modal) { modal.style.display = 'none'; } } // Carregar configurações atuais da PGV do backend async function carregarConfigPGV() { try { const resp = await fetch(`${backendUrl}/api/iptu/pgv`, { headers: window.ngrokHeaders }); const data = await resp.json(); if (data.success && data.pgv) { const cfg = data.pgv; // ========== ABA VALORES BASE ========== // Zona fiscal e valores por zona const zonaFiscal = cfg.zona_fiscal || {}; const valorM2Terreno = cfg.valor_m2_terreno || {}; const usarZonas = zonaFiscal.zona_unica === false; // Configurar checkbox e campos de zona const chkZonas = document.getElementById('pgvUsarZonas'); if (chkZonas) chkZonas.checked = usarZonas; toggleZonasFiscais(); // Atualizar visibilidade // Campo zona const campoZona = zonaFiscal.campo_zona; if (campoZona && document.getElementById('pgvCampoZona')) { document.getElementById('pgvCampoZona').value = Array.isArray(campoZona) ? campoZona[0] : campoZona; } // Valores de TERRENO por zona const valoresTerrenoPorZona = zonaFiscal.valores_terreno_por_zona || zonaFiscal.valores_por_zona || {}; for (let i = 1; i <= 5; i++) { const el = document.getElementById('pgvZona' + i); if (el) { // Tentar várias formas de nome de zona const val = valoresTerrenoPorZona[String(i)] || valoresTerrenoPorZona['zona ' + i] || valoresTerrenoPorZona['Zona ' + i] || (200 - (i-1)*25); el.value = val; } } // Valores de CONSTRUÇÃO por zona const valoresConstrucaoPorZona = zonaFiscal.valores_construcao_por_zona || {}; for (let i = 1; i <= 5; i++) { const el = document.getElementById('pgvZonaConstr' + i); if (el) { const val = valoresConstrucaoPorZona[String(i)] || valoresConstrucaoPorZona['zona ' + i] || valoresConstrucaoPorZona['Zona ' + i] || (1500 - (i-1)*150); el.value = val; } } // Valor base terreno (fallback) document.getElementById('pgvValorTerreno').value = zonaFiscal.valor_m2_terreno_base || valorM2Terreno.zona_unica || 150; if (document.getElementById('pgvValorTerrenoBase')) { document.getElementById('pgvValorTerrenoBase').value = zonaFiscal.valor_m2_terreno_base || 100; } // Valor base construção (fallback) const valorM2Constr = cfg.valor_m2_construcao || {}; const valorConstrucaoBase = zonaFiscal.valor_m2_construcao_base || valorM2Constr.base || 1200; document.getElementById('pgvValorConstrucao').value = valorConstrucaoBase; if (document.getElementById('pgvValorConstrucaoZonas')) { document.getElementById('pgvValorConstrucaoZonas').value = valorConstrucaoBase; } // Descontos e encargos if (cfg.desconto_cota_unica) document.getElementById('pgvDescCotaUnica').value = cfg.desconto_cota_unica * 100; if (cfg.multa_atraso) document.getElementById('pgvMultaAtraso').value = cfg.multa_atraso * 100; if (cfg.juros_mes) document.getElementById('pgvJurosMes').value = cfg.juros_mes * 100; if (cfg.max_parcelas) document.getElementById('pgvMaxParcelas').value = cfg.max_parcelas; if (cfg.valor_minimo_parcela) document.getElementById('pgvMinParcela').value = cfg.valor_minimo_parcela; // ========== ABA ALÍQUOTAS ========== if (cfg.aliquotas) { const aliq = cfg.aliquotas; document.getElementById('pgvAliqResidencial').value = (aliq.residencial || 0.01) * 100; document.getElementById('pgvAliqComercial').value = (aliq.comercial || 0.015) * 100; document.getElementById('pgvAliqIndustrial').value = (aliq.industrial || 0.02) * 100; document.getElementById('pgvAliqBaldio').value = (aliq.baldio || 0.03) * 100; document.getElementById('pgvAliqMisto').value = (aliq.misto || 0.012) * 100; document.getElementById('pgvAliqEspecial').value = (aliq.especial || 0.005) * 100; } // ========== ABA FATORES TERRENO ========== const fatoresTerreno = cfg.fatores_terreno || {}; // Topografia const topo = fatoresTerreno.topografia?.valores || {}; if (topo.plano) document.getElementById('pgvTopoPlano').value = topo.plano; if (topo.aclive) document.getElementById('pgvTopoAclive').value = topo.aclive; if (topo.declive) document.getElementById('pgvTopoDeclive').value = topo.declive; if (topo.irregular) document.getElementById('pgvTopoIrregular').value = topo.irregular; if (topo.alagado) document.getElementById('pgvTopoAlagado').value = topo.alagado; // Situação const sit = fatoresTerreno.situacao?.valores || {}; if (sit.esquina) document.getElementById('pgvSitEsquina').value = sit.esquina; if (sit['meio de quadra'] || sit.meio_quadra) document.getElementById('pgvSitMeio').value = sit['meio de quadra'] || sit.meio_quadra; if (sit.vila) document.getElementById('pgvSitVila').value = sit.vila; if (sit.encravado) document.getElementById('pgvSitEncravado').value = sit.encravado; if (sit.fundos) document.getElementById('pgvSitFundos').value = sit.fundos; // Limitação const lim = fatoresTerreno.limitacao?.valores || {}; if (lim.muro || lim['muro de alvenaria']) document.getElementById('pgvLimMuro').value = lim.muro || lim['muro de alvenaria']; if (lim.gradil || lim.grade) document.getElementById('pgvLimGradil').value = lim.gradil || lim.grade; if (lim['cerca de madeira']) document.getElementById('pgvLimCercaMad').value = lim['cerca de madeira']; if (lim['cerca de arame'] || lim.cerca) document.getElementById('pgvLimCercaArame').value = lim['cerca de arame'] || lim.cerca; if (lim.sem || lim.inexistente) document.getElementById('pgvLimSem').value = lim.sem || lim.inexistente; // Pedologia const pedo = fatoresTerreno.pedologia?.valores || {}; if (pedo.normal || pedo.firme) document.getElementById('pgvPedoNormal').value = pedo.normal || pedo.firme; if (pedo.arenoso) document.getElementById('pgvPedoArenoso').value = pedo.arenoso; if (pedo.rochoso) document.getElementById('pgvPedoRochoso').value = pedo.rochoso; if (pedo.alagadico) document.getElementById('pgvPedoAlagadico').value = pedo.alagadico; // ========== ABA FATORES CONSTRUÇÃO ========== const fatoresConstr = cfg.fatores_construcao || {}; // Tipo construção const tipo = fatoresConstr.tipo_construcao?.valores || {}; if (tipo.casa) document.getElementById('pgvTipoCasa').value = tipo.casa; if (tipo.apartamento) document.getElementById('pgvTipoApto').value = tipo.apartamento; if (tipo.sobrado) document.getElementById('pgvTipoSobrado').value = tipo.sobrado; if (tipo.galpao || tipo['galpão']) document.getElementById('pgvTipoGalpao').value = tipo.galpao || tipo['galpão']; if (tipo.barracao || tipo['barracão']) document.getElementById('pgvTipoBarracao').value = tipo.barracao || tipo['barracão']; // Padrão construtivo const padrao = fatoresConstr.padrao_construtivo?.valores || {}; if (padrao.luxo) document.getElementById('pgvPadraoLuxo').value = padrao.luxo; if (padrao.alto) document.getElementById('pgvPadraoAlto').value = padrao.alto; if (padrao.medio || padrao['médio']) document.getElementById('pgvPadraoMedio').value = padrao.medio || padrao['médio']; if (padrao.popular) document.getElementById('pgvPadraoPopular').value = padrao.popular; if (padrao.precario || padrao['precário']) document.getElementById('pgvPadraoPrecario').value = padrao.precario || padrao['precário']; // Estado de conservação const conserv = fatoresConstr.estado_conservacao?.valores || {}; if (conserv.otimo || conserv['ótimo']) document.getElementById('pgvConservOtimo').value = conserv.otimo || conserv['ótimo']; if (conserv.bom) document.getElementById('pgvConservBom').value = conserv.bom; if (conserv.regular) document.getElementById('pgvConservRegular').value = conserv.regular; if (conserv.ruim) document.getElementById('pgvConservRuim').value = conserv.ruim; if (conserv.precario || conserv['precário']) document.getElementById('pgvConservPrecario').value = conserv.precario || conserv['precário']; // Estrutura const estrut = fatoresConstr.estrutura?.valores || {}; if (estrut.concreto || estrut['concreto armado']) document.getElementById('pgvEstrutConcreto').value = estrut.concreto || estrut['concreto armado']; if (estrut.metalica || estrut['metálica']) document.getElementById('pgvEstrutMetalica').value = estrut.metalica || estrut['metálica']; if (estrut.alvenaria) document.getElementById('pgvEstrutAlvenaria').value = estrut.alvenaria; if (estrut.mista) document.getElementById('pgvEstrutMista').value = estrut.mista; if (estrut.madeira) document.getElementById('pgvEstrutMadeira').value = estrut.madeira; // Cobertura const cobert = fatoresConstr.cobertura?.valores || {}; if (cobert.laje) document.getElementById('pgvCobertLaje').value = cobert.laje; if (cobert['telha ceramica'] || cobert['telha cerâmica']) document.getElementById('pgvCobertCeramica').value = cobert['telha ceramica'] || cobert['telha cerâmica']; if (cobert['telha concreto']) document.getElementById('pgvCobertConcreto').value = cobert['telha concreto']; if (cobert.fibrocimento || cobert.amianto) document.getElementById('pgvCobertFibro').value = cobert.fibrocimento || cobert.amianto; if (cobert.zinco) document.getElementById('pgvCobertZinco').value = cobert.zinco; // Piso const piso = fatoresConstr.piso?.valores || {}; if (piso.porcelanato) document.getElementById('pgvPisoPorcelanato').value = piso.porcelanato; if (piso.ceramica || piso['cerâmica']) document.getElementById('pgvPisoCeramica').value = piso.ceramica || piso['cerâmica']; if (piso.madeira) document.getElementById('pgvPisoMadeira').value = piso.madeira; if (piso.cimento || piso['cimento queimado']) document.getElementById('pgvPisoCimento').value = piso.cimento || piso['cimento queimado']; if (piso.terra) document.getElementById('pgvPisoTerra').value = piso.terra; // Instalação elétrica const elet = fatoresConstr.instalacao_eletrica?.valores || {}; if (elet.embutida) document.getElementById('pgvEletEmbutida').value = elet.embutida; if (elet.aparente) document.getElementById('pgvEletAparente').value = elet.aparente; if (elet.inexistente) document.getElementById('pgvEletInexist').value = elet.inexistente; // Instalação sanitária const sanit = fatoresConstr.instalacao_sanitaria?.valores || {}; if (sanit.interna) document.getElementById('pgvSanitInterna').value = sanit.interna; if (sanit.externa) document.getElementById('pgvSanitExterna').value = sanit.externa; if (sanit.inexistente) document.getElementById('pgvSanitInexist').value = sanit.inexistente; console.log('✅ Configurações PGV completas carregadas'); } } catch (e) { logWarning('⚠️ Erro ao carregar configurações PGV:', e); } } // Trocar aba do modal PGV function trocarAbaPGV(aba) { // Esconder todas as abas document.querySelectorAll('.pgv-aba-content').forEach(el => el.style.display = 'none'); document.querySelectorAll('.pgv-tab').forEach(el => { el.style.background = '#f1f5f9'; el.style.color = '#64748b'; el.style.borderBottom = '3px solid transparent'; }); // Mostrar aba selecionada const abaMap = { 'valores': 'pgvAbaValores', 'aliquotas': 'pgvAbaAliquotas', 'terreno': 'pgvAbaTerreno', 'construcao': 'pgvAbaConstrucao' }; const tabMap = { 'valores': 'tabValores', 'aliquotas': 'tabAliquotas', 'terreno': 'tabTerreno', 'construcao': 'tabConstrucao' }; document.getElementById(abaMap[aba]).style.display = 'block'; const tab = document.getElementById(tabMap[aba]); tab.style.background = 'white'; tab.style.color = '#0d3b66'; tab.style.borderBottom = '3px solid #0d3b66'; } // Salvar configurações completas da PGV async function salvarConfigPGVCompleta() { try { // Verificar se está usando múltiplas zonas const usarZonas = document.getElementById('pgvUsarZonas')?.checked; // Construir valores por zona const valoresPorZona = {}; for (let i = 1; i <= 5; i++) { const el = document.getElementById('pgvZona' + i); if (el) { const val = parseFloat(el.value) || (200 - (i-1)*25); valoresPorZona[String(i)] = val; valoresPorZona['zona ' + i] = val; valoresPorZona['Zona ' + i] = val; } } // Construir valores de CONSTRUÇÃO por zona const valoresConstrucaoPorZona = {}; for (let i = 1; i <= 5; i++) { const el = document.getElementById('pgvZonaConstr' + i); if (el) { const val = parseFloat(el.value) || (1500 - (i-1)*150); valoresConstrucaoPorZona[String(i)] = val; valoresConstrucaoPorZona['zona ' + i] = val; valoresConstrucaoPorZona['Zona ' + i] = val; } } // Obter valor base do terreno (depende do modo) let valorTerrenoBase; if (usarZonas) { valorTerrenoBase = parseFloat(document.getElementById('pgvValorTerrenoBase')?.value) || 100; } else { valorTerrenoBase = parseFloat(document.getElementById('pgvValorTerreno')?.value) || 150; } // Obter valor construção (depende do modo) let valorConstrucaoBase; if (usarZonas) { valorConstrucaoBase = parseFloat(document.getElementById('pgvValorConstrucaoZonas')?.value) || 1200; } else { valorConstrucaoBase = parseFloat(document.getElementById('pgvValorConstrucao')?.value) || 1200; } // Campo zona no GeoServer const campoZonaInput = document.getElementById('pgvCampoZona')?.value || 'Zona'; const camposZona = [campoZonaInput, campoZonaInput.toLowerCase(), campoZonaInput.toUpperCase()]; const config = { // Valores base com suporte a zonas zona_fiscal: { zona_unica: !usarZonas, valor_m2_terreno_base: valorTerrenoBase, valor_m2_construcao_base: valorConstrucaoBase, campo_zona: camposZona, valores_terreno_por_zona: valoresPorZona, valores_construcao_por_zona: valoresConstrucaoPorZona }, valor_m2_terreno: { zona_unica: valorTerrenoBase }, valor_m2_construcao: { base: valorConstrucaoBase }, // Alíquotas (converter de % para decimal) aliquotas: { residencial: parseFloat(document.getElementById('pgvAliqResidencial').value) / 100 || 0.01, comercial: parseFloat(document.getElementById('pgvAliqComercial').value) / 100 || 0.015, industrial: parseFloat(document.getElementById('pgvAliqIndustrial').value) / 100 || 0.02, baldio: parseFloat(document.getElementById('pgvAliqBaldio').value) / 100 || 0.03, misto: parseFloat(document.getElementById('pgvAliqMisto').value) / 100 || 0.012, especial: parseFloat(document.getElementById('pgvAliqEspecial').value) / 100 || 0.005 }, // Encargos desconto_cota_unica: parseFloat(document.getElementById('pgvDescCotaUnica').value) / 100 || 0.10, multa_atraso: parseFloat(document.getElementById('pgvMultaAtraso').value) / 100 || 0.02, juros_mes: parseFloat(document.getElementById('pgvJurosMes').value) / 100 || 0.01, max_parcelas: parseInt(document.getElementById('pgvMaxParcelas').value) || 10, valor_minimo_parcela: parseFloat(document.getElementById('pgvMinParcela').value) || 50, // Fatores do terreno fatores_terreno: { topografia: { valores: { plano: parseFloat(document.getElementById('pgvTopoPlano').value) || 1.00, aclive: parseFloat(document.getElementById('pgvTopoAclive').value) || 0.95, declive: parseFloat(document.getElementById('pgvTopoDeclive').value) || 0.90, irregular: parseFloat(document.getElementById('pgvTopoIrregular').value) || 0.85, alagado: parseFloat(document.getElementById('pgvTopoAlagado').value) || 0.70 }, default: 1.00 }, situacao: { valores: { esquina: parseFloat(document.getElementById('pgvSitEsquina').value) || 1.15, 'meio de quadra': parseFloat(document.getElementById('pgvSitMeio').value) || 1.00, meio_quadra: parseFloat(document.getElementById('pgvSitMeio').value) || 1.00, vila: parseFloat(document.getElementById('pgvSitVila').value) || 0.90, encravado: parseFloat(document.getElementById('pgvSitEncravado').value) || 0.80, fundos: parseFloat(document.getElementById('pgvSitFundos').value) || 0.75 }, default: 1.00 }, limitacao: { valores: { muro: parseFloat(document.getElementById('pgvLimMuro').value) || 1.10, 'muro de alvenaria': parseFloat(document.getElementById('pgvLimMuro').value) || 1.10, gradil: parseFloat(document.getElementById('pgvLimGradil').value) || 1.05, grade: parseFloat(document.getElementById('pgvLimGradil').value) || 1.05, 'cerca de madeira': parseFloat(document.getElementById('pgvLimCercaMad').value) || 1.00, 'cerca de arame': parseFloat(document.getElementById('pgvLimCercaArame').value) || 0.95, cerca: parseFloat(document.getElementById('pgvLimCercaArame').value) || 0.95, sem: parseFloat(document.getElementById('pgvLimSem').value) || 0.90, inexistente: parseFloat(document.getElementById('pgvLimSem').value) || 0.90 }, default: 1.00 }, pedologia: { valores: { normal: parseFloat(document.getElementById('pgvPedoNormal').value) || 1.00, firme: parseFloat(document.getElementById('pgvPedoNormal').value) || 1.00, arenoso: parseFloat(document.getElementById('pgvPedoArenoso').value) || 0.90, rochoso: parseFloat(document.getElementById('pgvPedoRochoso').value) || 0.85, alagadico: parseFloat(document.getElementById('pgvPedoAlagadico').value) || 0.70 }, default: 1.00 } }, // Fatores da construção fatores_construcao: { tipo_construcao: { valores: { casa: parseFloat(document.getElementById('pgvTipoCasa').value) || 1.00, apartamento: parseFloat(document.getElementById('pgvTipoApto').value) || 1.10, sobrado: parseFloat(document.getElementById('pgvTipoSobrado').value) || 1.15, galpao: parseFloat(document.getElementById('pgvTipoGalpao').value) || 0.80, 'galpão': parseFloat(document.getElementById('pgvTipoGalpao').value) || 0.80, barracao: parseFloat(document.getElementById('pgvTipoBarracao').value) || 0.60, 'barracão': parseFloat(document.getElementById('pgvTipoBarracao').value) || 0.60 }, default: 1.00 }, padrao_construtivo: { valores: { luxo: parseFloat(document.getElementById('pgvPadraoLuxo').value) || 2.00, alto: parseFloat(document.getElementById('pgvPadraoAlto').value) || 1.50, medio: parseFloat(document.getElementById('pgvPadraoMedio').value) || 1.00, 'médio': parseFloat(document.getElementById('pgvPadraoMedio').value) || 1.00, popular: parseFloat(document.getElementById('pgvPadraoPopular').value) || 0.70, precario: parseFloat(document.getElementById('pgvPadraoPrecario').value) || 0.40, 'precário': parseFloat(document.getElementById('pgvPadraoPrecario').value) || 0.40 }, default: 1.00 }, estado_conservacao: { valores: { otimo: parseFloat(document.getElementById('pgvConservOtimo').value) || 1.00, 'ótimo': parseFloat(document.getElementById('pgvConservOtimo').value) || 1.00, bom: parseFloat(document.getElementById('pgvConservBom').value) || 0.95, regular: parseFloat(document.getElementById('pgvConservRegular').value) || 0.85, ruim: parseFloat(document.getElementById('pgvConservRuim').value) || 0.70, precario: parseFloat(document.getElementById('pgvConservPrecario').value) || 0.50, 'precário': parseFloat(document.getElementById('pgvConservPrecario').value) || 0.50 }, default: 0.85 }, estrutura: { valores: { concreto: parseFloat(document.getElementById('pgvEstrutConcreto').value) || 1.10, 'concreto armado': parseFloat(document.getElementById('pgvEstrutConcreto').value) || 1.10, metalica: parseFloat(document.getElementById('pgvEstrutMetalica').value) || 1.05, 'metálica': parseFloat(document.getElementById('pgvEstrutMetalica').value) || 1.05, alvenaria: parseFloat(document.getElementById('pgvEstrutAlvenaria').value) || 1.00, mista: parseFloat(document.getElementById('pgvEstrutMista').value) || 0.90, madeira: parseFloat(document.getElementById('pgvEstrutMadeira').value) || 0.80 }, default: 1.00 }, cobertura: { valores: { laje: parseFloat(document.getElementById('pgvCobertLaje').value) || 1.20, 'telha ceramica': parseFloat(document.getElementById('pgvCobertCeramica').value) || 1.10, 'telha cerâmica': parseFloat(document.getElementById('pgvCobertCeramica').value) || 1.10, 'telha concreto': parseFloat(document.getElementById('pgvCobertConcreto').value) || 1.05, fibrocimento: parseFloat(document.getElementById('pgvCobertFibro').value) || 0.90, amianto: parseFloat(document.getElementById('pgvCobertFibro').value) || 0.90, zinco: parseFloat(document.getElementById('pgvCobertZinco').value) || 0.85 }, default: 1.00 }, piso: { valores: { porcelanato: parseFloat(document.getElementById('pgvPisoPorcelanato').value) || 1.25, ceramica: parseFloat(document.getElementById('pgvPisoCeramica').value) || 1.10, 'cerâmica': parseFloat(document.getElementById('pgvPisoCeramica').value) || 1.10, madeira: parseFloat(document.getElementById('pgvPisoMadeira').value) || 1.15, cimento: parseFloat(document.getElementById('pgvPisoCimento').value) || 0.85, 'cimento queimado': parseFloat(document.getElementById('pgvPisoCimento').value) || 0.85, terra: parseFloat(document.getElementById('pgvPisoTerra').value) || 0.60 }, default: 1.00 }, instalacao_eletrica: { valores: { embutida: parseFloat(document.getElementById('pgvEletEmbutida').value) || 1.10, aparente: parseFloat(document.getElementById('pgvEletAparente').value) || 0.95, inexistente: parseFloat(document.getElementById('pgvEletInexist').value) || 0.80 }, default: 1.00 }, instalacao_sanitaria: { valores: { interna: parseFloat(document.getElementById('pgvSanitInterna').value) || 1.10, externa: parseFloat(document.getElementById('pgvSanitExterna').value) || 0.90, inexistente: parseFloat(document.getElementById('pgvSanitInexist').value) || 0.70 }, default: 1.00 } } }; // Enviar para o backend const workspace = getWorkspaceAtual(); config.workspace = workspace; console.log('📋 Salvando PGV para workspace:', workspace, config); const resp = await fetch(`${backendUrl}/api/iptu/pgv?workspace=${encodeURIComponent(workspace)}`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...window.ngrokHeaders }, body: JSON.stringify(config) }); const data = await resp.json(); if (data.success) { alert('✅ Planta Genérica de Valores salva com sucesso!\n\nTodos os fatores e alíquotas foram atualizados.'); // Fechar modal fecharConfigPGV(); // Recarregar estatísticas e lotes IPTU para refletir alterações imediatamente try { await carregarEstatisticasIptu(); await carregarLotesIptuMapa(); } catch(e) { console.warn('Erro ao recarregar estatísticas/lotes IPTU:', e); } } else { alert('❌ Erro ao salvar: ' + (data.error || 'Erro desconhecido')); } } catch (e) { console.error('❌ Erro ao salvar PGV:', e); alert('❌ Erro ao salvar configurações: ' + e.message); } } // Manter função antiga para compatibilidade async function salvarConfigPGV() { return salvarConfigPGVCompleta(); } // ============================================ // FIM FUNÇÕES PGV // ============================================ // Variável global para rastrear lote selecionado na camada let selectedIptuLayerGlobal = null; // Carregar lotes com status IPTU no mapa PRINCIPAL async function carregarLotesIptuMapa() { if (!map) { logWarning('Mapa principal não inicializado'); return; } const workspace = getWorkspaceAtual(); console.log(`🗺️ Carregando lotes IPTU para workspace: ${workspace}`); try { // Buscar dados de IPTU com geometria const exercicio = document.getElementById('iptuExercicio')?.value || new Date().getFullYear(); const resp = await fetch(`${backendUrl}/api/iptu/consultar?exercicio=${exercicio}&workspace=${workspace}`, { headers: window.ngrokHeaders }); const iptuData = await resp.json(); if (!iptuData.success || !iptuData.resultados || iptuData.resultados.length === 0) { logWarning('Sem dados de IPTU'); // Tentar carregar lotes do GeoServer diretamente await carregarLotesBasicos(); return; } // Remover camada anterior do mapa principal if (iptuLotesLayer) { map.removeLayer(iptuLotesLayer); } // Cores por status const cores = { 'pago': '#22c55e', 'parcial': '#eab308', 'pendente': '#f97316', 'vencido': '#ef4444', 'isento': '#6b7280', 'default': '#94a3b8' }; // Labels de status const statusLabels = { 'pago': '✅ Pago', 'parcial': '⚠️ Parcial', 'pendente': '🕐 Pendente', 'vencido': '❌ Vencido', 'isento': '🔵 Isento' }; // Converter resultados para GeoJSON const features = iptuData.resultados .filter(r => r.geometry) .map(r => ({ type: 'Feature', geometry: r.geometry, properties: r })); const geojson = { type: 'FeatureCollection', features: features }; // Variável para rastrear lote selecionado let selectedIptuLayer = null; // Criar camada GeoJSON com estilo dinâmico iptuLotesLayer = L.geoJSON(geojson, { style: function(feature) { const props = feature.properties || {}; const cor = props.cor || cores[props.status] || cores.default; return { color: '#1e3a5f', weight: 2, fillColor: cor, fillOpacity: 0.6 }; }, onEachFeature: function(feature, layer) { const props = feature.properties || {}; const statusLabel = statusLabels[props.status] || '❓ Desconhecido'; const cor = props.cor || cores[props.status] || cores.default; // Popup com estilo melhorado e fundo sólido branco layer.bindPopup(`
🏠 Quadra ${props.quadra || 'A'} - Lote ${props.lote || '-'}
${props.inscricao || '-'}
Proprietário: ${props.proprietario || '-'}
Bairro: ${props.bairro || '-'}
Área: ${(props.area_terreno || 0).toLocaleString('pt-BR')} m²

Status: ${statusLabel}
IPTU ${props.exercicio || new Date().getFullYear()}: R$ ${(props.valor_iptu || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
Pago: R$ ${(props.valor_pago || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
Devido: R$ ${(props.valor_devido || 0).toLocaleString('pt-BR', {minimumFractionDigits: 2})}
`, { className: 'iptu-popup', maxWidth: 300, offset: [0, -15] }); // Salvar referência do layer nas properties para busca posterior layer._iptuQuadra = props.quadra; layer._iptuLote = props.lote; layer._iptuInscricao = props.inscricao; // Eventos de hover e clique layer.on({ mouseover: function(e) { if (selectedIptuLayerGlobal !== layer) { layer.setStyle({ weight: 3, color: '#fbbf24' }); } }, mouseout: function(e) { if (selectedIptuLayerGlobal !== layer) { layer.setStyle({ weight: 2, color: '#1e3a5f' }); } }, click: function(e) { // Fechar popups anteriores map.closePopup(); destacarLoteNaCamada(layer, cores); // Abrir popup e garantir que fique acima setTimeout(() => { layer.openPopup(); // Forçar popup acima de tudo const popupPane = document.querySelector('.leaflet-popup-pane'); if (popupPane) { popupPane.style.zIndex = '99999'; } const popup = document.querySelector('.leaflet-popup'); if (popup) { popup.style.zIndex = '100000'; } }, 50); } }); } }).addTo(map); // Ajustar visualização no mapa principal if (features.length > 0) { map.fitBounds(iptuLotesLayer.getBounds(), { padding: [20, 20] }); } console.log(`🗺️ ${features.length} lotes carregados no mapa IPTU`); } catch (error) { console.error('Erro ao carregar lotes IPTU:', error); } } // Função para destacar lote na camada (usada tanto pelo clique no mapa quanto no card) function destacarLoteNaCamada(layer, cores) { if (!layer || !iptuLotesLayer) return; const coresDefault = cores || { 'pago': '#22c55e', 'parcial': '#eab308', 'pendente': '#f97316', 'vencido': '#ef4444', 'isento': '#6b7280', 'default': '#94a3b8' }; // Restaurar estilo do anterior if (selectedIptuLayerGlobal && selectedIptuLayerGlobal !== layer) { const oldCor = selectedIptuLayerGlobal.feature?.properties?.cor || coresDefault.default; selectedIptuLayerGlobal.setStyle({ weight: 2, color: '#1e3a5f', fillColor: oldCor, fillOpacity: 0.6 }); } // Destacar lote selecionado selectedIptuLayerGlobal = layer; layer.setStyle({ weight: 4, color: '#fbbf24', fillColor: '#fef08a', fillOpacity: 0.8 }); layer.bringToFront(); // Abrir popup layer.openPopup(); // Centralizar no mapa if (layer.getBounds) { map.fitBounds(layer.getBounds(), { padding: [100, 100], maxZoom: 18 }); } // Atualizar seleção no painel lateral const props = layer.feature?.properties; if (props?.inscricao) { // Encontrar e selecionar o card correspondente const idx = iptuResultados.findIndex(r => r.inscricao === props.inscricao); if (idx >= 0) { document.querySelectorAll('.iptu-result-card').forEach((c, i) => { c.classList.toggle('selected', i === idx); }); iptuSelectedLote = iptuResultados[idx]; } } } // Função para encontrar e destacar lote pelo card (quadra/lote) function destacarLotePorDados(quadra, lote) { if (!iptuLotesLayer) return; iptuLotesLayer.eachLayer(layer => { if (layer._iptuQuadra == quadra && layer._iptuLote == lote) { destacarLoteNaCamada(layer); } }); } // Carregar lotes básicos (sem dados de IPTU) - usa mapa principal async function carregarLotesBasicos() { if (!map) return; const workspace = getWorkspaceAtual(); try { const wfsUrl = `http://localhost:8080/geoserver/${workspace}/ows`; const params = new URLSearchParams({ service: 'WFS', version: '2.0.0', request: 'GetFeature', typeName: `${workspace}:Lote`, outputFormat: 'application/json', srsName: 'EPSG:4326' }); const resp = await fetch(`${wfsUrl}?${params}`); const data = await resp.json(); if (data.features && data.features.length > 0) { if (iptuLotesLayer) { map.removeLayer(iptuLotesLayer); } iptuLotesLayer = L.geoJSON(data, { style: { color: '#94a3b8', weight: 1, fillColor: '#94a3b8', fillOpacity: 0.3 } }).addTo(map); map.fitBounds(iptuLotesLayer.getBounds(), { padding: [20, 20] }); } } catch (e) { console.error('Erro ao carregar lotes básicos:', e); } } // Cache de autocomplete para evitar requisições repetidas let autocompleteCache = {}; let autocompleteTimeout = null; // Carregar autocomplete para campos async function carregarAutocomplete(campo, termo) { // Debounce - espera 300ms após parar de digitar clearTimeout(autocompleteTimeout); if (!termo || termo.length < 1) return; const workspace = getWorkspaceAtual(); autocompleteTimeout = setTimeout(async () => { const cacheKey = `${campo}_${termo}_${workspace}`; if (autocompleteCache[cacheKey]) { preencherDatalist(campo, autocompleteCache[cacheKey]); return; } try { const resp = await fetch(`${backendUrl}/api/iptu/autocomplete/${campo}?q=${encodeURIComponent(termo)}&workspace=${workspace}`, { headers: window.ngrokHeaders }); const data = await resp.json(); if (data.success && data.valores) { autocompleteCache[cacheKey] = data.valores; preencherDatalist(campo, data.valores); } } catch (e) { console.error('Erro no autocomplete:', e); } }, 300); } // Preencher datalist com valores function preencherDatalist(campo, valores) { const mapId = { 'quadra': 'iptuQuadraList', 'lote': 'iptuLoteList', 'bairro': 'iptuBairroList', 'proprietario': 'iptuProprietarioList' }; const datalist = document.getElementById(mapId[campo]); if (!datalist) return; datalist.innerHTML = valores.map(v => `