diff --git a/README.md b/README.md index cbc62f3..34593e8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,30 @@ -# imobi -Sistema de sistema de Imobiliária +[![Python Version](https://img.shields.io/badge/python-3.8.7-brightgreen.svg)](https://python.org) +[![Django Version](https://img.shields.io/badge/django-4.0.1-brightgreen.svg)](https://djangoproject.com) + +
+

Sistema de Agendamento de Visitas para Imobiliárias

+
+ +![](https://github.com/rogeriodelphi/imobi/blob/master/blob/master/images/demo.png) + +# SAVI +```bash +Sistema de Agendamento de Visitas para Imobiliárias +Projeto proprietário desenvolvido em Python 3 no Windows, testado no GNU/Linux e Windows. +``` + +## Implementações +```bash +* Cadastro de Imóveis, visitas, agendamentos. +* Login/Logout +* Interface simples e em português +``` + +
+ +

+ +Languages Count +Last Commit +Repo Size +

\ No newline at end of file diff --git a/autenticacao/__pycache__/urls.cpython-38.pyc b/autenticacao/__pycache__/urls.cpython-38.pyc index 3fc7dd9..bbe73c6 100644 Binary files a/autenticacao/__pycache__/urls.cpython-38.pyc and b/autenticacao/__pycache__/urls.cpython-38.pyc differ diff --git a/autenticacao/__pycache__/views.cpython-38.pyc b/autenticacao/__pycache__/views.cpython-38.pyc index e23292c..15f1c1f 100644 Binary files a/autenticacao/__pycache__/views.cpython-38.pyc and b/autenticacao/__pycache__/views.cpython-38.pyc differ diff --git a/autenticacao/templates/cadastro.html b/autenticacao/templates/cadastro.html index f134df5..2c9d5d3 100644 --- a/autenticacao/templates/cadastro.html +++ b/autenticacao/templates/cadastro.html @@ -11,7 +11,7 @@
-
+ + {% csrf_token %}

Cadastre-se

+ {% if messages %} + {% for message in messages %} +
+ {{message}} +
+ {% endfor %} + {% endif %}

diff --git a/autenticacao/templates/logar.html b/autenticacao/templates/logar.html new file mode 100644 index 0000000..c0e565b --- /dev/null +++ b/autenticacao/templates/logar.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load static %} + + +{% block 'head' %} + +{% endblock %} + +{% block 'titulo' %}Logar{% endblock %} + +{% block 'body' %} +
+
+ +
+
+

Logar

+ {% if messages %} + {% for message in messages %} +
+ {{message}} +
+ {% endfor %} + {% endif %} +
+
+ {% csrf_token %} + +
+
+ +
+ + +
+
+
+
+{% endblock %} diff --git a/autenticacao/urls.py b/autenticacao/urls.py index 3fe1c70..7f0afb1 100644 --- a/autenticacao/urls.py +++ b/autenticacao/urls.py @@ -3,4 +3,5 @@ urlpatterns = [ path('cadastro/', views.cadastro, name='cadastro' ), path('logar/', views.logar, name='logar' ), + path('sair/', views.sair, name="sair") ] \ No newline at end of file diff --git a/autenticacao/views.py b/autenticacao/views.py index a519e89..890ab9a 100644 --- a/autenticacao/views.py +++ b/autenticacao/views.py @@ -1,8 +1,57 @@ -from django.shortcuts import render +from django.contrib import messages +from django.contrib.messages import constants +from django.shortcuts import render, redirect from django.http import HttpResponse +from django.contrib.auth.models import User +from django.contrib import auth def cadastro(request): - return render(request, 'cadastro.html') + if request.method == "GET": + if request.user.is_authenticated: + return redirect('/') + return render(request, 'cadastro.html') + elif request.method == "POST": + username = request.POST.get('username') + email = request.POST.get('email') + senha = request.POST.get('senha') + + if len(username.strip()) == 0 or len(email.strip()) == 0 or len(senha.strip()) == 0: + messages.add_message(request, constants.ERROR, 'Preenha os todos os campos.') + return redirect('/auth/cadastro') + + user = User.objects.filter(username=username) + if user.exists(): + messages.add_message(request, constants.ERROR, 'Usuário já existe.') + return redirect('/auth/cadastro') + try: + user = User.objects.create_user(username=username, + email=email, + password=senha) + user.save() + messages.add_message(request, constants.SUCCESS, 'Usuário cadastrado com sucesso.') + return redirect('/auth/logar') + except: + messages.add_message(request, constants.ERROR, 'Erro interno do sistema.') + return redirect('/auth/cadastro') def logar(request): - return HttpResponse('Logar') \ No newline at end of file + if request.method == "GET": + if request.user.is_authenticated: + return redirect('/') + return render(request, 'logar.html') + elif request.method == "POST": + username = request.POST.get('username') + senha = request.POST.get('senha') + + usuario = auth.authenticate(username=username, password=senha) + + if not usuario: + messages.add_message(request, constants.ERROR, 'Username ou senha inválidos') + return redirect('/auth/logar') + else: + auth.login(request, usuario) + return redirect('/') + +def sair(request): + auth.logout(request) + return redirect('/auth/logar') diff --git a/blob/master/images/demo.png b/blob/master/images/demo.png new file mode 100644 index 0000000..7d569e5 Binary files /dev/null and b/blob/master/images/demo.png differ diff --git a/identifier.sqlite b/identifier.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/imobi/__pycache__/settings.cpython-38.pyc b/imobi/__pycache__/settings.cpython-38.pyc index 2244d30..863246d 100644 Binary files a/imobi/__pycache__/settings.cpython-38.pyc and b/imobi/__pycache__/settings.cpython-38.pyc differ diff --git a/imobi/__pycache__/urls.cpython-38.pyc b/imobi/__pycache__/urls.cpython-38.pyc index 498ef70..33f354f 100644 Binary files a/imobi/__pycache__/urls.cpython-38.pyc and b/imobi/__pycache__/urls.cpython-38.pyc differ diff --git a/imobi/settings.py b/imobi/settings.py index 3002e02..58a178e 100644 --- a/imobi/settings.py +++ b/imobi/settings.py @@ -11,6 +11,7 @@ """ import os from pathlib import Path +from django.contrib.messages import constants # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -38,6 +39,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'autenticacao', + 'plataforma', ] MIDDLEWARE = [ @@ -127,3 +129,12 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +MESSAGE_TAGS = { + constants.DEBUG: 'alert-primary', + constants.ERROR: 'alert-danger', + constants.SUCCESS: 'alert-success', + constants.INFO: 'alert-info', + constants.WARNING: 'alert-warning', +} + diff --git a/imobi/urls.py b/imobi/urls.py index bddc112..55d564b 100644 --- a/imobi/urls.py +++ b/imobi/urls.py @@ -21,6 +21,7 @@ urlpatterns = [ path('admin/', admin.site.urls), path('auth/', include('autenticacao.urls')), + path('', include('plataforma.urls')), ] urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/media/img/istockphoto-1165384568-612x612.jpg b/media/img/istockphoto-1165384568-612x612.jpg new file mode 100644 index 0000000..f9a3498 Binary files /dev/null and b/media/img/istockphoto-1165384568-612x612.jpg differ diff --git a/media/img/istockphoto-1165936957-612x612.jpg b/media/img/istockphoto-1165936957-612x612.jpg new file mode 100644 index 0000000..69873f7 Binary files /dev/null and b/media/img/istockphoto-1165936957-612x612.jpg differ diff --git a/media/img/istockphoto-155700839-612x612.jpg b/media/img/istockphoto-155700839-612x612.jpg new file mode 100644 index 0000000..5b2f6a4 Binary files /dev/null and b/media/img/istockphoto-155700839-612x612.jpg differ diff --git a/media/img/istockphoto-453195517-612x612.jpg b/media/img/istockphoto-453195517-612x612.jpg new file mode 100644 index 0000000..13c0d90 Binary files /dev/null and b/media/img/istockphoto-453195517-612x612.jpg differ diff --git a/plataforma/__init__.py b/plataforma/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plataforma/admin.py b/plataforma/admin.py new file mode 100644 index 0000000..31afa5d --- /dev/null +++ b/plataforma/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from .models import DiasVisita, Horario, Imovei, Cidade, Imagem, Visitas + + +@admin.register(Imovei) +class ImoveiAdmin(admin.ModelAdmin): + list_display = ('id', 'rua', 'valor', 'quartos', 'tamanho', 'cidade', 'tipo') + list_editable = ('valor', 'tipo') + list_filter = ('cidade', 'tipo') + +admin.site.register(DiasVisita) +admin.site.register(Horario) +admin.site.register(Imagem) +admin.site.register(Cidade) +admin.site.register(Visitas) \ No newline at end of file diff --git a/plataforma/apps.py b/plataforma/apps.py new file mode 100644 index 0000000..961bbac --- /dev/null +++ b/plataforma/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PlataformaConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'plataforma' diff --git a/plataforma/migrations/0001_initial.py b/plataforma/migrations/0001_initial.py new file mode 100644 index 0000000..f38272a --- /dev/null +++ b/plataforma/migrations/0001_initial.py @@ -0,0 +1,74 @@ +# Generated by Django 4.0.1 on 2022-01-14 04:12 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Cidade', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nome', models.CharField(max_length=30)), + ], + ), + migrations.CreateModel( + name='DiasVisita', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dia', models.CharField(max_length=20)), + ], + ), + migrations.CreateModel( + name='Horario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('horario', models.TimeField()), + ], + ), + migrations.CreateModel( + name='Imagem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('img', models.ImageField(upload_to='img')), + ], + ), + migrations.CreateModel( + name='Imovei', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('valor', models.FloatField()), + ('quartos', models.IntegerField()), + ('tamanho', models.FloatField()), + ('rua', models.CharField(max_length=50)), + ('tipo', models.CharField(choices=[('V', 'Venda'), ('A', 'Aluguel')], max_length=1)), + ('tipo_imovel', models.CharField(choices=[('A', 'Apartamento'), ('C', 'Casa')], max_length=1)), + ('numero', models.IntegerField()), + ('descricao', models.TextField()), + ('cidade', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='plataforma.cidade')), + ('dias_visita', models.ManyToManyField(to='plataforma.DiasVisita')), + ('horarios', models.ManyToManyField(to='plataforma.Horario')), + ('imagens', models.ManyToManyField(to='plataforma.Imagem')), + ], + ), + migrations.CreateModel( + name='Visitas', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('dia', models.CharField(max_length=20)), + ('horario', models.TimeField()), + ('status', models.CharField(choices=[('A', 'Agendado'), ('F', 'Finalizado'), ('C', 'Cancelado')], default='A', max_length=1)), + ('imovel', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='plataforma.imovei')), + ('usuario', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/plataforma/migrations/__init__.py b/plataforma/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plataforma/models.py b/plataforma/models.py new file mode 100644 index 0000000..878e8ce --- /dev/null +++ b/plataforma/models.py @@ -0,0 +1,76 @@ +from django.db import models +from django.contrib.auth.models import User + + +class Imagem(models.Model): + img = models.ImageField(upload_to='img') + + def __str__(self) -> str: + return self.img.url + + +class Cidade(models.Model): + nome = models.CharField(max_length=30) + + def __str__(self) -> str: + return self.nome + + +class DiasVisita(models.Model): + dia = models.CharField(max_length=20) + + def __str__(self) -> str: + return self.dia + + +class Horario(models.Model): + horario = models.TimeField() + + def __str__(self) -> str: + return str(self.horario) + + +class Imovei(models.Model): + choices = (('V', 'Venda'), + ('A', 'Aluguel')) + + choices_imovel = (('A', 'Apartamento'), + ('C', 'Casa')) + + imagens = models.ManyToManyField(Imagem) + valor = models.FloatField() + quartos = models.IntegerField() + tamanho = models.FloatField() + cidade = models.ForeignKey(Cidade, on_delete=models.DO_NOTHING) + rua = models.CharField(max_length=50) + tipo = models.CharField(max_length=1, choices=choices) + tipo_imovel = models.CharField(max_length=1, choices=choices_imovel) + numero = models.IntegerField() + descricao = models.TextField() + dias_visita = models.ManyToManyField(DiasVisita) + horarios = models.ManyToManyField(Horario) + + def __str__(self) -> str: + return self.rua + + +class Visitas(models.Model): + choices = (('S', 'Segunda'), + ('T', 'Terça'), + ('Q', 'Quarta'), + ('QI', 'Quinta'), + ('SE', 'Sexta'), + ('SA', 'Sabado'), + ('D', 'Domingo')) + + choices_status = (('A', 'Agendado'), + ('F', 'Finalizado'), + ('C', 'Cancelado')) + imovel = models.ForeignKey(Imovei, on_delete=models.DO_NOTHING) + usuario = models.ForeignKey(User, on_delete=models.DO_NOTHING) + dia = models.CharField(max_length=20) + horario = models.TimeField() + status = models.CharField(max_length=1, choices=choices_status, default="A") + + def __str__(self) -> str: + return self.usuario.username diff --git a/plataforma/templates/agendamentos.html b/plataforma/templates/agendamentos.html new file mode 100644 index 0000000..bcd3640 --- /dev/null +++ b/plataforma/templates/agendamentos.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load static %} + +{% block 'head' %} + + + +{% endblock %} + + +{% block 'body' %} + +
+ +
+ + + + + + + + + + + + {% for visita in visitas %} + + + + + + + + {% endfor %} + +
imgEnderecoDia/HoraStatusCancelar
{{visita.imovel.rua}}, {{visita.imovel.cidade}}{{visita.dia}}/{{visita.horario}} + {% if visita.status == "A" %} + Agendado + {% endif %} + + {% if visita.status == "F" %} + Finalizado + {% endif %} + + {% if visita.status == "C" %} + Cancelado + {% endif %} + + {% if visita.status == "A" %} + CANCELAR + {% else %} + CANCELAR + {% endif %} +
+
+{% endblock %} \ No newline at end of file diff --git a/plataforma/templates/home.html b/plataforma/templates/home.html new file mode 100644 index 0000000..6c2c9e5 --- /dev/null +++ b/plataforma/templates/home.html @@ -0,0 +1,83 @@ +{% extends 'base.html' %} +{% load static %} + +{% block 'head' %} + +{% endblock %} + +{% block 'body' %} +
+
+ +
+ {% for imovel in imoveis %} +
+
+ +
+ +

R$ {{imovel.valor}}

+
+

{{imovel.rua}}, {{imovel.cidade}}

+
+ +
+
+
+ {% endfor %} +
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/plataforma/templates/imovel.html b/plataforma/templates/imovel.html new file mode 100644 index 0000000..5f49731 --- /dev/null +++ b/plataforma/templates/imovel.html @@ -0,0 +1,114 @@ +{% extends 'base.html' %} +{% load static %} + +{% block 'head' %} + +{% endblock %} + +{% block 'body' %} + +
+
+
+ +
+
+

Valor

+

R$ {{imovel.valor}}

+
+
+

Quartos

+

{{imovel.quartos}} Quartos

+
+
+

Tamanho

+

{{imovel.tamanho}} m²

+
+
+
+
+
+

Endereço

+

{{imovel.rua}}, {{imovel.cidade }}

+
+
+
+

Descricao

+

{{imovel.descricao}}

+
+ +
+
+
+
+
+
+

Talvez você se interesse por

+
+ {% for sugestao in sugestoes %} +
+
+ +
+

R$ {{sugestao.valor}}

+

{{sugestao.rua}}, {{sugestao.cidade}}

+
+ +
+
+
+ {% endfor %} +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/plataforma/tests.py b/plataforma/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/plataforma/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/plataforma/urls.py b/plataforma/urls.py new file mode 100644 index 0000000..17603b1 --- /dev/null +++ b/plataforma/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.home, name="home"), + path('imovel/', views.imovel, name="imovel"), + path('agendar_visitas', views.agendar_visitas, name="agendar_visitas"), + path('agendamentos', views.agendamentos, name="agendamentos"), + path('cancelar_agendamento/', views.cancelar_agendamento, name="cancelar_agendamento"), +] diff --git a/plataforma/views.py b/plataforma/views.py new file mode 100644 index 0000000..261ae7d --- /dev/null +++ b/plataforma/views.py @@ -0,0 +1,58 @@ +from django.contrib.auth.decorators import login_required +from django.http import HttpResponse +from django.shortcuts import render, get_object_or_404, redirect +from plataforma.models import Imovei, Cidade, Visitas + + +@login_required(login_url='/auth/logar/') +def home(request): + preco_minimo = request.GET.get('preco_minimo') + preco_maximo = request.GET.get('preco_maximo') + cidade = request.GET.get('cidade') + tipo = request.GET.getlist('tipo') + print(f' PM {preco_maximo}, PMAX {preco_maximo}, Cidade {cidade}, Tipo{tipo}') + cidades = Cidade.objects.all() + if preco_minimo or preco_maximo or cidade or tipo: + if not preco_minimo: + preco_minimo = 0 + if not preco_maximo: + preco_maximo = 999999999 + if not tipo: + tipo = ['A', 'C'] + imoveis = Imovei.objects.filter(valor__gte=preco_minimo) \ + .filter(valor__lte=preco_maximo) \ + .filter(tipo_imovel__in=tipo).filter(cidade=cidade) + else: + imoveis = Imovei.objects.all() + return render(request, 'home.html', {'imoveis': imoveis, 'cidades': cidades}) + +def imovel(request, id): + imovel = get_object_or_404(Imovei, id=id) + sugestoes = Imovei.objects.filter(cidade=imovel.cidade).exclude(id=id)[:2] + return render(request, 'imovel.html', {'imovel': imovel, 'sugestoes': sugestoes, 'id': id}) + +def agendar_visitas(request): + usuario = request.user + dia = request.POST.get('dia') + horario = request.POST.get('horario') + id_imovel = request.POST.get('id_imovel') + + visita = Visitas( + imovel_id=id_imovel, + usuario=usuario, + dia=dia, + horario=horario + ) + visita.save() + + return redirect('/agendamentos') + +def agendamentos(request): + visitas = Visitas.objects.filter(usuario=request.user) + return render(request, "agendamentos.html", {'visitas': visitas}) + +def cancelar_agendamento(request, id): + visitas = get_object_or_404(Visitas, id=id) + visitas.status = "C" + visitas.save() + return redirect('/agendamentos') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..981c034 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +asgiref==3.4.1 +backports.zoneinfo==0.2.1 +Django==4.0.1 +Pillow==9.0.0 +sqlparse==0.4.2 +tzdata==2021.5 diff --git a/templates/base.html b/templates/base.html index 3466520..0ed9d34 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,6 +10,25 @@ {% block 'titulo' %}{% endblock %} + {% if user.is_authenticated %} + + {% endif %} {% block 'body' %}{% endblock %} diff --git a/templates/static/autenticacao/img/email.png b/templates/static/autenticacao/img/email.png new file mode 100644 index 0000000..0ef3f4f Binary files /dev/null and b/templates/static/autenticacao/img/email.png differ diff --git a/templates/static/autenticacao/img/logo.png b/templates/static/autenticacao/img/logo.png new file mode 100644 index 0000000..139ed04 Binary files /dev/null and b/templates/static/autenticacao/img/logo.png differ diff --git a/templates/static/autenticacao/img/senha.png b/templates/static/autenticacao/img/senha.png new file mode 100644 index 0000000..b0d2061 Binary files /dev/null and b/templates/static/autenticacao/img/senha.png differ diff --git a/templates/static/autenticacao/img/user.png b/templates/static/autenticacao/img/user.png new file mode 100644 index 0000000..c3fccfa Binary files /dev/null and b/templates/static/autenticacao/img/user.png differ diff --git a/templates/static/plataforma/css/home.css b/templates/static/plataforma/css/home.css new file mode 100644 index 0000000..e367441 --- /dev/null +++ b/templates/static/plataforma/css/home.css @@ -0,0 +1,135 @@ +.bg_navbar{ + background-color: #30CD9E; +} + +.font_logo{ + font-family: 'Courier New', Courier, monospace; + font-size: 25px; + display: inline; + +} + +.card_imovel{ + background-color: #30CD9E; + + width: 20rem; + margin-top: 50px; + border-radius: 26px; +} + +.img_capa_imovel{ + + width: 100%; + border-top-left-radius: 26px; + border-top-right-radius: 26px; + +} + +.informacoes_imoveis{ + + padding: 20px; + color: white; + +} + +.valor{ + + font-weight: bold; + border-bottom: 1px solid white; +} + +.btn_filtrar{ + + border: none; + background-color: transparent; + border: 2px solid #30CD9E; + color: #30CD9E; + padding: 5px 10px 5px 10px; + font-weight: bold; + +} + +.titulo_filtrar{ + + color:#30CD9E; + font-weight: bold; + font-size: 30px; + +} + +.btn_filtrar_modal{ + + border: none; + color: white; + background-color: #30CD9E; + font-weight: bold; + font-size: 20px; + +} + +.a_none{ + + color: white; + +} + +.a_none:hover{ + + text-decoration: none; + color: white; + +} + +.titulo_especificacao{ + color: #30CD9E; + font-weight: bold; + margin-top: 20px; + font-size: 40px; + +} + +.especificacao{ + font-size: 30px; + font-weight: bold; + +} + +.especificacao_endereco{ + font-size: 25px; + +} + +.btn_agendar_visita{ + + border: none; + background-color: #30CD9E; + color: white; + font-weight: bold; + font-size: 20px; + padding: 5px 10px 5px 10px; + +} + +.div_possivel_interesse{ + + text-align: left; + +} + +.btn_agendar{ + + border: none; + background-color: #30CD9E; + color: white; + font-weight: bold; + font-size: 20px; + padding: 5px 10px 5px 10px; + +} + +.btn_agendar:hover{ + + text-decoration: none; + color: white; + +} \ No newline at end of file diff --git a/templates/static/plataforma/img/heart.png b/templates/static/plataforma/img/heart.png new file mode 100644 index 0000000..eaff72b Binary files /dev/null and b/templates/static/plataforma/img/heart.png differ