File size: 13,355 Bytes
131e309
c3e94f7
08ba218
 
efef30d
 
c3e94f7
ce87491
057b5b2
119d340
08ba218
057b5b2
131e309
c3e94f7
e638816
 
41bb393
c3e94f7
 
131e309
41bb393
131e309
41bb393
 
131e309
c3e94f7
08ba218
131e309
08ba218
 
 
131e309
 
c3e94f7
131e309
 
08ba218
7f7c7ed
 
 
131e309
 
eeb3b83
 
131e309
8c97de6
131e309
 
e638816
 
131e309
41bb393
131e309
 
 
8c97de6
131e309
8c97de6
057b5b2
8c97de6
 
131e309
8c97de6
131e309
8c97de6
 
08ba218
8c97de6
 
131e309
7ecb4f3
 
 
131e309
6178dc6
131e309
6178dc6
 
131e309
6178dc6
a5df4fb
 
131e309
a5df4fb
6178dc6
131e309
a5df4fb
 
131e309
7ecb4f3
a5df4fb
 
131e309
a5df4fb
 
131e309
a5df4fb
 
131e309
a5df4fb
131e309
a5df4fb
 
 
 
 
 
 
 
131e309
7ecb4f3
131e309
6178dc6
131e309
6178dc6
 
131e309
7ecb4f3
131e309
 
 
 
 
 
7ecb4f3
 
131e309
7ecb4f3
 
131e309
7ecb4f3
 
131e309
7ecb4f3
 
131e309
7ecb4f3
131e309
7ecb4f3
 
131e309
a6940ae
131e309
e638816
131e309
8571ff6
5afc6de
131e309
 
 
41bb393
e638816
8571ff6
 
131e309
e638816
131e309
 
 
 
 
7ecb4f3
 
 
131e309
 
 
 
 
7ecb4f3
131e309
 
5afc6de
76525e5
131e309
76525e5
131e309
 
7ecb4f3
131e309
7ecb4f3
76525e5
7ecb4f3
76525e5
41bb393
131e309
76525e5
 
131e309
41bb393
 
 
 
131e309
 
41bb393
 
 
131e309
41bb393
 
 
131e309
41bb393
131e309
76525e5
41bb393
131e309
76525e5
 
131e309
76525e5
 
131e309
 
 
 
41bb393
131e309
 
 
41bb393
 
131e309
76525e5
131e309
76525e5
131e309
 
76525e5
 
a7992d0
 
 
 
 
 
 
131e309
a7992d0
 
 
 
 
131e309
6178dc6
131e309
76525e5
 
eeb3b83
41bb393
76525e5
 
eeb3b83
76525e5
eeb3b83
 
 
 
76525e5
 
6178dc6
a5df4fb
a7992d0
76525e5
8c97de6
131e309
 
efef30d
9f2d369
131e309
eeb3b83
0b7f02a
7ecb4f3
 
 
 
 
0b7f02a
eeb3b83
131e309
8c97de6
41bb393
eeb3b83
 
 
 
131e309
057b5b2
9bf522d
 
 
 
7ecb4f3
 
 
9bf522d
 
 
 
 
 
 
 
 
 
 
 
 
7ecb4f3
9bf522d
 
131e309
9bf522d
 
131e309
9bf522d
 
 
 
131e309
9bf522d
 
 
7ecb4f3
9bf522d
131e309
4217908
 
131e309
9bf522d
4217908
 
057b5b2
4217908
131e309
9bf522d
 
 
 
131e309
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# app.py - VERSÃO COM MARGEM ABSOLUTA FIXA
import pandas as pd
import requests
import io
import itertools
from collections import defaultdict
import gradio as gr

# 🔧 CONFIGURAÇÕES GLOBAIS
FILE_ID = "1XvNz7DlWioT64uNdcfBSL8Ui2nRSi9gT"
URL = f"https://drive.google.com/uc?export=download&id={FILE_ID}"

# Variáveis globais para armazenamento
df = None
df_imovel = None
grupos_imovel = None
planilha_carregada = False

def carregar_planilha():
    """Carrega e prepara a planilha para análise."""
    global df, df_imovel, grupos_imovel, planilha_carregada

    if planilha_carregada:
        return True

    try:
        print("⏳ Carregando planilha...")
        response = requests.get(URL)
        response.raise_for_status()
        data = io.BytesIO(response.content)
        df = pd.read_excel(data)

        # Padroniza nomes das colunas
        df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")

        # Colunas numéricas
        colunas_numericas = ['valor_credito', 'valor_entrada', 'valor_parcela', 'saldo_devedor', 'qtd_parcelas']
        for coluna in colunas_numericas:
            if coluna in df.columns:
                df[coluna] = pd.to_numeric(df[coluna], errors='coerce').fillna(0)

        # Verifica colunas essenciais
        if 'tipo_do_bem' not in df.columns or 'administradora' not in df.columns:
            raise ValueError("Colunas essenciais não encontradas")

        df.drop_duplicates(inplace=True)

        # Pré‑processa imóvel
        df_imovel = df[df['tipo_do_bem'].str.lower() == 'imovel'].copy()
        grupos_imovel = agrupar_por_administradora(df_imovel)

        planilha_carregada = True
        print("✅ Planilha carregada e pré-processada com sucesso")
        print(f"✅ Total de cotas de imóvel: {len(df_imovel)}")
        print(f"✅ Administradoras disponíveis: {list(grupos_imovel.keys())}")
        return True

    except Exception as e:
        print(f"❌ Erro ao carregar planilha: {str(e)}")
        return False


def agrupar_por_administradora(dataframe):
    """Agrupa cotas por administradora."""
    grupos = defaultdict(list)
    for _, row in dataframe.iterrows():
        grupos[row['administradora']].append(row.to_dict())
    return grupos


def formatar_moeda(valor):
    return f"R$ {valor:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")


def calcular_tempo_parcelas(combinacao):
    """Calcula o tempo de parcelas baseado nas cotas da combinação."""
    if not combinacao:
        return ""

    try:
        cotas_ordenadas = sorted(combinacao, key=lambda x: int(x.get('qtd_parcelas', 0)))
        prazos = sorted(set(int(c.get('qtd_parcelas', 0)) for c in combinacao))

        if not prazos or prazos[0] <= 0:
            return ""

        resultado = []
        prazo_anterior = 0

        for prazo_atual in prazos:
            cotas_ativas = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) >= prazo_anterior + 1]
            valor_parcela_total = sum(c.get('valor_parcela', 0) for c in cotas_ativas)

            inicio = prazo_anterior + 1
            fim = prazo_atual

            if inicio <= fim and valor_parcela_total > 0:
                resultado.append(f"{inicio}x à {fim}x: {formatar_moeda(valor_parcela_total)}/mês")

            prazo_anterior = prazo_atual

        if prazos and prazo_anterior < max(prazos):
            cotas_ativas_finais = [c for c in cotas_ordenadas if int(c.get('qtd_parcelas', 0)) > prazo_anterior]
            if cotas_ativas_finais:
                valor_parcela_final = sum(c.get('valor_parcela', 0) for c in cotas_ativas_finais)
                inicio_final = prazo_anterior + 1
                fim_final = max(prazos)
                if valor_parcela_final > 0:
                    resultado.append(f"{inicio_final}x à {fim_final}x: {formatar_moeda(valor_parcela_final)}/mês")

        return "\\n".join(resultado) if resultado else ""

    except Exception as e:
        print(f"❌ Erro ao calcular tempo de parcelas: {str(e)}")
        return ""


def definir_margens_absolutas(credito_desejado, entrada_desejada, parcela_desejada):
    """Define margens fixas em R$ para crédito, entrada e parcela."""
    # Margens em R$ (ex.: 500.000 → 480.000–520.000)
    margem_credito = 40_000
    margem_entrada  = 10_000
    margem_parcela  = 1_000

    credito_min = max(0, credito_desejado - margem_credito)
    credito_max = credito_desejado + margem_credito

    entrada_min = max(0, entrada_desejada - margem_entrada)
    entrada_max = entrada_desejada + margem_entrada

    parcela_min = max(0, parcela_desejada - margem_parcela)
    parcela_max = parcela_desejada + margem_parcela

    return credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max


def filtrar_cotas_por_faixa(cotas, credito_min, credito_max):
    """Filtra cotas que pelo menos têm chance de entrar na faixa de crédito."""
    return [c for c in cotas if c['valor_credito'] < credito_max * 2]


def buscar_combinacoes_otimizado(credito_desejado, entrada_desejada, parcela_desejada, administradoras=None, max_combinacoes=15):
    """Busca combinações de cotas dentro de margens fixas absolutas."""
    global df_imovel, grupos_imovel

    if not planilha_carregada:
        if not carregar_planilha():
            return "❌ Erro: Planilha não carregada. Por favor, tente novamente."

    # Filtra administradoras
    if administradoras and len(administradoras) > 0:
        grupos_filtrados = {adm: cotas for adm, cotas in grupos_imovel.items() if adm in administradoras}
    else:
        grupos_filtrados = grupos_imovel

    if not grupos_filtrados:
        return "🚫 Nenhuma cota encontrada com os filtros aplicados. Tente ajustar os parâmetros."

    print(f"🔍 Total de administradoras no filtro: {len(grupos_filtrados)}")

    # Usa margens fixas em R$
    credito_min, credito_max, entrada_min, entrada_max, parcela_min, parcela_max = definir_margens_absolutas(
        credito_desejado, entrada_desejada, parcela_desejada
    )

    print(f"💰 Valores desejados: Crédito: R$ {credito_desejado:,.2f}, Entrada: R$ {entrada_desejada:,.2f}, Parcela: R$ {parcela_desejada:,.2f}")
    print(f"🔍 Margem de crédito: de R$ {credito_min:,.2f} até R$ {credito_max:,.2f}")

    # Limites de busca
    max_cotas_combinacao = 15
    max_combinacoes_por_adm = 4000  # para evitar explosão de combinações [web:12][web:18]

    resultados = []
    total_combinacoes_testadas = 0

    for adm, cotas in grupos_filtrados.items():
        print(f"🔍 Analisando administradora: {adm} ({len(cotas)} cotas)")

        cotas_filtradas = filtrar_cotas_por_faixa(cotas, credito_min, credito_max)

        for r in range(1, min(max_cotas_combinacao + 1, len(cotas_filtradas) + 1)):
            combinacoes_geradas = 0
            for combinacao in itertools.combinations(cotas_filtradas, r):
                if combinacoes_geradas >= max_combinacoes_por_adm:
                    break

                total_combinacoes_testadas += 1
                combinacoes_geradas += 1

                total_credito = sum(c['valor_credito'] for c in combinacao)
                total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
                total_entrada = total_entrada_base + (total_credito * 0.085)
                total_parcelas = sum(c['valor_parcela'] for c in combinacao)

                # Verifica margens fixas
                if (credito_min <= total_credito <= credito_max and
                    entrada_min <= total_entrada <= entrada_max and
                    parcela_min <= total_parcelas <= parcela_max):

                    total_saldo = sum(c['saldo_devedor'] for c in combinacao)
                    valor_final = round(total_entrada + total_saldo, 2)
                    diferenca = abs(valor_final - total_credito)

                    resultados.append((adm, combinacao, diferenca))

                    if len(resultados) >= max_combinacoes * 2:
                        break

            if len(resultados) >= max_combinacoes * 2:
                break

        if len(resultados) >= max_combinacoes * 2:
            break

    print(f"🔍 Total de combinações testadas: {total_combinacoes_testadas}")
    print(f"🔍 Combinações válidas encontradas: {len(resultados)}")

    if not resultados:
        return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."

    resultados.sort(key=lambda x: x[2])  # ordena pela menor diferença
    return formatar_resultados(resultados[:max_combinacoes])


def formatar_resultados(resultados):
    """Formata os resultados para exibição incluindo tempo de parcelas."""
    if not resultados:
        return "🚫 Nenhuma combinação adequada encontrada com os parâmetros fornecidos."

    resposta = ""
    for idx, (adm, combinacao, _) in enumerate(resultados, 1):
        codigos = [str(c.get('codigo', 'N/A')) for c in combinacao]
        total_credito = sum(c['valor_credito'] for c in combinacao)
        total_entrada_base = sum(c['valor_entrada'] for c in combinacao)
        total_entrada = total_entrada_base + (total_credito * 0.085)
        total_saldo = sum(c['saldo_devedor'] for c in combinacao)
        total_parcelas = sum(c['valor_parcela'] for c in combinacao)
        maior_prazo = max(int(c.get('qtd_parcelas', 0)) for c in combinacao)

        valor_final = round(total_entrada + total_saldo, 2)
        credito_real = round(total_credito - total_entrada, 2)
        taxa_total = total_saldo - credito_real
        taxa_percentual = (taxa_total / credito_real) * 100 if credito_real > 0 else 0
        taxa_mensal = taxa_percentual / maior_prazo if maior_prazo > 0 else 0

        tempo_parcelas = calcular_tempo_parcelas(combinacao)

        resposta += f"""
--- COMBINAÇÃO {idx} ---
🏢 Administradora: {adm}
🔢 Códigos: {', '.join(codigos)} ({len(combinacao)} cotas)
💰 Crédito Total: {formatar_moeda(total_credito)}
💵 Entrada (+comissão): {formatar_moeda(total_entrada)}
📉 Saldo Devedor: {formatar_moeda(total_saldo)}
💲 Parcela: {formatar_moeda(total_parcelas)}
⏳ Prazo: {maior_prazo} meses
🔚 Valor Final: {formatar_moeda(valor_final)}
💠 Crédito Líquido: {formatar_moeda(credito_real)}
📊 Taxa Total: {taxa_percentual:.2f}% ({formatar_moeda(taxa_total)})
🗓️ Taxa Mensal: {taxa_mensal:.2f}%
🗓️ Taxa Anual: {taxa_mensal * 12:.2f}%
⏰ TEMPO DE PARCELAS:
{tempo_parcelas if tempo_parcelas else "Não foi possível calcular o tempo de parcelas"}
"""
    return resposta


# Carrega a planilha uma vez quando o aplicativo inicia
carregar_planilha()


def ajustar_e_buscar(credito_desejado, entrada_desejada, parcela_desejada, administradoras):
    return buscar_combinacoes_otimizado(
        float(credito_desejado or 0),
        float(entrada_desejada or 0),
        float(parcela_desejada or 0),
        administradoras,
        max_combinacoes=15
    )


def get_administradoras_options():
    if df is not None and planilha_carregada:
        adm_list = df[df['tipo_do_bem'].str.lower() == 'imovel']['administradora'].unique()
        return sorted(adm_list)
    return []


# Interface Gradio
with gr.Blocks(title="Sistema de Busca de Consórcio Imóvel") as demo:
    gr.HTML("""
    <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
    <style>
        * { font-family: 'Poppins', sans-serif !important; }
        body { background-color: #ff8f00 !important; color: black !important; }
        input[type="number"], input[type="text"], textarea {
            background-color: white !important;
            border: 2px solid black !important;
            border-radius: 12px !important;
            color: black !important;
            padding: 10px !important;
        }
        .gr-button {
            font-weight: bold;
            border-radius: 10px !important;
            background-color: white !important;
            color: black !important;
            border: 2px solid black !important;
        }
        .gr-button:hover { background-color: #fff3e0 !important; }
    </style>
    """)

    gr.Markdown("# 🏠 Busca de Combinações de Consórcio Imóvel")
    gr.Markdown("Insira os valores desejados para crédito, entrada e parcela, e filtre por administradora.")

    with gr.Row():
        credito_input = gr.Number(label="Crédito Desejado (R$)", value=300000, step=1000)
        entrada_input = gr.Number(label="Entrada Desejada (R$)", value=30000, step=1000)
        parcela_input = gr.Number(label="Parcela Desejada (R$)", value=1500, step=100)

    administradoras_multiselect = gr.CheckboxGroup(
        label="Filtrar por Administradoras",
        choices=get_administradoras_options(),
        value=get_administradoras_options()
    )

    submit_btn = gr.Button("🔍 Buscar Combinações", variant="primary")
    output_text = gr.Textbox(label="Resultados da Busca", lines=20, max_lines=40, interactive=False, placeholder="Os resultados aparecerão aqui...")

    submit_btn.click(
        fn=ajustar_e_buscar,
        inputs=[credito_input, entrada_input, parcela_input, administradoras_multiselect],
        outputs=output_text
    )

    gr.Markdown("---")
    gr.Markdown("Desenvolvido para auxiliar na busca por cotas de consórcio ideais.")

if __name__ == "__main__":
    demo.launch()
#