feat: selecao individual de leads antes de salvar, dark mode global e extracao 100% real do Google Maps
This commit is contained in:
+59
-24
@@ -11,9 +11,13 @@ from app.utils.rbac import admin_required, get_current_user
|
||||
|
||||
leads_bp = Blueprint('leads', __name__, url_prefix='/api/v1/leads')
|
||||
|
||||
@leads_bp.route('/search-maps', methods=['POST'])
|
||||
@leads_bp.route('/scrape-preview', methods=['POST'])
|
||||
@jwt_required()
|
||||
def search_maps():
|
||||
def scrape_preview():
|
||||
"""
|
||||
Realiza a varredura no Google Maps e retorna os estabelecimentos encontrados
|
||||
para visualização prévia e seleção ANTES de salvar no banco de dados.
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
cep = data.get('cep')
|
||||
ramo = data.get('ramo')
|
||||
@@ -22,31 +26,66 @@ def search_maps():
|
||||
if not cep or not ramo:
|
||||
return jsonify({'error': 'CEP e Ramo de Atividade são campos obrigatórios.'}), 400
|
||||
|
||||
# 1. Consulta CEP via ViaCEP
|
||||
location_info = GeocodingService.get_location_by_cep(cep)
|
||||
if 'error' in location_info and not location_info.get('cidade'):
|
||||
return jsonify({'error': location_info['error']}), 400
|
||||
|
||||
# 2. Monta query de busca e dispara Playwright Scraper
|
||||
search_query = GeocodingService.build_search_query(ramo, location_info)
|
||||
scraped_data = ScraperService.scrape_google_maps(search_query, max_results=max_results)
|
||||
|
||||
# Se Playwright não retornar dados em ambiente sem display ou se for mock em desenvolvimento
|
||||
if not scraped_data:
|
||||
# Tenta fallback estruturado mock para garantir funcionalidade se não houver display
|
||||
scraped_data = [
|
||||
{
|
||||
'nome_empresa': f"{ramo.title()} {location_info.get('bairro', 'Centro').title()}",
|
||||
'telefone': '(11) 98888-7777',
|
||||
'endereco': f"{location_info.get('logradouro', 'Rua Principal')}, {location_info.get('bairro', 'Bairro')}",
|
||||
'google_rating': 4.8,
|
||||
'total_avaliacoes': 42,
|
||||
'google_maps_url': 'https://maps.google.com',
|
||||
'website': ''
|
||||
}
|
||||
]
|
||||
return jsonify({
|
||||
'search_query': search_query,
|
||||
'location': location_info,
|
||||
'count': len(scraped_data),
|
||||
'results': scraped_data
|
||||
}), 200
|
||||
|
||||
@leads_bp.route('/import-selected', methods=['POST'])
|
||||
@jwt_required()
|
||||
def import_selected():
|
||||
"""
|
||||
Persiste no banco de dados APENAS os leads selecionados pelo usuário na tela.
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
cep = data.get('cep')
|
||||
ramo = data.get('ramo')
|
||||
selected_leads = data.get('selected_leads', [])
|
||||
|
||||
if not cep or not ramo:
|
||||
return jsonify({'error': 'CEP e Ramo de Atividade são obrigatórios.'}), 400
|
||||
|
||||
if not selected_leads or not isinstance(selected_leads, list):
|
||||
return jsonify({'error': 'Nenhum lead selecionado para importação.'}), 400
|
||||
|
||||
user_id = get_jwt_identity()
|
||||
summary = ScraperService.process_and_persist_leads(cep, ramo, selected_leads, usuario_id=user_id)
|
||||
|
||||
return jsonify({
|
||||
'message': f'{summary["created_count"]} novos leads importados e {summary["updated_count"]} atualizados com sucesso!',
|
||||
'summary': summary
|
||||
}), 200
|
||||
|
||||
@leads_bp.route('/search-maps', methods=['POST'])
|
||||
@jwt_required()
|
||||
def search_maps():
|
||||
"""
|
||||
Endpoint legados de busca direta com salvamento automático.
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
cep = data.get('cep')
|
||||
ramo = data.get('ramo')
|
||||
max_results = int(data.get('max_results', 15))
|
||||
|
||||
if not cep or not ramo:
|
||||
return jsonify({'error': 'CEP e Ramo de Atividade são campos obrigatórios.'}), 400
|
||||
|
||||
location_info = GeocodingService.get_location_by_cep(cep)
|
||||
if 'error' in location_info and not location_info.get('cidade'):
|
||||
return jsonify({'error': location_info['error']}), 400
|
||||
|
||||
search_query = GeocodingService.build_search_query(ramo, location_info)
|
||||
scraped_data = ScraperService.scrape_google_maps(search_query, max_results=max_results)
|
||||
|
||||
# 3. Processa e salva/deduplica no banco de dados
|
||||
user_id = get_jwt_identity()
|
||||
summary = ScraperService.process_and_persist_leads(cep, ramo, scraped_data, usuario_id=user_id)
|
||||
|
||||
@@ -63,7 +102,7 @@ def list_leads():
|
||||
ramo = request.args.get('ramo')
|
||||
search = request.args.get('search')
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = int(request.args.get('per_page', 50))
|
||||
per_page = int(request.args.get('per_page', 200))
|
||||
|
||||
query = Lead.query
|
||||
|
||||
@@ -197,7 +236,6 @@ def export_leads():
|
||||
if format_type == 'json':
|
||||
return jsonify([l.to_dict() for l in leads]), 200
|
||||
|
||||
# Formato CSV
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output, delimiter=';')
|
||||
writer.writerow([
|
||||
@@ -235,9 +273,6 @@ def delete_lead(lead_id):
|
||||
@leads_bp.route('/bulk', methods=['DELETE'])
|
||||
@admin_required()
|
||||
def expunge_leads():
|
||||
"""
|
||||
Expurgo / Limpeza em lote do banco de dados (Exclusivo Admin).
|
||||
"""
|
||||
LeadInteracao.query.delete()
|
||||
num_deleted = Lead.query.delete()
|
||||
db.session.commit()
|
||||
|
||||
Reference in New Issue
Block a user