UNPKG

@horizon-modules/jetimob-crm-integration

Version:

Integração CRM Jetimob para conversão de dados imobiliários para property-model-v3

1,347 lines (1,336 loc) 189 kB
"use strict"; "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { JetimobApiClient: () => JetimobApiClient, JetimobDownloader: () => JetimobDownloader, ProfilerService: () => ProfilerService, convertJetimobToPropertyV3: () => convertJetimobToPropertyV3, fakeData: () => fakeData }); module.exports = __toCommonJS(index_exports); // src/converter/discoverers/discoverOperation.ts function discoverOperation(imovel) { if (imovel.contrato) { return [imovel.contrato]; } return []; } // src/converter/discoverers/discoverAttributes.ts function discoverAttributes(imovel) { const attributes = {}; const operacao = discoverOperation(imovel); if (operacao.length > 0) attributes.operacao = operacao; if (imovel.valor_venda) { const valor = Number(imovel.valor_venda); if (!isNaN(valor) && valor > 0) attributes.valor_venda = valor; } if (imovel.valor_locacao && imovel.valor_locacao_visivel) { const valor = Number(imovel.valor_locacao); if (!isNaN(valor) && valor > 0) attributes.valor_aluguel = valor; } if (imovel.valor_temporada && imovel.valor_temporada_visivel) { const valor = Number(imovel.valor_temporada); if (!isNaN(valor) && valor > 0) attributes.valor_temporada = valor; } if (imovel.valor_condominio && imovel.valor_condominio_visivel) { const valor = Number(imovel.valor_condominio); if (!isNaN(valor) && valor >= 0) attributes.valor_condominio = valor; } if (imovel.valor_iptu && imovel.valor_iptu_visivel) { const valor = Number(imovel.valor_iptu); if (!isNaN(valor) && valor >= 0) attributes.valor_iptu = valor; } if (imovel.area_total) { const area = Number(imovel.area_total); if (!isNaN(area) && area > 0) attributes.area_total = area; } if (imovel.area_privativa) { const area = Number(imovel.area_privativa); if (!isNaN(area) && area > 0) attributes.area_privativa = area; } if (imovel.dormitorios) attributes.dormitorios = Number(imovel.dormitorios); if (imovel.suites) attributes.suites = Number(imovel.suites); if (imovel.banheiros) attributes.banheiros = Number(imovel.banheiros); if (imovel.garagens) attributes.vagas_garagem = Number(imovel.garagens); if (imovel.tipo) attributes.finalidade = imovel.tipo; if (imovel.subtipo) attributes.tipo = imovel.subtipo; if (imovel.endereco_cep) attributes.endereco_cep = imovel.endereco_cep; if (imovel.endereco_estado) attributes.endereco_estado = imovel.endereco_estado; if (imovel.endereco_cidade) attributes.endereco_cidade = imovel.endereco_cidade; if (imovel.endereco_bairro) attributes.endereco_bairro = imovel.endereco_bairro; if (imovel.endereco_logradouro) attributes.endereco_logradouro = imovel.endereco_logradouro; if (imovel.endereco_numero) attributes.endereco_numero = imovel.endereco_numero; if (imovel.endereco_complemento) attributes.endereco_complemento = imovel.endereco_complemento; if (imovel.endereco_referencia) attributes.endereco_referencia = imovel.endereco_referencia; if (imovel.andar) attributes.andar = Number(imovel.andar); const geoValue = imovel.geoposicionamento_visivel ? Number(imovel.geoposicionamento_visivel) : 0; if (imovel.latitude && imovel.longitude && geoValue !== 0) { const lat = Number(imovel.latitude); const lng = Number(imovel.longitude); if (!isNaN(lat) && !isNaN(lng) && lat !== 0 && lng !== 0) { attributes.latitude = lat; attributes.longitude = lng; } } if (String(imovel.mobiliado) === "1" || String(imovel.mobiliado) === "true") { attributes.mobiliado = true; } if (String(imovel.financiavel) === "1" || String(imovel.financiavel) === "true") { attributes.financiavel = true; } if (String(imovel.exclusividade) === "true" || String(imovel.exclusividade) === "1") { attributes.exclusividade = true; } if (String(imovel.permuta) === "true" || String(imovel.permuta) === "1") { attributes.permuta = true; } if (String(imovel.seguro_fianca) === "true" || String(imovel.seguro_fianca) === "1") { attributes.seguro_fianca = true; } if (imovel.imovel_comodidades) { const comodidadesArray = String(imovel.imovel_comodidades).split(",").map((c) => c.trim()).filter((c) => c.length > 0); if (comodidadesArray.length > 0) { attributes.comodidades = comodidadesArray; } } if (imovel.situacao) attributes.situacao = imovel.situacao; if (imovel.destaque && imovel.destaque !== "Sem destaque") { if (imovel.destaque === "Destaque" || imovel.destaque === "true" || String(imovel.destaque) === "true") { attributes.destaque = true; } } if (imovel.distancia_mar) attributes.distancia_mar = Number(imovel.distancia_mar); if (imovel.posicao_solar) attributes.posicao_solar = imovel.posicao_solar; if (imovel.id_corretor) attributes.corretor_id = String(imovel.id_corretor); if (imovel.updated_at) attributes.data_atualizacao = imovel.updated_at; if (imovel.tags) attributes.tags = imovel.tags; if (imovel.status) attributes.status = imovel.status; if (imovel.valor_seguro_incendio) { const valor = Number(imovel.valor_seguro_incendio); if (!isNaN(valor) && valor >= 0) attributes.valor_seguro_incendio = valor; } if (imovel.valor_taxa_limpeza) { const valor = Number(imovel.valor_taxa_limpeza); if (!isNaN(valor) && valor >= 0) attributes.valor_taxa_limpeza = valor; } if (imovel.calendario_temporada) attributes.calendario_temporada = imovel.calendario_temporada; if (imovel.condominio_comodidades) attributes.condominio_comodidades = imovel.condominio_comodidades; return attributes; } // src/converter/discoverers/discoverMedia.ts function discoverMedias(imovel) { const images = imovel.imagens?.map((imagem, index) => ({ full: imagem.link, md: imagem.link_thumb || imagem.link, sm: imagem.link_thumb || imagem.link, cover: index === 0 // primeira imagem como cover })) ?? []; const videos = imovel.videos && imovel.videos.length > 0 ? imovel.videos.map((url) => generateVideo(url)) : []; const virtual_tours = imovel.tour360 ? [{ embed_url: imovel.tour360 }] : []; const documents = imovel.plantas && imovel.plantas.length > 0 ? imovel.plantas.map((plantaUrl, index) => ({ name: `Planta ${index + 1}`, url: plantaUrl })) : []; const result = {}; if (images.length > 0) result.images = images; if (videos.length > 0) result.videos = videos; if (virtual_tours.length > 0) result.virtual_tours = virtual_tours; if (documents.length > 0) result.documents = documents; return result; } function generateVideo(url) { try { const parsed = new URL(url); const host = parsed.hostname; if (host.includes("youtube.com") || host.includes("youtu.be")) { const id = parsed.searchParams.get("v") || parsed.pathname.split("/").pop(); return { provider: "youtube", id: id || void 0, embed_url: `https://www.youtube.com/embed/${id}` }; } return { embed_url: url }; } catch (e) { return { embed_url: url }; } } // src/converter/discoverers/discoverSettings.ts function discoverSettings(imovel) { return { currency_unit: "BRL", // sempre BRL no Jetimob area_unit: imovel.medida === "m\xB2" ? "m2" : imovel.medida || "m2", // mapeia m² -> m2 distance_unit: "meters", exibir_no_mapa: true }; } // src/converter/discoverers/discoverSEO.ts function discoverSEO(imovel) { const seo = {}; if (imovel.meta_title) { seo.meta_title = imovel.meta_title; } if (imovel.meta_description) { seo.meta_description = imovel.meta_description; } if (Object.keys(seo).length > 0) { return seo; } return void 0; } // src/converter/index.ts function convertJetimobToPropertyV3(imovel) { const seo = discoverSEO(imovel); return { // Identificação básica reference: imovel.codigo ?? "", title: imovel.titulo_anuncio ?? "", description: imovel.observacoes ?? "", // SEO (apenas se houver dados) ...seo && { seo }, // Descoberta de atributos complexos (inclui operação) attributes: discoverAttributes(imovel), // Descoberta de mídias (imagens) media_assets: discoverMedias(imovel), // Descoberta de configurações e visibilidade settings: discoverSettings(imovel), // Data de atualização updated_at: imovel.updated_at || (/* @__PURE__ */ new Date()).toISOString() }; } // src/services/JetimobApiClient.ts var JetimobApiClient = class { constructor(config) { this.webserviceKey = config.webserviceKey; this.baseUrl = config.baseUrl || "https://api.jetimob.com"; } async request(endpoint) { const url = `${this.baseUrl}${endpoint}`; console.log(`\u{1F517} Requisi\xE7\xE3o para: ${url}`); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 1e4); const response = await fetch(url, { signal: controller.signal }); clearTimeout(timeoutId); console.log(`\u{1F4CA} Status: ${response.status}`); if (!response.ok) { const errorText = await response.text(); throw new Error( `Erro na requisi\xE7\xE3o: ${response.status} - ${response.statusText} - ${errorText}` ); } return response.json(); } async getImoveis(page = 1, pageSize = 50) { return this.request( `/webservice/${this.webserviceKey}/imoveis?v=4&page=${page}&pageSize=${pageSize}` ); } async getImovel(id) { return this.request(`/webservice/${this.webserviceKey}/imoveis/${id}`); } async testConnection() { try { console.log("\u{1F680} Testando conex\xE3o com API Jetimob..."); console.log(`\u{1F4CD} Base URL: ${this.baseUrl}`); console.log(`\u{1F511} Webservice Key: ${this.webserviceKey.substring(0, 10)}...`); const result = await this.getImoveis(1, 1); console.log("\u2705 Conex\xE3o bem sucedida!"); return result; } catch (error) { console.error("\u274C Erro na conex\xE3o:", error); throw error; } } }; // src/services/JetimobDownloader.ts var import_fs = require("fs"); var import_path = require("path"); var JetimobDownloader = class { constructor(config) { this.apiClient = new JetimobApiClient({ webserviceKey: config.webserviceKey, baseUrl: config.baseUrl || "https://api.jetimob.com" }); this.outputDir = config.outputDir; } async ensureOutputDir() { try { await import_fs.promises.access(this.outputDir); } catch { await import_fs.promises.mkdir(this.outputDir, { recursive: true }); } } async savePageData(page, data) { const properties = data?.data || data; if (!Array.isArray(properties) || properties.length === 0) { console.log(` \u{1F4C4} P\xE1gina ${page} vazia, n\xE3o salvando arquivo`); return false; } const fileName = `page-${page}.json`; const filePath = (0, import_path.join)(this.outputDir, fileName); await import_fs.promises.writeFile(filePath, JSON.stringify(properties, null, 2), "utf8"); return true; } async downloadPage(page, pageSize = 100) { await this.ensureOutputDir(); const response = await this.apiClient.getImoveis(page, pageSize); await this.savePageData(page, response); } async downloadPages(options = {}) { const { startPage = 1, endPage, maxPages, pageSize = 100 } = options; await this.ensureOutputDir(); const errors = []; let totalPages = 0; let totalItems = 0; let downloadedItems = 0; try { console.log(`\u{1F50D} Descobrindo total de p\xE1ginas dispon\xEDveis...`); const firstPageResponse = await this.apiClient.getImoveis(1, pageSize); const firstPage = firstPageResponse?.data || firstPageResponse; if (!Array.isArray(firstPage) || firstPage.length === 0) { console.log(`\u26A0\uFE0F Primeira p\xE1gina vazia, finalizando download`); return { totalPages: 1, totalItems: 0, downloadedItems: 0, errors }; } totalPages = firstPageResponse?.totalPages || 0; totalItems = firstPageResponse?.total || firstPage.length; console.log(`\u{1F4CA} Total de p\xE1ginas: ${totalPages}`); console.log(`\u{1F4CA} Total de im\xF3veis: ${totalItems}`); if (startPage === 1) { const saved = await this.savePageData(1, firstPageResponse); if (saved) { downloadedItems += firstPage.length; console.log(` \u{1F4C4} P\xE1gina 1: ${firstPage.length} im\xF3veis salvos`); } } let currentPage = Math.max(startPage, 2); let hasMorePages = true; const finalEndPage = endPage || (maxPages ? Math.min(startPage + maxPages - 1, totalPages) : totalPages); while (hasMorePages && currentPage <= finalEndPage) { try { console.log(`\u{1F504} Baixando p\xE1gina ${currentPage}...`); const response = await this.apiClient.getImoveis(currentPage, pageSize); const properties = response?.data || response; if (!Array.isArray(properties) || properties.length === 0) { console.log(`\u26A0\uFE0F P\xE1gina ${currentPage} vazia, finalizando download`); hasMorePages = false; break; } const saved = await this.savePageData(currentPage, response); if (saved) { downloadedItems += properties.length; console.log(` \u{1F4C4} P\xE1gina ${currentPage}: ${properties.length} im\xF3veis salvos`); } currentPage++; totalPages = currentPage - 1; await new Promise((resolve) => setTimeout(resolve, 200)); } catch (error) { const errorMsg = `Erro ao baixar p\xE1gina ${currentPage}: ${error instanceof Error ? error.message : String(error)}`; errors.push(errorMsg); console.error(` \u274C ${errorMsg}`); try { console.log(`\u{1F504} Tentando novamente p\xE1gina ${currentPage}...`); await new Promise((resolve) => setTimeout(resolve, 1e3)); const response = await this.apiClient.getImoveis(currentPage, pageSize); if (Array.isArray(response) && response.length > 0) { const saved = await this.savePageData(currentPage, response); if (saved) { downloadedItems += response.length; totalItems += response.length; console.log(` \u{1F4C4} P\xE1gina ${currentPage}: ${response.length} im\xF3veis salvos (retry)`); } currentPage++; totalPages = currentPage - 1; } else { hasMorePages = false; } } catch (retryError) { const retryErrorMsg = `Erro na segunda tentativa p\xE1gina ${currentPage}: ${retryError instanceof Error ? retryError.message : String(retryError)}`; errors.push(retryErrorMsg); console.error(` \u274C ${retryErrorMsg}`); hasMorePages = false; } } } } catch (error) { const errorMsg = `Erro geral no download: ${error instanceof Error ? error.message : String(error)}`; errors.push(errorMsg); console.error(`\u274C ${errorMsg}`); } console.log(` \u{1F4CA} RESUMO DO DOWNLOAD:`); console.log(`\u{1F4C4} Total de p\xE1ginas: ${totalPages}`); console.log(`\u{1F4E6} Total de im\xF3veis: ${totalItems}`); console.log(`\u2705 Im\xF3veis baixados: ${downloadedItems}`); if (errors.length > 0) { console.log(`\u274C Erros: ${errors.length}`); } return { totalPages, totalItems, downloadedItems, errors }; } async downloadAll(pageSize = 100) { console.log(`\u{1F680} Iniciando download completo da API Jetimob...`); console.log(`\u{1F4C4} Tamanho da p\xE1gina: ${pageSize} im\xF3veis`); return this.downloadPages({ pageSize }); } async uploadToApi(uploadConfig) { const { endpoint, headers = {}, convertData = true } = uploadConfig; const errors = []; let totalProcessed = 0; let totalSent = 0; let totalErrors = 0; try { const files = await import_fs.promises.readdir(this.outputDir); const jsonFiles = files.filter((file) => file.endsWith(".json")).sort((a, b) => { const numA = parseInt(a.match(/page-(\d+)/)?.[1] || "0"); const numB = parseInt(b.match(/page-(\d+)/)?.[1] || "0"); return numA - numB; }); console.log(` \u{1F4C1} Encontrados ${jsonFiles.length} arquivos para processar`); for (const file of jsonFiles) { console.log(` \u{1F4C4} Processando ${file}...`); try { const filePath = (0, import_path.join)(this.outputDir, file); const content = await import_fs.promises.readFile(filePath, "utf8"); const pageData = JSON.parse(content); if (!Array.isArray(pageData) || pageData.length === 0) { console.log(" \u{1F4C4} Arquivo vazio, pulando..."); continue; } const properties = pageData; console.log(` \u{1F4CA} ${properties.length} im\xF3veis encontrados`); totalProcessed += properties.length; try { let payload; if (convertData) { payload = { properties, source: "jetimob" }; } else { payload = { properties }; } const response = await fetch(endpoint, { method: "PUT", headers: { "Content-Type": "application/json", ...headers }, body: JSON.stringify(payload) }); if (response.ok) { const result = await response.json(); totalSent += properties.length; console.log(` \u2705 Sucesso: ${properties.length} im\xF3veis enviados`); console.log(` \u{1F4DD} ${result.message || "Processado com sucesso"}`); } else { const errorText = await response.text(); totalErrors += properties.length; const errorMsg = `Erro ${response.status} ao enviar arquivo ${file}: ${errorText}`; errors.push(errorMsg); console.log(` \u274C ${errorMsg}`); break; } } catch (error) { totalErrors += properties.length; const errorMsg = `Erro de rede ao enviar arquivo ${file}: ${error instanceof Error ? error.message : String(error)}`; errors.push(errorMsg); console.error(` \u274C ${errorMsg}`); break; } await new Promise((resolve) => setTimeout(resolve, 500)); } catch (error) { const errorMsg = `Erro ao processar arquivo ${file}: ${error instanceof Error ? error.message : String(error)}`; errors.push(errorMsg); console.error(` \u274C ${errorMsg}`); } } } catch (error) { const errorMsg = `Erro ao acessar diret\xF3rio ${this.outputDir}: ${error instanceof Error ? error.message : String(error)}`; errors.push(errorMsg); console.error(`\u274C ${errorMsg}`); } console.log("\n\u{1F389} RESUMO FINAL DO UPLOAD:"); console.log(`\u{1F4CA} Total processado: ${totalProcessed} im\xF3veis`); console.log(`\u2705 Sucessos: ${totalSent} im\xF3veis`); console.log(`\u274C Erros: ${totalErrors} im\xF3veis`); if (totalProcessed > 0) { console.log(`\u{1F4C8} Taxa de sucesso: ${Math.round(totalSent / totalProcessed * 100)}%`); } return { totalProcessed, totalSent, totalErrors, errors }; } async downloadAndUpload(downloadOptions, uploadConfig) { console.log(`\u{1F680} Iniciando processo completo: Download + Upload`); const downloadResult = await this.downloadPages(downloadOptions); if (downloadResult.downloadedItems > 0) { console.log(` \u{1F4E4} Iniciando upload de ${downloadResult.downloadedItems} im\xF3veis...`); const uploadResult = await this.uploadToApi(uploadConfig); return { downloadResult, uploadResult }; } else { console.log(`\u26A0\uFE0F Nenhum im\xF3vel foi baixado, pulando upload`); return { downloadResult, uploadResult: { totalProcessed: 0, totalSent: 0, totalErrors: 0, errors: ["Nenhum dado para enviar"] } }; } } }; // src/services/ProfilerService.ts var fs2 = __toESM(require("fs")); var path = __toESM(require("path")); var ProfilerService = class { constructor(config) { this.fieldData = /* @__PURE__ */ new Map(); this.fieldExamples = /* @__PURE__ */ new Map(); this.config = { outputFileName: "profiling-report.json", defaultMaxExamples: 10, verbose: false, uniqueField: "id", dataLabel: "items", serviceLabel: "Data", ...config }; } async profile() { if (this.config.verbose) { console.log(`\u{1F4CA} Iniciando profiling dos dados ${this.config.serviceLabel}...`); } if (!fs2.existsSync(this.config.inputDir)) { throw new Error(`Diret\xF3rio de entrada n\xE3o encontrado: ${this.config.inputDir}`); } const data = await this.loadData(); this.processData(data); const result = this.generateResult(); await this.saveResult(result); return result; } async loadData() { const files = fs2.readdirSync(this.config.inputDir).filter((file) => file.endsWith(".json")).sort(); if (this.config.verbose) { console.log(`\u{1F4C1} Carregando ${files.length} arquivos ${this.config.serviceLabel}...`); } const allData = []; for (const file of files) { const filePath = path.join(this.config.inputDir, file); const content = fs2.readFileSync(filePath, "utf8"); const fileData = JSON.parse(content); if (Array.isArray(fileData)) { allData.push(...fileData); if (this.config.verbose) { console.log(` \u2705 ${file}: ${fileData.length} ${this.config.dataLabel}`); } } else if (fileData.data && Array.isArray(fileData.data)) { allData.push(...fileData.data); if (this.config.verbose) { console.log(` \u2705 ${file}: ${fileData.data.length} ${this.config.dataLabel}`); } } } if (this.config.verbose) { console.log(`\u{1F50D} Analisando ${allData.length} ${this.config.dataLabel} ${this.config.serviceLabel}...`); } return allData; } processData(data) { data.forEach((item, index) => { if (this.config.verbose && (index + 1) % 100 === 0) { console.log(` Processando ${index + 1}/${data.length}...`); } this.processObject(item, ""); }); } processObject(obj, prefix) { for (const [key, value] of Object.entries(obj)) { const fieldName = prefix ? `${prefix}.${key}` : key; if (value === null || value === void 0) { continue; } if (Array.isArray(value)) { this.processArrayField(fieldName, value); } else if (typeof value === "object") { this.processObject(value, fieldName); } else { this.processSimpleField(fieldName, value); } } } processArrayField(fieldName, array) { for (const item of array) { if (item !== null && item !== void 0) { if (typeof item === "object") { this.processObject(item, fieldName); } else { this.processSimpleField(fieldName, item); } } } } processSimpleField(fieldName, value) { if (!this.fieldData.has(fieldName)) { this.fieldData.set(fieldName, /* @__PURE__ */ new Set()); this.fieldExamples.set(fieldName, []); } const valueSet = this.fieldData.get(fieldName); const examples = this.fieldExamples.get(fieldName); valueSet.add(value); const fieldConfig = this.config.fieldConfigs?.[fieldName] || {}; const maxExamples = fieldConfig.maxExamples ?? this.config.defaultMaxExamples; if (!examples.includes(value) && examples.length < maxExamples) { examples.push(value); } } generateResult() { const result = {}; const sortedFields = Array.from(this.fieldData.keys()).sort(); for (const fieldName of sortedFields) { const examples = this.fieldExamples.get(fieldName) || []; const sortedExamples = examples.sort((a, b) => { const aStr = String(a); const bStr = String(b); return aStr.localeCompare(bStr); }); result[fieldName] = sortedExamples; } return result; } async saveResult(result) { if (!fs2.existsSync(this.config.outputDir)) { fs2.mkdirSync(this.config.outputDir, { recursive: true }); } const outputPath = path.join(this.config.outputDir, this.config.outputFileName); const jsonContent = JSON.stringify(result, null, 2); fs2.writeFileSync(outputPath, jsonContent, "utf8"); if (this.config.verbose) { const sizeKB = (jsonContent.length / 1024).toFixed(1); console.log(`\u2705 Profiling ${this.config.serviceLabel} salvo em: ${outputPath} (${sizeKB}KB)`); console.log(`\u{1F4CA} Total de campos analisados: ${Object.keys(result).length}`); } } /** * Método para analisar apenas dados únicos * Remove duplicatas baseado no campo configurado em uniqueField */ async profileUnique() { const allData = await this.loadData(); const uniqueData = Array.from( new Map(allData.map((item) => [item[this.config.uniqueField], item])).values() ); if (this.config.verbose) { console.log(`\u{1F50D} Removidas ${allData.length - uniqueData.length} duplicatas`); console.log(`\u{1F4CA} Analisando ${uniqueData.length} ${this.config.dataLabel} \xFAnicos...`); } this.processData(uniqueData); const result = this.generateResult(); await this.saveResult(result); return result; } }; // src/data/fake-data/apartamentos.json var apartamentos_default = [ { codigo: "FAKE_APARTAMENTOS_001", titulo_anuncio: "Apartamento Centro - 2 dorm, 2 su\xEDtes, 165m\xB2, 1 vaga", observacoes: "Apartamento com vista para o mar em Torres, a praia mais linda do RS.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2024-01-06 13:26:01", data_update: "2025-03-03 00:46:23", data_atualizacao: "2025-03-16 10:42:00", updated_at: "2025-03-19 08:18:57", dormitorios: 2, suites: 2, banheiros: 3, garagens: 1, area_total: 150, area_privativa: 165, andar: 1, valor_venda: 28e4, endereco_bairro: "Vila S\xE3o Jo\xE3o", endereco_logradouro: "Rua J\xFAlio de Castilhos", endereco_numero: "491", endereco_cep: "95560-070", endereco_complemento: "Apto 1", latitude: -29.342936776054497, longitude: -49.72035923955464, imovel_comodidades: "Sacada,Pr\xF3ximo \xE0 praia,\xC1rea de servi\xE7o,Churrasqueira,Lavabo", imagens: [ { link: "https://images.unsplash.com/photo-1565183997392-2f6f122e5912?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1565183997392-2f6f122e5912?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1568605117036-5fe5e7bab0b7?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1568605117036-5fe5e7bab0b7?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_002", titulo_anuncio: "Apartamento Centro - 4 dorm, 3 su\xEDtes, 165m\xB2, 3 vagas", observacoes: "Unidade moderna pr\xF3xima ao Centro Hist\xF3rico e ao Farol de Torres.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2024-03-10 02:20:33", data_update: "2025-05-18 22:01:44", data_atualizacao: "2025-04-13 15:23:31", updated_at: "2025-01-14 22:31:12", dormitorios: 4, suites: 3, banheiros: 3, garagens: 3, area_total: 105, area_privativa: 165, andar: 10, valor_venda: 85e4, endereco_bairro: "Vila S\xE3o Jo\xE3o", endereco_logradouro: "Rua Jo\xE3o Neves da Fontoura", endereco_numero: "882", endereco_cep: "95560-020", endereco_complemento: "", latitude: -29.326109044782587, longitude: -49.72886046595862, imovel_comodidades: "\xC1rea gourmet,\xC1rea de servi\xE7o,Lavabo,Hidromassagem", imagens: [ { link: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=800&q=80", titulo: "4.jpg", link_thumb: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800&q=80", titulo: "5.jpg", link_thumb: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_003", titulo_anuncio: "Apartamento Centro - 3 dorm, 115m\xB2, 3 vagas", observacoes: "Im\xF3vel com excelente localiza\xE7\xE3o e acabamentos de primeira linha.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2025-07-23 02:33:46", data_update: "2024-06-18 14:20:49", data_atualizacao: "2025-05-26 22:11:42", updated_at: "2025-07-18 14:46:17", dormitorios: 3, suites: 0, banheiros: 3, garagens: 3, area_total: 45, area_privativa: 115, andar: 15, valor_venda: 85e4, endereco_bairro: "Praia Grande", endereco_logradouro: "Rua Senador Pinheiro Machado", endereco_numero: "430", endereco_cep: "95560-060", endereco_complemento: "Apto 2", latitude: -29.336375878338483, longitude: -49.72503985134924, imovel_comodidades: "Ar condicionado,Varanda,\xC1rea gourmet,\xC1rea de servi\xE7o", imagens: [ { link: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=800&q=80", titulo: "4.jpg", link_thumb: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=800&q=80", titulo: "5.jpg", link_thumb: "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_004", titulo_anuncio: "Apartamento Centro - 3 dorm, 2 su\xEDtes, 140m\xB2, 1 vaga", observacoes: "Im\xF3vel com excelente localiza\xE7\xE3o e acabamentos de primeira linha.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2025-01-12 05:21:57", data_update: "2025-07-10 14:50:25", data_atualizacao: "2024-10-07 07:40:13", updated_at: "2025-05-05 10:51:02", dormitorios: 3, suites: 2, banheiros: 1, garagens: 1, area_total: 65, area_privativa: 140, andar: 12, valor_venda: 85e4, endereco_bairro: "Jardim Ubatuba", endereco_logradouro: "Rua Senador Pinheiro Machado", endereco_numero: "427", endereco_cep: "95560-020", endereco_complemento: "Apto 6", latitude: -29.339157171441027, longitude: -49.72632203630522, imovel_comodidades: "Ar condicionado,Jardim,Garagem coberta,Terra\xE7o,Sacada,Pr\xF3ximo \xE0 praia", imagens: [ { link: "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1568605117036-5fe5e7bab0b7?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1568605117036-5fe5e7bab0b7?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=800&q=80", titulo: "4.jpg", link_thumb: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_005", titulo_anuncio: "Apartamento Centro - 3 dorm, 3 su\xEDtes, 55m\xB2, 2 vagas", observacoes: "Apartamento com sacada, a poucos metros da Praia Grande.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2024-05-08 10:23:39", data_update: "2024-09-18 14:42:42", data_atualizacao: "2024-01-07 03:13:44", updated_at: "2025-02-09 06:49:29", dormitorios: 3, suites: 3, banheiros: 4, garagens: 2, area_total: 180, area_privativa: 55, andar: 12, valor_venda: 18e4, endereco_bairro: "Prainha", endereco_logradouro: "Rua Bar\xE3o do Rio Branco", endereco_numero: "526", endereco_cep: "95560-060", endereco_complemento: "", latitude: -29.33708529462322, longitude: -49.73430782637095, imovel_comodidades: "Lareira,Varanda", imagens: [ { link: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1522708323590-d24dbb6b0267?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1581858726788-75bc0f6a952d?w=800&q=80", titulo: "4.jpg", link_thumb: "https://images.unsplash.com/photo-1581858726788-75bc0f6a952d?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_006", titulo_anuncio: "Apartamento Centro - 4 dorm, 2 su\xEDtes, 55m\xB2, 3 vagas", observacoes: "Apartamento com vista para o mar em Torres, a praia mais linda do RS.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2025-06-12 10:45:18", data_update: "2024-09-07 03:24:40", data_atualizacao: "2024-12-04 16:01:29", updated_at: "2024-07-27 13:34:42", dormitorios: 4, suites: 2, banheiros: 4, garagens: 3, area_total: 105, area_privativa: 55, andar: 12, valor_venda: 62e4, endereco_bairro: "Residencial Quinta da Colina", endereco_logradouro: "Rua Crist\xF3v\xE3o Colombo", endereco_numero: "52", endereco_cep: "95560-010", endereco_complemento: "", latitude: -29.3424800569058, longitude: -49.71831372337415, imovel_comodidades: "Churrasqueira,Vista para o mar,Garagem coberta,Ar condicionado", imagens: [ { link: "https://images.unsplash.com/photo-1574362848149-11496d93a7c7?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1574362848149-11496d93a7c7?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1565183997392-2f6f122e5912?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1565183997392-2f6f122e5912?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1581858726788-75bc0f6a952d?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1581858726788-75bc0f6a952d?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=800&q=80", titulo: "4.jpg", link_thumb: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_007", titulo_anuncio: "Apartamento Centro - 2 dorm, 3 su\xEDtes, 35m\xB2, 2 vagas", observacoes: "Apartamento com sacada, a poucos metros da Praia Grande.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2024-10-27 12:03:54", data_update: "2025-03-07 19:18:00", data_atualizacao: "2024-07-30 07:14:20", updated_at: "2024-08-20 11:51:48", dormitorios: 2, suites: 3, banheiros: 3, garagens: 2, area_total: 180, area_privativa: 35, andar: 15, valor_venda: 18e4, endereco_bairro: "Vila Aparecida", endereco_logradouro: "Avenida Paraguassu", endereco_numero: "501", endereco_cep: "95560-001", endereco_complemento: "", latitude: -29.32939553045724, longitude: -49.72914491690439, imovel_comodidades: "Hidromassagem,Garagem coberta,\xC1rea de servi\xE7o", imagens: [ { link: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1502672260266-1c1ef2d93688?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=800&q=80", titulo: "4.jpg", link_thumb: "https://images.unsplash.com/photo-1586023492125-27b2c045efd7?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=800&q=80", titulo: "5.jpg", link_thumb: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_008", titulo_anuncio: "Apartamento Centro - 4 dorm, 3 su\xEDtes, 75m\xB2, 3 vagas", observacoes: "Apartamento com sacada, a poucos metros da Praia Grande.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2024-08-09 15:10:05", data_update: "2024-11-13 10:58:00", data_atualizacao: "2024-06-27 21:22:25", updated_at: "2024-04-26 14:12:51", dormitorios: 4, suites: 3, banheiros: 4, garagens: 3, area_total: 105, area_privativa: 75, andar: 12, valor_venda: 18e4, endereco_bairro: "Prainha", endereco_logradouro: "Avenida Beira Mar", endereco_numero: "573", endereco_cep: "95560-010", endereco_complemento: "Apto 13", latitude: -29.33017236005315, longitude: -49.7229083304007, imovel_comodidades: "Churrasqueira,\xC1rea gourmet,Terra\xE7o,Vista para o mar", imagens: [ { link: "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1560448204-e02f11c3d0e2?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1560185127-6ed189bf02f4?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_009", titulo_anuncio: "Apartamento Centro - 4 dorm, 95m\xB2, 1 vaga", observacoes: "Im\xF3vel com excelente localiza\xE7\xE3o e acabamentos de primeira linha.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul", endereco_cidade: "Torres", data_cadastro: "2025-02-11 23:09:26", data_update: "2024-06-21 03:00:28", data_atualizacao: "2024-02-08 04:56:29", updated_at: "2024-07-04 12:24:44", dormitorios: 4, suites: 0, banheiros: 4, garagens: 1, area_total: 150, area_privativa: 95, andar: 8, valor_venda: 28e4, endereco_bairro: "Vila S\xE3o Jo\xE3o", endereco_logradouro: "Rua Benjamin Constant", endereco_numero: "967", endereco_cep: "95560-020", endereco_complemento: "", latitude: -29.338789575624848, longitude: -49.736857533133374, imovel_comodidades: "Garagem coberta,Ar condicionado,Lareira,Churrasqueira", imagens: [ { link: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=800&q=80", titulo: "1.jpg", link_thumb: "https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=800&q=80", titulo: "2.jpg", link_thumb: "https://images.unsplash.com/photo-1545324418-cc1a3fa10c00?w=400&q=80" }, { link: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=800&q=80", titulo: "3.jpg", link_thumb: "https://images.unsplash.com/photo-1556909114-f6e7ad7d3136?w=400&q=80" } ], videos: [], tour360: [], plantas: [], valor_venda_visivel: true, valor_locacao_visivel: false, valor_temporada_visivel: false, valor_condominio_visivel: false, valor_iptu_visivel: false, endereco_estado_visivel: true, endereco_cidade_visivel: false, endereco_bairro_visivel: false, endereco_logradouro_visivel: false, endereco_numero_visivel: false, endereco_complemento_visivel: false }, { codigo: "FAKE_APARTAMENTOS_010", titulo_anuncio: "Apartamento Centro - 3 dorm, 55m\xB2, 2 vagas", observacoes: "Im\xF3vel com excelente localiza\xE7\xE3o e acabamentos de primeira linha.", contrato: "Compra", tipo: "Residencial", subtipo: "Apartamento", mobiliado: 0, financiavel: 0, exclusividade: false, medida: "m\xB2", endereco_estado: "Rio Grande do Sul