Compare commits

..

No commits in common. "main" and "Release/support" have entirely different histories.

113 changed files with 2103 additions and 13522 deletions

View File

@ -1,48 +0,0 @@
# Napkin Runbook
## Curation Rules
- Re-prioritize on every read.
- Keep recurring, high-value notes only.
- Max 10 items per category.
- Each item includes date + "Do instead".
## Execution & Validation (Highest Priority)
1. **[2026-03-20] Use clean-build.sh after VS 2022 updates**
Do instead: run `./clean-build.sh` before debugging OAuth or NuGet issues.
2. **[2026-03-20] Google OAuth fails in Edge browser**
Do instead: test OAuth in Vivaldi or Chrome; clear browser data for localhost:49178.
## Razor View Editing
1. **[2026-03-20] replace_all corrupts @inject namespace if brand name appears in it**
Do instead: run replace_all BEFORE adding @inject, or use namespace-specific strings to avoid replacing C# namespace occurrences.
## Shell & Command Reliability
1. **[2026-03-20] Windows environment — use Unix shell syntax**
Do instead: use forward slashes and bash syntax (not PowerShell) in all shell commands.
## Domain Behavior Guardrails
1. **[2026-03-20] PageStatus enum has explicit numeric values**
Do instead: always reference by name (e.g., `PageStatus.Creating`), never by magic number. Values: Creating=6, PendingModeration=4, Rejected=5, Active=0, Inactive=3, Expired=1, PendingPayment=2.
2. **[2026-03-20] Preview tokens expire in 4 hours**
Do instead: generate fresh tokens via `POST /Admin/GeneratePreviewToken/{id}` before accessing non-Active pages.
3. **[2026-03-20] Non-Active pages require preview token for access**
Do instead: always append `?preview={token}` when testing Creating/PendingModeration/Rejected pages.
4. **[2026-03-20] Plans source of truth is appsettings.json ["Plans"] section**
Do instead: read plan limits/features from config, not README (README has outdated values). Plans: Trial(free,1p,3l), Basic(R$12.90,3p,8l), Professional(R$25.90,5p,20l,DECOY), Premium(R$29.90,15p,∞l,PDF), PremiumAffiliate(R$34.90,15p,∞l,links produto). Annual variants save 2 months.
5. **[2026-03-20] MaxLinks=-1 means unlimited (Premium and PremiumAffiliate)**
Do instead: check `if (maxLinks == -1) return true;` before comparing counts.
6. **[2026-03-20] Subscription model stores plan limits at time of purchase**
Do instead: read limits from Subscription entity (MaxLinks, AllowCustomThemes etc.), not from current plan config — they may diverge after plan changes.
## User Directives
1. **[2026-03-20] Project targets Brazilian/Spanish markets**
Do instead: use pt-BR or es as default locale in UI text; keep pricing in BRL (R$).
2. **[2026-03-20] appsettings.json contains live credentials committed to git**
Do instead: be aware that MongoDB, OAuth, SendGrid secrets are in the repo. Never log or expose them further. Do not add more secrets to appsettings.json.

View File

@ -30,32 +30,7 @@
"Bash(ssh:*)",
"Bash(cat:*)",
"Bash(dig:*)",
"Bash(git commit:*)",
"Bash(netstat:*)",
"Bash(ss:*)",
"Bash(lsof:*)",
"Bash(dotnet run:*)",
"Bash(dotnet user-secrets:*)",
"Bash(xargs grep:*)",
"mcp__stripe__list_products",
"mcp__stripe__list_prices",
"mcp__stripe__get_stripe_account_info",
"mcp__stripe__search_stripe_resources",
"Bash(docker exec:*)",
"mcp__stripe__list_subscriptions",
"mcp__stripe__create_product",
"mcp__stripe__create_price",
"Bash(git push:*)",
"mcp__stripe__stripe_api_execute",
"mcp__stripe__stripe_api_search",
"Bash(ctx csharp:*)",
"Bash(ctx auto:*)",
"Bash(git log:*)",
"Bash(git mv:*)",
"Bash(python3 -c \"import sys,json; [print\\(json.loads\\(l\\).get\\('MessageTemplate',''\\) + ' ' + str\\(json.loads\\(l\\).get\\('Level',''\\)\\)\\) for l in sys.stdin if 'categ' in l.lower\\(\\) or 'Error' in l or 'error' in l.lower\\(\\)]\")",
"Bash(python3 -c \" import sys, json for line in sys.stdin: line = line.strip\\(\\).rstrip\\(','\\) try: obj = json.loads\\(line\\) lvl = obj.get\\('Level',''\\) msg = obj.get\\('MessageTemplate',''\\) or obj.get\\('message',''\\) if lvl in \\('Error','Warning','Fatal'\\) or 'categ' in msg.lower\\(\\) or 'initial' in msg.lower\\(\\): print\\(f'[{lvl}] {msg}'\\) except: pass \")",
"Bash(grep -E \"\\\\.\\(cs|csproj\\)$\")",
"Bash(git -C /c/vscode/bcards log --oneline --follow src/BCards.Web/Views/Admin/CreatePage.cshtml)"
"Bash(git commit:*)"
]
},
"enableAllProjectMcpServers": false

View File

@ -1,34 +1,11 @@
name: BCards Multi-Tenant Deployment Pipeline
# ─── Required Gitea Secrets ────────────────────────────────────────────────
# secrets.SSH_PRIVATE_KEY SSH key for ubuntu@ on both OCI nodes
# secrets.STRIPE_SECRET_KEY Shared Stripe secret key
# secrets.STRIPE_WEBHOOK_SECRET Shared Stripe webhook secret
# secrets.GOOGLE_CLIENT_SECRET Google OAuth client secret
# secrets.MICROSOFT_CLIENT_SECRET Microsoft OAuth client secret
# secrets.SENDGRID_API_KEY SendGrid API key
#
# ─── Required Gitea Variables (vars.*) ────────────────────────────────────
# vars.STRIPE_PUBLISHABLE_KEY
# vars.STRIPE_ENVIRONMENT (default: test)
# vars.GOOGLE_CLIENT_ID
# vars.MICROSOFT_CLIENT_ID
# vars.OPENSEARCH_URL (default: http://localhost:9201)
# vars.MODERATOR_EMAIL
# vars.MODERATOR_EMAIL_1
# vars.MODERATOR_EMAIL_2
#
# ─── Per-Tenant Variables (optional, have defaults) ───────────────────────
# vars.SPICYLINKS_FROM_EMAIL (default: noreply@spicylinks.site)
# vars.SPICYLINKS_FROM_NAME (default: SpicyLinks)
# vars.LUZLINKS_FROM_EMAIL (default: noreply@luzlinks.site)
# vars.LUZLINKS_FROM_NAME (default: LuzLinks)
name: BCards Deployment Pipeline
on:
push:
branches:
- main
- 'Release/*'
# PRs apenas validam, não fazem deploy
pull_request:
branches: [ main ]
types: [opened, synchronize, reopened]
@ -36,11 +13,9 @@ on:
env:
REGISTRY: registry.redecarneir.us
IMAGE_NAME: bcards
SWARM_MANAGER: 141.148.162.114
SWARM_WORKER: 129.146.116.218
MONGODB_HOST: 192.168.0.100:27017
jobs:
# ─── Tests ────────────────────────────────────────────────────────────────
test:
name: Run Tests
runs-on: ubuntu-latest
@ -49,7 +24,11 @@ jobs:
- name: Test info
run: |
echo "🧪 Executando testes para ${{ github.ref_name }}"
echo "🎯 Trigger: ${{ github.event_name }}"
# Verificar se deve pular testes
SKIP_TESTS="${{ github.event.inputs.skip_tests || vars.SKIP_TESTS }}"
if [ "$SKIP_TESTS" == "true" ]; then
echo "⚠️ Testes PULADOS"
echo "TESTS_SKIPPED=true" >> $GITHUB_ENV
@ -89,7 +68,7 @@ jobs:
if: env.TESTS_SKIPPED == 'false'
run: dotnet test --no-build --configuration Release --verbosity normal --collect:"XPlat Code Coverage"
# ─── PR Validation (no deploy) ────────────────────────────────────────────
# Job específico para validação de PRs (sem deploy)
pr-validation:
name: PR Validation
runs-on: ubuntu-latest
@ -100,16 +79,19 @@ jobs:
- name: PR Validation Summary
run: |
echo "✅ Pull Request Validation Summary"
echo "🎯 Target: ${{ github.base_ref }}"
echo "📂 Source: ${{ github.head_ref }}"
echo "🎯 Target Branch: ${{ github.base_ref }}"
echo "📂 Source Branch: ${{ github.head_ref }}"
echo "🧪 Tests: ${{ needs.test.result }}"
echo "👤 Author: ${{ github.event.pull_request.user.login }}"
echo "📝 Title: ${{ github.event.pull_request.title }}"
echo ""
echo "✨ PR está pronto para merge!"
# ─── Build & Push (single image, all tenants share it) ───────────────────
build-and-push:
name: Build and Push Image
runs-on: [self-hosted, arm64, bcards]
needs: [test]
# Só faz build/push em push (não em PR)
if: github.event_name == 'push' && (needs.test.result == 'success' || needs.test.result == 'skipped')
steps:
@ -125,50 +107,84 @@ jobs:
id: settings
run: |
BRANCH_NAME="${{ github.ref_name }}"
if [ "$BRANCH_NAME" = "main" ]; then
# Main = Produção (ARM64) - usando Dockerfile simples
echo "tag=latest" >> $GITHUB_OUTPUT
echo "platform=linux/arm64" >> $GITHUB_OUTPUT
echo "environment=Production" >> $GITHUB_OUTPUT
echo "dockerfile=Dockerfile" >> $GITHUB_OUTPUT
echo "deploy_target=production" >> $GITHUB_OUTPUT
elif [[ "$BRANCH_NAME" == Release/* ]]; then
# Release = Swarm tests (Orange Pi arm64) - usando Dockerfile simples também
VERSION_RAW=${BRANCH_NAME#Release/}
# Only remove V/v if it's at the start and followed by a number (like v1.0.0)
VERSION=$(echo "$VERSION_RAW" | sed 's/^[Vv]\([0-9]\)/\1/')
[ -z "$VERSION" ] && VERSION="0.0.1"
echo "tag=$VERSION" >> $GITHUB_OUTPUT
echo "platform=linux/arm64" >> $GITHUB_OUTPUT
echo "environment=Testing" >> $GITHUB_OUTPUT
echo "dockerfile=Dockerfile" >> $GITHUB_OUTPUT
echo "deploy_target=testing" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
fi
SHORT_COMMIT=${GITHUB_SHA:0:7}
COMMIT_SHA=${{ github.sha }}
SHORT_COMMIT=${COMMIT_SHA:0:7}
echo "commit=$SHORT_COMMIT" >> $GITHUB_OUTPUT
echo "📦 Tag: ${{ steps.settings.outputs.tag }}"
echo "🏗️ Platform: ${{ steps.settings.outputs.platform }}"
echo "🌍 Environment: ${{ steps.settings.outputs.environment }}"
echo "🎯 Target: ${{ steps.settings.outputs.deploy_target }}"
- name: Build and push image
run: |
echo "🏗️ Building image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }}"
echo "🏗️ Building image for ${{ steps.settings.outputs.deploy_target }}..."
# Debug das variáveis
echo "Platform: ${{ steps.settings.outputs.platform }}"
echo "Dockerfile: ${{ steps.settings.outputs.dockerfile }}"
echo "Tag: ${{ steps.settings.outputs.tag }}"
# Verificar se o Dockerfile existe
if [ ! -f "${{ steps.settings.outputs.dockerfile }}" ]; then
echo "❌ Dockerfile não encontrado: ${{ steps.settings.outputs.dockerfile }}"
echo "📂 Arquivos na raiz:"
ls -la
echo "📂 Arquivos em src/BCards.Web/:"
ls -la src/BCards.Web/ || echo "Diretório não existe"
exit 1
else
echo "✅ Dockerfile encontrado: ${{ steps.settings.outputs.dockerfile }}"
fi
# Build para a plataforma correta
if [ "${{ steps.settings.outputs.deploy_target }}" = "production" ]; then
# Build para produção (main branch)
docker buildx build \
--platform linux/arm64 \
--file Dockerfile \
--platform ${{ steps.settings.outputs.platform }} \
--file ${{ steps.settings.outputs.dockerfile }} \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }} \
--push \
--progress=plain \
.
else
# Build para staging (Release branches)
docker buildx build \
--platform linux/arm64 \
--file Dockerfile \
--platform ${{ steps.settings.outputs.platform }} \
--file ${{ steps.settings.outputs.dockerfile }} \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.settings.outputs.tag }} \
--push \
--build-arg BUILD_CONFIGURATION=Testing \
--build-arg VERSION=${{ steps.settings.outputs.version || 'latest' }} \
--build-arg COMMIT=${{ steps.settings.outputs.commit }} \
--progress=plain \
.
fi
# ─── Deploy: bcards.site ──────────────────────────────────────────────────
deploy-bcards:
name: Deploy bcards.site
deploy-production:
name: Deploy to Production (ARM - OCI)
runs-on: ubuntu-latest
needs: [build-and-push]
if: github.ref_name == 'main'
@ -178,15 +194,32 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate appsettings for bcards
- name: Create Production Configuration
run: |
cat > appsettings.bcards.json << 'CONFIG_EOF'
echo "🔧 Creating appsettings.Production.json with environment variables..."
# Cria o arquivo de configuração para produção
cat > appsettings.Production.json << 'CONFIG_EOF'
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"BCards": "Information"
"Microsoft.EntityFrameworkCore": "Warning",
"BCards": "Information",
"BCards.Web.Services.GridFSImageStorage": "Debug"
},
"Console": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Information"
}
},
"File": {
"Path": "/app/logs/bcards-{Date}.log",
"LogLevel": {
"Default": "Information"
}
}
},
"AllowedHosts": "*",
@ -212,456 +245,249 @@ jobs:
"FromName": "${{ vars.SENDGRID_FROM_NAME || 'Ricardo Carneiro' }}"
},
"Plans": {
"Basic": { "Name": "Básico", "PriceId": "price_1TR10MBk8jHwC3c0iey23Ghb", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" },
"Professional": { "Name": "Profissional", "PriceId": "price_1TR10NBk8jHwC3c0yqmy8soD", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" },
"Premium": { "Name": "Premium", "PriceId": "price_1TR10OBk8jHwC3c0eZa77y31", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)" ], "Interval": "month" },
"PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1TR10PBk8jHwC3c0B1oIvvYY", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)" ], "Interval": "month" },
"BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1TR10NBk8jHwC3c0L4SDaWe9", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" },
"ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1TR10OBk8jHwC3c0IuyvrvRf", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" },
"PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1TR10PBk8jHwC3c0qngPYMUN", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Upload de PDFs (até 5 arquivos)", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" },
"PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1TR10QBk8jHwC3c0f8CBaD1n", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Upload de PDFs (até 10 arquivos)", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" }
"Basic": {
"Name": "Básico",
"PriceId": "price_1RycPaBMIadsOxJVKioZZofK",
"Price": 5.90,
"MaxPages": 3,
"MaxLinks": 8,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ],
"Interval": "month"
},
"Professional": {
"Name": "Profissional",
"PriceId": "price_1RycQmBMIadsOxJVGqjVMaOj",
"Price": 12.90,
"MaxPages": 5,
"MaxLinks": 20,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ],
"Interval": "month"
},
"Premium": {
"Name": "Premium",
"PriceId": "price_1RycRUBMIadsOxJVkxGOh3uu",
"Price": 19.90,
"MaxPages": 15,
"MaxLinks": -1,
"AllowPremiumThemes": true,
"AllowProductLinks": false,
"AllowAnalytics": true,
"SpecialModeration": false,
"Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados" ],
"Interval": "month"
},
"PremiumAffiliate": {
"Name": "Premium+Afiliados",
"PriceId": "price_1RycTaBMIadsOxJVeDLseXQq",
"Price": 29.90,
"MaxPages": 15,
"MaxLinks": -1,
"AllowPremiumThemes": true,
"AllowProductLinks": true,
"AllowAnalytics": true,
"SpecialModeration": true,
"Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ],
"Interval": "month"
},
"BasicYearly": {
"Name": "Básico Anual",
"PriceId": "price_1RycWgBMIadsOxJVGdtEeoMS",
"Price": 59.00,
"MaxPages": 3,
"MaxLinks": 8,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ],
"Interval": "year"
},
"ProfessionalYearly": {
"Name": "Profissional Anual",
"PriceId": "price_1RycXdBMIadsOxJV5cNX7dHm",
"Price": 129.00,
"MaxPages": 5,
"MaxLinks": 20,
"AllowPremiumThemes": false,
"AllowProductLinks": false,
"AllowAnalytics": true,
"Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ],
"Interval": "year"
},
"PremiumYearly": {
"Name": "Premium Anual",
"PriceId": "price_1RycYnBMIadsOxJVPdKmzy4m",
"Price": 199.00,
"MaxPages": 15,
"MaxLinks": -1,
"AllowPremiumThemes": true,
"AllowProductLinks": false,
"AllowAnalytics": true,
"SpecialModeration": false,
"Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ],
"Interval": "year"
},
"PremiumAffiliateYearly": {
"Name": "Premium+Afiliados Anual",
"PriceId": "price_1RycaEBMIadsOxJVEhsdB2Y1",
"Price": 299.00,
"MaxPages": 15,
"MaxLinks": -1,
"AllowPremiumThemes": true,
"AllowProductLinks": true,
"AllowAnalytics": true,
"SpecialModeration": true,
"Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ],
"Interval": "year"
}
},
"Moderation": {
"PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" },
"PriorityTimeframes": {
"Trial": "7.00:00:00",
"Basic": "7.00:00:00",
"Professional": "3.00:00:00",
"Premium": "1.00:00:00"
},
"MaxAttempts": 3,
"ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
"ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ]
"ModeratorEmails": [
"${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}",
"${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}"
]
},
"MongoDb": {
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin",
"DatabaseName": "BCardsDB"
"DatabaseName": "BCardsDB",
"MaxConnectionPoolSize": 100,
"ConnectTimeout": "30s",
"ServerSelectionTimeout": "30s",
"SocketTimeout": "30s"
},
"BaseUrl": "https://bcards.site",
"Tenant": {
"SiteName": "BCards",
"SiteDescription": "Crie sua página profissional com links organizados. A melhor alternativa para ter sua bio / links. Criada para profissionais e empresas no Brasil.",
"Tagline": "Sua bio de links profissional",
"SupportEmail": "suporte@bcards.site",
"ContentFolder": "bcards",
"AgeGated": false,
"UrlExample": "bcards.site/corretor/seu-nome",
"DpoEmail": "dpo@bcards.site",
"HeroHeadline": "Sua presença digital profissional em um só lugar",
"HeroDescription": "Organize seus links, portfólio, contatos e redes sociais em uma página única e elegante. Criado para profissionais e empresas brasileiras que querem ser encontrados.",
"HeroCtaText": "Criar Minha Página Grátis",
"FeaturesHeadline": "Por que profissionais escolhem o {SiteName}?",
"Features": [
{ "Icon": "🎨", "Title": "Temas Profissionais", "Description": "Mais de 40 temas para sua área: corretores, advogados, médicos, consultores e muito mais." },
{ "Icon": "📊", "Title": "Analytics de Verdade", "Description": "Saiba quantas pessoas acessaram sua página, quais links clicaram e de onde vieram." },
{ "Icon": "🔗", "Title": "URLs com Credibilidade", "Description": "Sua URL tem contexto profissional: bcards.site/corretor/seu-nome — transmite autoridade instantânea." }
],
"CtaHeadline": "Pronto para se destacar?",
"CtaDescription": "Junte-se a milhares de profissionais que já têm sua presença digital organizada no BCards.",
"CtaButtonText": "Criar Minha Página Grátis",
"MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor",
"FooterTagline": "Sua presença digital profissional, simplificada.",
"HeroGradient": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"PrimaryColor": "#667eea",
"PrimaryColorDark": "#5a6fd6"
"Environment": {
"Name": "Production",
"IsStagingEnvironment": false,
"AllowTestData": false,
"EnableDetailedErrors": false
},
"Support": {
"TelegramUrl": "https://t.me/jobmakerbr",
"FormspreeUrl": "https://formspree.io/f/xpwynqpj",
"EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ],
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
"EnableRatingForAllUsers": true
"Performance": {
"EnableCaching": true,
"CacheExpirationMinutes": 30,
"EnableCompression": true,
"EnableResponseCaching": true
},
"Security": {
"EnableHttpsRedirection": true,
"EnableHsts": true,
"RequireHttpsMetadata": true
},
"HealthChecks": {
"Enabled": true,
"Endpoints": {
"Health": "/health",
"Ready": "/ready",
"Live": "/live"
},
"MongoDb": {
"Enabled": true,
"Timeout": "10s"
}
},
"Features": {
"EnablePreviewMode": true,
"EnableModerationWorkflow": true,
"EnableAnalytics": true,
"EnableFileUploads": true,
"MaxFileUploadSize": "5MB"
},
"Serilog": {
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}"
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://localhost:9201' }}"
}
}
CONFIG_EOF
echo "✅ appsettings.bcards.json gerado"
- name: Deploy bcards stack
echo "✅ Configuration file created!"
echo "🔍 File content (sensitive data masked):"
cat appsettings.Production.json | sed 's/"[^"]*_[0-9A-Za-z_]*"/"***MASKED***"/g'
- name: Deploy to Production Swarm
run: |
echo "🚀 Deploying to production Docker Swarm (ARM64)..."
# Configura SSH
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
# ── Sync Content to both nodes ──────────────────────────────────────
for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
echo "📂 Syncing content to $NODE..."
ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'sudo mkdir -p /opt/bcards-content/bcards && sudo chown -R ubuntu:ubuntu /opt/bcards-content'
rsync -az --delete -e "ssh -o StrictHostKeyChecking=no" src/BCards.Web/Content/Tenants/bcards/ ubuntu@$NODE:/opt/bcards-content/bcards/
done
# Adiciona hosts conhecidos
ssh-keyscan -H 141.148.162.114 >> ~/.ssh/known_hosts
ssh-keyscan -H 129.146.116.218 >> ~/.ssh/known_hosts
# ── Deploy stack on manager ─────────────────────────────────────────
scp -o StrictHostKeyChecking=no appsettings.bcards.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
scp -o StrictHostKeyChecking=no deploy/docker-stack-bcards.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
# Testa a chave SSH
ssh-add ~/.ssh/id_rsa 2>/dev/null || echo "SSH key loaded"
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
# Upload configuration and stack file to swarm manager
echo "📤 Uploading files to Swarm manager..."
scp -o StrictHostKeyChecking=no appsettings.Production.json ubuntu@141.148.162.114:/tmp/
scp -o StrictHostKeyChecking=no deploy/docker-stack.yml ubuntu@141.148.162.114:/tmp/
# Deploy to Docker Swarm
ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 << 'EOF'
set -e
echo "🔄 Updating bcards stack..."
docker config rm bcards-appsettings 2>/dev/null || true
echo "🔄 Updating Docker Swarm stack..."
# Update Docker config with new appsettings
echo "📝 Updating bcards-appsettings config..."
# Remove old config (will fail if in use, that's ok - swarm will use it until update)
docker config rm bcards-appsettings 2>/dev/null || echo "Config in use or doesn't exist, will create new one"
# Create new config with timestamp to force update
CONFIG_NAME="bcards-appsettings-$(date +%s)"
docker config create ${CONFIG_NAME} /tmp/appsettings.bcards.json
docker config create ${CONFIG_NAME} /tmp/appsettings.Production.json
sed "s/bcards-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-bcards.yml > /tmp/docker-stack-bcards-final.yml
# Update stack file to use new config name
sed "s/bcards-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack.yml > /tmp/docker-stack-updated.yml
docker stack deploy -c /tmp/docker-stack-bcards-final.yml bcards --with-registry-auth
echo "🐳 Deploying stack to Swarm (rolling update, zero downtime)..."
docker stack deploy -c /tmp/docker-stack-updated.yml bcards --with-registry-auth
rm -f /tmp/appsettings.bcards.json /tmp/docker-stack-bcards.yml /tmp/docker-stack-bcards-final.yml
echo "✅ bcards stack atualizado!"
EOF
- name: Health Check bcards
run: |
sleep 30
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
'curl -sf http://localhost:8080/health && echo "✅ bcards healthy" || echo "⚠️ bcards health check failed"'
# ─── Deploy: spicylinks.site ──────────────────────────────────────────────
deploy-spicylinks:
name: Deploy spicylinks.site
runs-on: ubuntu-latest
needs: [build-and-push]
if: github.ref_name == 'main'
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate appsettings for spicylinks
run: |
cat > appsettings.spicylinks.json << 'CONFIG_EOF'
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"BCards": "Information"
}
},
"AllowedHosts": "*",
"Stripe": {
"PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}",
"SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}",
"WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}",
"Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}"
},
"Authentication": {
"Google": {
"ClientId": "${{ vars.GOOGLE_CLIENT_ID }}",
"ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}"
},
"Microsoft": {
"ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}",
"ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}"
}
},
"SendGrid": {
"ApiKey": "${{ secrets.SENDGRID_API_KEY }}",
"FromEmail": "${{ vars.SPICYLINKS_FROM_EMAIL || 'noreply@spicylinks.site' }}",
"FromName": "${{ vars.SPICYLINKS_FROM_NAME || 'SpicyLinks' }}"
},
"Plans": {
"Basic": { "Name": "Básico", "PriceId": "price_1TR10QBk8jHwC3c06u8j4XVY", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" },
"Professional": { "Name": "Profissional", "PriceId": "price_1TR10RBk8jHwC3c0KfunnYYn", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" },
"Premium": { "Name": "Premium", "PriceId": "price_1TR10SBk8jHwC3c0gMqUEp7m", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas (básicos + premium)", "Suporte prioritário", "Links ilimitados" ], "Interval": "month" },
"PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1TR10TBk8jHwC3c0uPOZVZ4P", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], "Interval": "month" },
"BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1TR10RBk8jHwC3c0C8aOMAYE", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" },
"ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1TR10SBk8jHwC3c0X7LBy3UU", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" },
"PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1TR10TBk8jHwC3c0TaDIA6bD", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" },
"PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1TR10UBk8jHwC3c0NF66MzC7", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" }
},
"Moderation": {
"PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" },
"MaxAttempts": 3,
"ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
"ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ]
},
"MongoDb": {
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/SpicyLinksDB?replicaSet=rs0&authSource=admin",
"DatabaseName": "SpicyLinksDB"
},
"BaseUrl": "https://spicylinks.site",
"Tenant": {
"SiteName": "SpicyLinks",
"SiteDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes sociais e conteúdo exclusivo em uma única bio.",
"Tagline": "Seu conteúdo exclusivo, um link só",
"SupportEmail": "suporte@spicylinks.site",
"ContentFolder": "spicylinks",
"AgeGated": true,
"UrlExample": "spicylinks.site/modelo/seu-nome",
"DpoEmail": "dpo@spicylinks.site",
"HeroHeadline": "Seu conteúdo exclusivo, um link só",
"HeroDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes e conteúdo exclusivo em uma bio elegante.",
"HeroCtaText": "Criar Minha Bio",
"FeaturesHeadline": "Por que criadores escolhem o {SiteName}?",
"Features": [
{ "Icon": "❤️", "Title": "Tudo num Só Link", "Description": "Instagram, Twitter/X, OnlyFans, lista de desejos e mais — tudo em uma bio única, elegante e fácil de compartilhar." },
{ "Icon": "🔒", "Title": "Verificação de Idade", "Description": "Acesso protegido com verificação de idade automática. Plataforma segura, discreta e responsável." },
{ "Icon": "📊", "Title": "Saiba Quem Te Visita", "Description": "Analytics detalhado de cliques, visualizações e origem do tráfego para otimizar suas conversões." }
],
"CtaHeadline": "Pronta para monetizar seu conteúdo?",
"CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.",
"CtaButtonText": "Criar Minha Bio",
"MetaKeywords": "bio links criadora, creator bio, linktree conteudo adulto, links onlyfans, bio instagram criadora",
"FooterTagline": "Seu conteúdo, sua identidade.",
"HeroGradient": "linear-gradient(135deg, #ff416c 0%, #c0392b 100%)",
"PrimaryColor": "#e63946",
"PrimaryColorDark": "#c1121f",
"DefaultCategories": [
{ "Icon": "📸", "Name": "Modelos", "Slug": "modelos", "Description": "Modelos e criadores de conteúdo visual", "SeoKeywords": [ "modelo", "fotografia", "conteúdo", "criadora" ] },
{ "Icon": "⭐", "Name": "Influencers", "Slug": "influencers","Description": "Influencers e personalidades digitais", "SeoKeywords": [ "influencer", "digital", "social media" ] },
{ "Icon": "💪", "Name": "Fitness", "Slug": "fitness", "Description": "Criadores de conteúdo fitness e lifestyle", "SeoKeywords": [ "fitness", "academia", "saúde", "corpo" ] },
{ "Icon": "🎨", "Name": "Arte", "Slug": "arte", "Description": "Artistas e criadores de conteúdo visual", "SeoKeywords": [ "arte", "ilustração", "design", "criativo" ] },
{ "Icon": "🎵", "Name": "Música", "Slug": "musica", "Description": "Músicos e cantores independentes", "SeoKeywords": [ "música", "cantor", "artista", "show" ] },
{ "Icon": "🎮", "Name": "Gaming", "Slug": "gaming", "Description": "Streamers e criadores de conteúdo gamer", "SeoKeywords": [ "gaming", "streamer", "games", "twitch" ] },
{ "Icon": "🦸", "Name": "Cosplay", "Slug": "cosplay", "Description": "Cosplayers e criadores de fantasia", "SeoKeywords": [ "cosplay", "anime", "fantasia", "cosplayer" ] },
{ "Icon": "💋", "Name": "Lifestyle", "Slug": "lifestyle", "Description": "Criadores de conteúdo lifestyle e entretenimento", "SeoKeywords": [ "lifestyle", "entretenimento", "diversão" ] }
],
"AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site Geral", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite o domínio e caminho", "Color": "bg-primary" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "seuemail@exemplo.com", "Instructions": "Digite apenas o email", "Color": "bg-success" },
{ "Icon": "fas fa-phone", "Label": "📞 Telefone", "Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Número com código do país", "Color": "bg-success" },
{ "Icon": "fab fa-instagram", "Label": "📸 Instagram", "Prefix": "https://instagram.com/","Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-danger" },
{ "Icon": "fab fa-twitter", "Label": "🐦 Twitter/X", "Prefix": "https://x.com/", "Placeholder": "seu_usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-dark" },
{ "Icon": "fab fa-tiktok", "Label": "🎵 TikTok", "Prefix": "https://tiktok.com/@", "Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-dark" },
{ "Icon": "fas fa-shopping-cart","Label": "🛒 Lista de Desejos","Prefix": "https://", "Placeholder": "wishlist.com/...", "Instructions": "Link para lista de desejos", "Color": "bg-warning" },
{ "Icon": "fas fa-heart", "Label": "❤️ Assinatura", "Prefix": "https://", "Placeholder": "plataforma.com/...", "Instructions": "Link para plataforma paga", "Color": "bg-danger" }
]
},
"Support": {
"TelegramUrl": "https://t.me/jobmakerbr",
"FormspreeUrl": "https://formspree.io/f/xpwynqpj",
"EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ],
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
"EnableRatingForAllUsers": true
},
"Serilog": {
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}"
}
}
CONFIG_EOF
echo "✅ appsettings.spicylinks.json gerado"
- name: Deploy spicylinks stack
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
# ── Sync Content to both nodes ──────────────────────────────────────
for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
echo "📂 Syncing spicylinks content to $NODE..."
ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'sudo mkdir -p /opt/bcards-content/spicylinks && sudo chown -R ubuntu:ubuntu /opt/bcards-content'
rsync -az --delete -e "ssh -o StrictHostKeyChecking=no" src/BCards.Web/Content/Tenants/spicylinks/ ubuntu@$NODE:/opt/bcards-content/spicylinks/
done
# ── Deploy stack on manager ─────────────────────────────────────────
scp -o StrictHostKeyChecking=no appsettings.spicylinks.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
scp -o StrictHostKeyChecking=no deploy/docker-stack-spicylinks.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
set -e
echo "🔄 Updating spicylinks stack..."
docker config rm spicylinks-appsettings 2>/dev/null || true
CONFIG_NAME="spicylinks-appsettings-$(date +%s)"
docker config create ${CONFIG_NAME} /tmp/appsettings.spicylinks.json
sed "s/spicylinks-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-spicylinks.yml > /tmp/docker-stack-spicylinks-final.yml
docker stack deploy -c /tmp/docker-stack-spicylinks-final.yml spicylinks --with-registry-auth
rm -f /tmp/appsettings.spicylinks.json /tmp/docker-stack-spicylinks.yml /tmp/docker-stack-spicylinks-final.yml
echo "✅ spicylinks stack atualizado!"
EOF
- name: Health Check spicylinks
run: |
sleep 30
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
'curl -sf http://localhost:8082/health && echo "✅ spicylinks healthy" || echo "⚠️ spicylinks health check failed"'
# ─── Deploy: luzlinks.site ────────────────────────────────────────────────
deploy-luzlinks:
name: Deploy luzlinks.site
runs-on: ubuntu-latest
needs: [build-and-push]
if: github.ref_name == 'main'
environment: production
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Generate appsettings for luzlinks
run: |
cat > appsettings.luzlinks.json << 'CONFIG_EOF'
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"BCards": "Information"
}
},
"AllowedHosts": "*",
"Stripe": {
"PublishableKey": "${{ vars.STRIPE_PUBLISHABLE_KEY }}",
"SecretKey": "${{ secrets.STRIPE_SECRET_KEY }}",
"WebhookSecret": "${{ secrets.STRIPE_WEBHOOK_SECRET }}",
"Environment": "${{ vars.STRIPE_ENVIRONMENT || 'test' }}"
},
"Authentication": {
"Google": {
"ClientId": "${{ vars.GOOGLE_CLIENT_ID }}",
"ClientSecret": "${{ secrets.GOOGLE_CLIENT_SECRET }}"
},
"Microsoft": {
"ClientId": "${{ vars.MICROSOFT_CLIENT_ID }}",
"ClientSecret": "${{ secrets.MICROSOFT_CLIENT_SECRET }}"
}
},
"SendGrid": {
"ApiKey": "${{ secrets.SENDGRID_API_KEY }}",
"FromEmail": "${{ vars.LUZLINKS_FROM_EMAIL || 'noreply@luzlinks.site' }}",
"FromName": "${{ vars.LUZLINKS_FROM_NAME || 'LuzLinks' }}"
},
"Plans": {
"Basic": { "Name": "Básico", "PriceId": "price_1TR10UBk8jHwC3c0C9UJTNYg", "Price": 12.90, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples" ], "Interval": "month" },
"Professional": { "Name": "Profissional", "PriceId": "price_1TR10VBk8jHwC3c0v6nlFB0R", "Price": 25.90, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado" ], "Interval": "month" },
"Premium": { "Name": "Premium", "PriceId": "price_1TR10WBk8jHwC3c0QigqeR7b", "Price": 29.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados" ], "Interval": "month" },
"PremiumAffiliate": { "Name": "Premium+Afiliados", "PriceId": "price_1TR10XBk8jHwC3c03SfR3Z4v", "Price": 34.90, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados" ], "Interval": "month" },
"BasicYearly": { "Name": "Básico Anual", "PriceId": "price_1TR10VBk8jHwC3c000wYtGxR", "Price": 129.00, "MaxPages": 3, "MaxLinks": 8, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics simples", "Economize R$ 11,80 (2 meses grátis)" ], "Interval": "year" },
"ProfessionalYearly": { "Name": "Profissional Anual", "PriceId": "price_1TR10WBk8jHwC3c0zCitaA4j", "Price": 259.00, "MaxPages": 5, "MaxLinks": 20, "AllowPremiumThemes": false, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": false, "MaxDocuments": 0, "Features": [ "URL Personalizada", "20 temas básicos", "Analytics avançado", "Economize R$ 25,80 (2 meses grátis)" ], "Interval": "year" },
"PremiumYearly": { "Name": "Premium Anual", "PriceId": "price_1TR10XBk8jHwC3c0MSZFXh7x", "Price": 299.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": false, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 5, "Features": [ "URL Personalizada", "40 temas", "Suporte prioritário", "Links ilimitados", "Economize R$ 39,80 (2 meses grátis)" ], "Interval": "year" },
"PremiumAffiliateYearly": { "Name": "Premium+Afiliados Anual", "PriceId": "price_1TR10YBk8jHwC3c0EzYrA0PJ", "Price": 349.00, "MaxPages": 15, "MaxLinks": -1, "AllowPremiumThemes": true, "AllowProductLinks": true, "AllowAnalytics": true, "AllowDocumentUpload": true, "MaxDocuments": 10, "Features": [ "Tudo do Premium", "Links de produto", "Moderação especial", "Até 10 links afiliados", "Economize R$ 59,80 (2 meses grátis)" ], "Interval": "year" }
},
"Moderation": {
"PriorityTimeframes": { "Trial": "7.00:00:00", "Basic": "7.00:00:00", "Professional": "3.00:00:00", "Premium": "1.00:00:00" },
"MaxAttempts": 3,
"ModeratorEmail": "${{ vars.MODERATOR_EMAIL || 'ricardo.carneiro@jobmaker.com.br' }}",
"ModeratorEmails": [ "${{ vars.MODERATOR_EMAIL_1 || 'rrcgoncalves@gmail.com' }}", "${{ vars.MODERATOR_EMAIL_2 || 'rirocarneiro@gmail.com' }}" ]
},
"MongoDb": {
"ConnectionString": "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/LuzLinksDB?replicaSet=rs0&authSource=admin",
"DatabaseName": "LuzLinksDB"
},
"BaseUrl": "https://luzlinks.site",
"Tenant": {
"SiteName": "LuzLinks",
"SiteDescription": "A plataforma para pastores, padres, líderes religiosos e ministérios. Reúna seus estudos bíblicos, eventos, lives e canal em uma única página de fé.",
"Tagline": "Conecte sua comunidade em um único link",
"SupportEmail": "suporte@luzlinks.site",
"ContentFolder": "luzlinks",
"AgeGated": false,
"UrlExample": "luzlinks.site/pastor/seu-nome",
"DpoEmail": "dpo@luzlinks.site",
"HeroHeadline": "Conecte sua comunidade em um único link",
"HeroDescription": "A plataforma ideal para pastores, padres, líderes e ministérios. Reúna seus estudos bíblicos, agenda de cultos, canal e dízimos em uma só página.",
"HeroCtaText": "Criar Minha Bio de Fé",
"FeaturesHeadline": "Por que líderes religiosos usam o {SiteName}?",
"Features": [
{ "Icon": "📖", "Title": "Conteúdo Espiritual Organizado", "Description": "Concentre seus estudos bíblicos, séries de pregações, agenda de cultos e canal do YouTube em um só link." },
{ "Icon": "🙏", "Title": "Facilite Dízimos e Ofertas", "Description": "Link direto para doações do seu ministério. Simplifique as ofertas e dízimos da sua congregação." },
{ "Icon": "📅", "Title": "Agenda e Eventos", "Description": "Compartilhe retiros, cultos especiais e eventos com toda a comunidade de forma simples e organizada." }
],
"CtaHeadline": "Compartilhe sua mensagem com o mundo",
"CtaDescription": "Líderes de toda denominação já usam o LuzLinks para alcançar mais pessoas com sua mensagem de fé.",
"CtaButtonText": "Criar Minha Bio de Fé",
"MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja",
"FooterTagline": "Conectando fé e comunidade.",
"HeroGradient": "linear-gradient(135deg, #5b9bd5 0%, #1a5276 100%)",
"PrimaryColor": "#2471a3",
"PrimaryColorDark": "#1a5276",
"DefaultCategories": [
{ "Icon": "🙏", "Name": "Pastores", "Slug": "pastor", "Description": "Pastores evangélicos, protestantes e pentecostais", "SeoKeywords": [ "pastor", "evangélico", "protestante", "pentecostal", "pregador" ] },
{ "Icon": "✝️", "Name": "Padres", "Slug": "padre", "Description": "Sacerdotes, padres e religiosos da Igreja Católica", "SeoKeywords": [ "padre", "sacerdote", "católico", "pároco", "religioso" ] },
{ "Icon": "⛪", "Name": "Igrejas", "Slug": "igreja", "Description": "Congregações, comunidades de fé e denominações", "SeoKeywords": [ "igreja", "congregação", "comunidade", "denominação", "templo" ] },
{ "Icon": "🌟", "Name": "Ministérios", "Slug": "ministerio", "Description": "Ministérios, organizações e missões cristãs", "SeoKeywords": [ "ministério", "missão", "organização cristã", "obra" ] },
{ "Icon": "🎵", "Name": "Louvor e Adoração", "Slug": "louvor", "Description": "Ministérios de louvor, bandas gospel e cantores cristãos", "SeoKeywords": [ "louvor", "adoração", "gospel", "banda", "música cristã" ] },
{ "Icon": "👨‍👩‍👧", "Name": "Família e Jovens", "Slug": "familia", "Description": "Líderes de grupos de jovens, casais e família", "SeoKeywords": [ "jovens", "família", "casais", "célula", "grupo" ] },
{ "Icon": "📖", "Name": "Estudos Bíblicos", "Slug": "estudos", "Description": "Mestres, professores bíblicos e teólogos", "SeoKeywords": [ "estudo bíblico", "teologia", "mestre", "professor", "bíblia" ] },
{ "Icon": "🌍", "Name": "Missionários", "Slug": "missionario","Description": "Missionários e evangelistas nacionais e internacionais", "SeoKeywords": [ "missionário", "evangelista", "evangelismo", "missões" ] },
{ "Icon": "📻", "Name": "Mídia Cristã", "Slug": "midia", "Description": "Podcasts, canais, rádios e comunicação cristã", "SeoKeywords": [ "podcast", "canal cristão", "rádio evangélica", "mídia" ] },
{ "Icon": "🤝", "Name": "Assistência Social", "Slug": "assistencia","Description": "Projetos sociais, pastorais de assistência e ONGs cristãs","SeoKeywords": [ "assistência social", "projeto social", "ONG", "pastoral" ] }
],
"AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site / Ministério", "Prefix": "https://", "Placeholder": "ministerio.com.br", "Instructions": "Digite o domínio do site", "Color": "bg-primary" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "contato@ministerio.com", "Instructions": "Digite apenas o email", "Color": "bg-success" },
{ "Icon": "fas fa-phone", "Label": "📞 Telefone / WhatsApp", "Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Número com código do país", "Color": "bg-success" },
{ "Icon": "fab fa-youtube", "Label": "📺 YouTube", "Prefix": "https://youtube.com/", "Placeholder": "@canal ou c/CANAL", "Instructions": "Digite o canal ou @usuário", "Color": "bg-danger" },
{ "Icon": "fab fa-instagram", "Label": "📸 Instagram", "Prefix": "https://instagram.com/", "Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-danger" },
{ "Icon": "fas fa-book", "Label": "📖 Estudo / Série", "Prefix": "https://", "Placeholder": "link-do-estudo.com", "Instructions": "Link para estudo bíblico ou série", "Color": "bg-info" },
{ "Icon": "fas fa-calendar", "Label": "📅 Agenda / Eventos", "Prefix": "https://", "Placeholder": "calendly.com/seunome", "Instructions": "Link para agenda ou evento", "Color": "bg-warning" },
{ "Icon": "fas fa-donate", "Label": "🙏 Dízimos / Ofertas", "Prefix": "https://", "Placeholder": "pix.com.br/ministerio", "Instructions": "Link para doações ou dízimos", "Color": "bg-success" },
{ "Icon": "fas fa-map-marker-alt","Label": "📍 Localização", "Prefix": "https://maps.google.com/?q=", "VisualPrefix": "📍 Maps:", "Placeholder": "Rua da Igreja, 123", "Instructions": "Endereço da igreja/ministério", "Color": "bg-warning" },
{ "Icon": "fas fa-download", "Label": "⬇️ Material / Apostila","Prefix": "https://", "Placeholder": "drive.google.com/...", "Instructions": "Link para download de material", "Color": "bg-secondary" }
]
},
"Support": {
"TelegramUrl": "https://t.me/jobmakerbr",
"FormspreeUrl": "https://formspree.io/f/xpwynqpj",
"EnableTelegramForPlans": [ "Premium", "PremiumAffiliate" ],
"EnableFormForPlans": [ "Basic", "Professional", "Premium", "PremiumAffiliate" ],
"EnableRatingForAllUsers": true
},
"Serilog": {
"OpenSearchUrl": "${{ vars.OPENSEARCH_URL || 'http://141.148.162.114:19201' }}"
}
}
CONFIG_EOF
echo "✅ appsettings.luzlinks.json gerado"
- name: Deploy luzlinks stack
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
# ── Sync Content to both nodes ──────────────────────────────────────
for NODE in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
echo "📂 Syncing luzlinks content to $NODE..."
ssh -o StrictHostKeyChecking=no ubuntu@$NODE 'sudo mkdir -p /opt/bcards-content/luzlinks && sudo chown -R ubuntu:ubuntu /opt/bcards-content'
rsync -az --delete -e "ssh -o StrictHostKeyChecking=no" src/BCards.Web/Content/Tenants/luzlinks/ ubuntu@$NODE:/opt/bcards-content/luzlinks/
done
# ── Deploy stack on manager ─────────────────────────────────────────
scp -o StrictHostKeyChecking=no appsettings.luzlinks.json ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
scp -o StrictHostKeyChecking=no deploy/docker-stack-luzlinks.yml ubuntu@${{ env.SWARM_MANAGER }}:/tmp/
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} << 'EOF'
set -e
echo "🔄 Updating luzlinks stack..."
# Remove stack luslinks legada se ainda existir
if docker stack ls --format '{{.Name}}' | grep -q '^luslinks$'; then
echo "🗑️ Removendo stack luslinks legada..."
docker stack rm luslinks
echo "⏳ Waiting for service to update..."
sleep 10
fi
docker config rm luzlinks-appsettings 2>/dev/null || true
CONFIG_NAME="luzlinks-appsettings-$(date +%s)"
docker config create ${CONFIG_NAME} /tmp/appsettings.luzlinks.json
# Show service status
docker service ls --filter name=bcards_bcards-app
docker service ps bcards_bcards-app --no-trunc --filter "desired-state=running" | head -10
sed "s/luzlinks-appsettings/${CONFIG_NAME}/g" /tmp/docker-stack-luzlinks.yml > /tmp/docker-stack-luzlinks-final.yml
echo "🧹 Cleaning up standalone containers if they exist..."
docker stop bcards-prod 2>/dev/null || echo "No standalone container to stop"
docker rm bcards-prod 2>/dev/null || echo "No standalone container to remove"
docker stack deploy -c /tmp/docker-stack-luzlinks-final.yml luzlinks --with-registry-auth
# Clean up temp files
rm -f /tmp/appsettings.Production.json /tmp/docker-stack.yml /tmp/docker-stack-updated.yml
rm -f /tmp/appsettings.luzlinks.json /tmp/docker-stack-luzlinks.yml /tmp/docker-stack-luzlinks-final.yml
echo "✅ luzlinks stack atualizado!"
echo "✅ Swarm stack updated successfully!"
EOF
- name: Health Check luzlinks
- name: Health Check Production
run: |
echo "🏥 Verificando saúde dos servidores de produção..."
sleep 30
ssh -o StrictHostKeyChecking=no ubuntu@${{ env.SWARM_MANAGER }} \
'curl -sf http://localhost:8083/health && echo "✅ luzlinks healthy" || echo "⚠️ luzlinks health check failed"'
# ─── Release branch deploy (test swarm) ───────────────────────────────────
# Verifica Servidor 1
echo "Verificando Servidor 1 (ARM)..."
ssh -o StrictHostKeyChecking=no ubuntu@141.148.162.114 'curl -f http://localhost:8080/health || echo "⚠️ Servidor 1 pode não estar respondendo"'
# Verifica Servidor 2
echo "Verificando Servidor 2 (ARM)..."
ssh -o StrictHostKeyChecking=no ubuntu@129.146.116.218 'curl -f http://localhost:8080/health || echo "⚠️ Servidor 2 pode não estar respondendo"'
deploy-test:
name: Deploy to Release Swarm (ARM)
runs-on: [self-hosted, arm64, bcards]
@ -677,6 +503,7 @@ jobs:
run: |
BRANCH_NAME="${{ github.ref_name }}"
VERSION_RAW=${BRANCH_NAME#Release/}
# Only remove V/v if it's at the start and followed by a number (like v1.0.0)
VERSION=$(echo "$VERSION_RAW" | sed 's/^[Vv]\([0-9]\)/\1/')
[ -z "$VERSION" ] && VERSION="0.0.1"
echo "version=$VERSION" >> $GITHUB_OUTPUT
@ -686,11 +513,17 @@ jobs:
run: |
mkdir -p artifacts
BCARDS_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}
# Replace ${BCARDS_IMAGE} with actual image name using sed
sed "s|\${BCARDS_IMAGE}|${BCARDS_IMAGE}|g" deploy/docker-stack.release.yml > artifacts/docker-stack.release.yml
echo "🔧 Generated manifest with image: ${BCARDS_IMAGE}"
echo "📄 Manifest content:"
head -10 artifacts/docker-stack.release.yml
- name: Deploy to release swarm
run: |
echo "🚀 Deploying release stack to Orange Pi swarm..."
docker stack deploy -c artifacts/docker-stack.release.yml bcards-release
- name: Await release service readiness
@ -707,19 +540,20 @@ jobs:
sleep 5
ATTEMPTS=$((ATTEMPTS-1))
done
if [ "$REPLICAS" != "1/1" ]; then
echo "❌ Serviço não atingiu 1/1 réplica"
docker service ps bcards-release_bcards-release
exit 1
fi
docker service ps bcards-release_bcards-release
# ─── Cleanup ──────────────────────────────────────────────────────────────
cleanup:
name: Cleanup Old Resources
runs-on: ubuntu-latest
needs: [deploy-bcards, deploy-spicylinks, deploy-luzlinks, deploy-test]
if: always() && (needs.deploy-bcards.result == 'success' || needs.deploy-spicylinks.result == 'success' || needs.deploy-luzlinks.result == 'success' || needs.deploy-test.result == 'success')
needs: [deploy-production, deploy-test]
if: always() && (needs.deploy-production.result == 'success' || needs.deploy-test.result == 'success')
steps:
- name: Cleanup containers and images
@ -730,34 +564,28 @@ jobs:
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ env.SWARM_MANAGER }} >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan -H ${{ env.SWARM_WORKER }} >> ~/.ssh/known_hosts 2>/dev/null
ssh-keyscan -H 141.148.162.114 >> ~/.ssh/known_hosts
ssh-keyscan -H 129.146.116.218 >> ~/.ssh/known_hosts
ssh-add ~/.ssh/id_rsa 2>/dev/null || echo "SSH key loaded"
for SERVER in ${{ env.SWARM_MANAGER }} ${{ env.SWARM_WORKER }}; do
echo "🧹 Limpando $SERVER..."
ssh -o StrictHostKeyChecking=no ubuntu@$SERVER << 'EOF'
for server in 141.148.162.114 129.146.116.218; do
echo "🧹 Limpando servidor $server..."
ssh -o StrictHostKeyChecking=no ubuntu@$server << 'EOF'
docker container prune -f
docker image prune -f
docker network prune -f
# Remove stale swarm configs (keep last 3 per tenant)
for TENANT in bcards spicylinks luzlinks; do
docker config ls --filter "name=${TENANT}-appsettings" --format "{{.ID}} {{.Name}}" \
| sort -k2 | head -n -3 | awk '{print $1}' \
| xargs -r docker config rm 2>/dev/null || true
done
EOF
done
else
echo " Release branch: limpeza remota ignorada."
echo " Release branch: limpeza remota ignorada (Swarm gerencia recursos)."
fi
echo "✅ Limpeza concluída!"
# ─── Summary ──────────────────────────────────────────────────────────────
deployment-summary:
name: Deployment Summary
runs-on: ubuntu-latest
needs: [deploy-bcards, deploy-spicylinks, deploy-luzlinks, deploy-test]
needs: [deploy-production, deploy-test]
if: always()
steps:
@ -770,15 +598,14 @@ jobs:
echo "🏗️ Registry: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
if [ "${{ github.ref_name }}" = "main" ]; then
echo "🌍 Environment: Production (Multi-Tenant)"
echo "🖥️ Manager: ${{ env.SWARM_MANAGER }}"
echo "🖥️ Worker: ${{ env.SWARM_WORKER }}"
echo ""
echo " bcards.site → :8080 [${{ needs.deploy-bcards.result }}]"
echo " spicylinks.site → :8082 [${{ needs.deploy-spicylinks.result }}]"
echo " luzlinks.site → :8083 [${{ needs.deploy-luzlinks.result }}]"
echo "🌍 Environment: Production (Swarm ARM)"
echo "🖥️ Servers: 141.148.162.114, 129.146.116.218"
echo "📦 Tag: latest"
echo "🔗 Status: ${{ needs.deploy-production.result }}"
else
echo "🌍 Environment: Release (Test Swarm)"
echo "🌍 Environment: Release (Swarm ARM)"
echo "🖥️ Servers: 141.148.162.114, 129.146.116.218"
echo "📦 Branch Tag: ${{ github.ref_name }}"
echo "🔗 Status: ${{ needs.deploy-test.result }}"
fi

View File

@ -1,36 +0,0 @@
# Repository Guidelines
## Project Structure & Module Organization
- `src/BCards.Web` is the main MVC app (Controllers, Services, Repositories, Razor Views, `wwwroot` assets).
- `src/BCards.IntegrationTests` spins up the site with fixtures for API-level checks.
- `tests/BCards.Tests` hosts xUnit + Moq unit coverage with overrides in `appsettings.Testing.json`.
- `tests.e2e` carries Playwright specs and config; utility scripts live under `scripts/`, with `clean-build.sh` mirroring CI cleanup.
## Build, Test & Development Commands
- `dotnet restore && dotnet build BCards.sln` primes dependencies and compiles.
- `dotnet run --project src/BCards.Web` launches the site (HTTPS on 5001 by default).
- `dotnet test` executes unit + integration suites; add `--collect:"XPlat Code Coverage"` to emit coverlet results.
- In `tests.e2e`, run `npm install` once and `npx playwright test` per change; append `--headed` when debugging flows.
- `./clean-build.sh` removes stale `bin/obj` output before CI or release builds.
## Coding Style & Naming Conventions
- Use 4-space indents, file-scoped namespaces, PascalCase for types, camelCase for locals, and `_camelCase` for DI fields.
- Keep Razor views presentation-only; push logic into Services, ViewModels, or TagHelpers.
- Store localization strings in `Resources/`, shared UI in `Views/Shared`, and bundle-ready assets in `wwwroot`.
- Run `dotnet format` before pushing; .NET 8 analyzers treat warnings as errors in the pipeline.
## Testing Guidelines
- Mirror namespaces when creating unit files (`FooServiceTests` for `FooService`) and favour FluentAssertions for expressiveness.
- Integration scenarios reside in `src/BCards.IntegrationTests/Tests`; use shared fixtures to mock MongoDB/Stripe without polluting global state.
- End-to-end cases focus on signup, checkout, and profile rendering; keep snapshots in `tests.e2e/debug_*`.
- Target ≥80% coverage across `Services/` and `Repositories/`; call out gaps explicitly in the PR body.
## Commit & Pull Request Guidelines
- Follow the observed `type: resumo` format (`feat: artigos & tutoriais`, `fix: checkout`); keep scopes short, Portuguese when public-facing.
- Squash WIP branches before review; one functional change per commit.
- Every PR needs a summary, verification list (`dotnet test`, Playwright when touched), related issue link, and UI artifacts when visuals change.
- Tag a module expert for review and flip the `Ready for QA` label only after E2E automation passes.
## Security & Configuration Notes
- Keep secrets out of version control; base new configs on `appsettings.Production.example.json` and document required keys.
- When callback URLs move, update both the environment files (`Dockerfile`, `docker-compose*.yml`) and external provider dashboards together.

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.2.11415.280 d18.0
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCards.Web", "src\BCards.Web\BCards.Web.csproj", "{2E8F4B5C-9B3A-4F8E-8C7D-1A2B3C4D5E6F}"
EndProject
@ -21,7 +21,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject
Conexoes.txt = Conexoes.txt
deploy\docker-stack.release.yml = deploy\docker-stack.release.yml
README.md = README.md
EndProjectSection
EndProject

View File

@ -1,740 +0,0 @@
[{
"_id": {
"$oid": "685b17a70138fec28779d354"
},
"name": "Corretor de Imóveis",
"slug": "corretor",
"icon": "🏠",
"seoKeywords": [
"corretor",
"imóveis",
"casa",
"apartamento",
"venda",
"locação"
],
"description": "Profissionais especializados em compra, venda e locação de imóveis",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:24:55.336Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d355"
},
"name": "Tecnologia",
"slug": "tecnologia",
"icon": "💻",
"seoKeywords": [
"desenvolvimento",
"software",
"programação",
"tecnologia",
"TI"
],
"description": "Empresas e profissionais de tecnologia, desenvolvimento e TI",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.709Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d356"
},
"name": "Saúde",
"slug": "saude",
"icon": "🏥",
"seoKeywords": [
"médico",
"saúde",
"clínica",
"consulta",
"tratamento"
],
"description": "Profissionais da saúde, clínicas e consultórios médicos",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.713Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d357"
},
"name": "Educação",
"slug": "educacao",
"icon": "📚",
"seoKeywords": [
"educação",
"ensino",
"professor",
"curso",
"escola"
],
"description": "Professores, escolas, cursos e instituições de ensino",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.717Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d358"
},
"name": "Comércio",
"slug": "comercio",
"icon": "🛍️",
"seoKeywords": [
"loja",
"comércio",
"venda",
"produtos",
"e-commerce"
],
"description": "Lojas, e-commerce e estabelecimentos comerciais",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.720Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d359"
},
"name": "Serviços",
"slug": "servicos",
"icon": "🔧",
"seoKeywords": [
"serviços",
"prestador",
"profissional",
"especializado"
],
"description": "Prestadores de serviços gerais e especializados",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.723Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d35a"
},
"name": "Alimentação",
"slug": "alimentacao",
"icon": "🍽️",
"seoKeywords": [
"restaurante",
"comida",
"delivery",
"alimentação",
"gastronomia"
],
"description": "Restaurantes, delivery, food trucks e estabelecimentos alimentícios",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.727Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d35b"
},
"name": "Beleza",
"slug": "beleza",
"icon": "💄",
"seoKeywords": [
"beleza",
"salão",
"estética",
"cabeleireiro",
"manicure"
],
"description": "Salões de beleza, barbearias, estética e cuidados pessoais",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.731Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d35c"
},
"name": "Advocacia",
"slug": "advocacia",
"icon": "⚖️",
"seoKeywords": [
"advogado",
"jurídico",
"direito",
"advocacia",
"legal"
],
"description": "Advogados, escritórios jurídicos e consultoria legal",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.734Z"
}
},
{
"_id": {
"$oid": "685b17c20138fec28779d35d"
},
"name": "Arquitetura",
"slug": "arquitetura",
"icon": "🏗️",
"seoKeywords": [
"arquiteto",
"engenheiro",
"construção",
"projeto",
"reforma"
],
"description": "Arquitetos, engenheiros e profissionais da construção",
"isActive": true,
"createdAt": {
"$date": "2025-06-24T21:25:22.737Z"
}
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6933"
},
"name": "Artesanato",
"slug": "artesanato",
"icon": "🎨",
"seoKeywords": [
"artesanato",
"artesão",
"feito à mão",
"personalizado",
"criativo",
"decoração"
],
"description": "Artesãos e criadores de produtos feitos à mão, decoração e arte personalizada",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6934"
},
"name": "Papelaria",
"slug": "papelaria",
"icon": "📝",
"seoKeywords": [
"papelaria",
"escritório",
"material escolar",
"impressão",
"convites",
"personalização"
],
"description": "Lojas de papelaria, material de escritório, impressão e produtos personalizados",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6935"
},
"name": "Coaching",
"slug": "coaching",
"icon": "🎯",
"seoKeywords": [
"coaching",
"mentoria",
"desenvolvimento pessoal",
"life coach",
"business coach",
"liderança"
],
"description": "Coaches, mentores e profissionais de desenvolvimento pessoal e empresarial",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6936"
},
"name": "Fitness",
"slug": "fitness",
"icon": "💪",
"seoKeywords": [
"fitness",
"academia",
"personal trainer",
"musculação",
"treinamento",
"exercícios"
],
"description": "Personal trainers, academias, estúdios de pilates e profissionais fitness",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6937"
},
"name": "Psicologia",
"slug": "psicologia",
"icon": "🧠",
"seoKeywords": [
"psicólogo",
"terapia",
"psicologia",
"saúde mental",
"consultório",
"atendimento"
],
"description": "Psicólogos, terapeutas e profissionais de saúde mental",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6938"
},
"name": "Nutrição",
"slug": "nutricao",
"icon": "🥗",
"seoKeywords": [
"nutricionista",
"dieta",
"nutrição",
"alimentação saudável",
"consultoria nutricional",
"emagrecimento"
],
"description": "Nutricionistas, consultores em alimentação e profissionais da nutrição",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6939"
},
"name": "Moda e Vestuário",
"slug": "moda",
"icon": "👗",
"seoKeywords": [
"moda",
"vestuário",
"roupas",
"fashion",
"estilista",
"costureira"
],
"description": "Lojas de roupas, estilistas, costureiras e profissionais da moda",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb693a"
},
"name": "Fotografia",
"slug": "fotografia",
"icon": "📸",
"seoKeywords": [
"fotógrafo",
"fotografia",
"ensaio",
"casamento",
"eventos",
"retratos"
],
"description": "Fotógrafos profissionais, estúdios fotográficos e serviços de fotografia",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb693b"
},
"name": "Marketing Digital",
"slug": "marketing-digital",
"icon": "📱",
"seoKeywords": [
"marketing digital",
"social media",
"publicidade",
"SEO",
"gestão de redes",
"digital"
],
"description": "Agências de marketing digital, gestores de redes sociais e consultores digitais",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb693c"
},
"name": "Contabilidade",
"slug": "contabilidade",
"icon": "📊",
"seoKeywords": [
"contador",
"contabilidade",
"fiscal",
"imposto de renda",
"MEI",
"consultoria contábil"
],
"description": "Contadores, escritórios contábeis e consultoria fiscal",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb693d"
},
"name": "Design",
"slug": "design",
"icon": "🎨",
"seoKeywords": [
"designer",
"design gráfico",
"identidade visual",
"logo",
"criativo",
"branding"
],
"description": "Designers gráficos, criativos e profissionais de identidade visual",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb693e"
},
"name": "Consultoria",
"slug": "consultoria",
"icon": "🤝",
"seoKeywords": [
"consultor",
"consultoria",
"assessoria",
"especialista",
"negócios",
"estratégia"
],
"description": "Consultores especializados, assessoria empresarial e serviços de consultoria",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb693f"
},
"name": "Pets",
"slug": "pets",
"icon": "🐕",
"seoKeywords": [
"veterinário",
"pet shop",
"animais",
"cuidados",
"petshop",
"adestramento"
],
"description": "Veterinários, pet shops, adestradores e serviços para animais",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6940"
},
"name": "Casa e Jardim",
"slug": "casa-jardim",
"icon": "🏡",
"seoKeywords": [
"paisagismo",
"jardinagem",
"decoração",
"casa",
"jardim",
"plantas"
],
"description": "Paisagistas, jardineiros, decoradores e serviços para casa e jardim",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6941"
},
"name": "Automóveis",
"slug": "automoveis",
"icon": "🚗",
"seoKeywords": [
"mecânico",
"automóveis",
"carros",
"oficina",
"manutenção",
"peças"
],
"description": "Mecânicos, oficinas, lojas de peças e serviços automotivos",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6942"
},
"name": "Turismo",
"slug": "turismo",
"icon": "✈️",
"seoKeywords": [
"turismo",
"viagem",
"agência",
"guia turístico",
"passeios",
"hospedagem"
],
"description": "Agências de turismo, guias, pousadas e prestadores de serviços turísticos",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6943"
},
"name": "Música",
"slug": "musica",
"icon": "🎵",
"seoKeywords": [
"músico",
"professor de música",
"instrumentos",
"aulas",
"banda",
"eventos musicais"
],
"description": "Músicos, professores de música, bandas e profissionais do entretenimento",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6944"
},
"name": "Idiomas",
"slug": "idiomas",
"icon": "🗣️",
"seoKeywords": [
"professor de idiomas",
"inglês",
"espanhol",
"tradutor",
"aulas particulares",
"curso de idiomas"
],
"description": "Professores de idiomas, tradutores e escolas de línguas",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6945"
},
"name": "Limpeza",
"slug": "limpeza",
"icon": "🧽",
"seoKeywords": [
"limpeza",
"faxina",
"diarista",
"higienização",
"empresa de limpeza",
"doméstica"
],
"description": "Empresas de limpeza, diaristas e serviços de higienização",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6946"
},
"name": "Segurança",
"slug": "seguranca",
"icon": "🛡️",
"seoKeywords": [
"segurança",
"vigilante",
"porteiro",
"alarmes",
"monitoramento",
"proteção"
],
"description": "Empresas de segurança, vigilantes e serviços de proteção",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6947"
},
"name": "Eventos",
"slug": "eventos",
"icon": "🎉",
"seoKeywords": [
"eventos",
"festa",
"casamento",
"buffet",
"decoração de festas",
"cerimonial"
],
"description": "Organizadores de eventos, buffets, decoração e cerimonial",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6948"
},
"name": "Transporte",
"slug": "transporte",
"icon": "🚐",
"seoKeywords": [
"transporte",
"frete",
"mudança",
"delivery",
"motorista",
"logística"
],
"description": "Empresas de transporte, fretes, mudanças e serviços de entrega",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6949"
},
"name": "Construção",
"slug": "construcao",
"icon": "🔨",
"seoKeywords": [
"construção",
"pedreiro",
"pintor",
"eletricista",
"encanador",
"reforma"
],
"description": "Profissionais da construção civil, reformas e manutenção predial",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb694a"
},
"name": "Joias e Acessórios",
"slug": "joias",
"icon": "💎",
"seoKeywords": [
"joias",
"bijuterias",
"acessórios",
"ourives",
"relógios",
"semijoias"
],
"description": "Joalherias, bijuterias, ourives e lojas de acessórios",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb694b"
},
"name": "Odontologia",
"slug": "odontologia",
"icon": "🦷",
"seoKeywords": [
"dentista",
"odontologia",
"clínica dentária",
"ortodontia",
"implante",
"oral"
],
"description": "Dentistas, clínicas odontológicas e profissionais da área bucal",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb694c"
},
"name": "Fisioterapia",
"slug": "fisioterapia",
"icon": "🏥",
"seoKeywords": [
"fisioterapeuta",
"fisioterapia",
"reabilitação",
"RPG",
"massagem",
"terapia"
],
"description": "Fisioterapeutas, clínicas de reabilitação e terapias corporais",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb694d"
},
"name": "Livraria",
"slug": "livraria",
"icon": "📚",
"seoKeywords": [
"livraria",
"livros",
"sebo",
"literatura",
"editora",
"publicação"
],
"description": "Livrarias, sebos, editoras e comércio de livros e publicações",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb694e"
},
"name": "Floricultura",
"slug": "floricultura",
"icon": "🌸",
"seoKeywords": [
"floricultura",
"flores",
"buquê",
"plantas",
"arranjos",
"casamento"
],
"description": "Floriculturas, arranjos florais e comércio de plantas ornamentais",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb694f"
},
"name": "Farmácia",
"slug": "farmacia",
"icon": "💊",
"seoKeywords": [
"farmácia",
"farmacêutico",
"medicamentos",
"drogaria",
"manipulação",
"remédios"
],
"description": "Farmácias, drogarias e farmacêuticos especializados",
"isActive": true
},
{
"_id": {
"$oid": "68c1bd62b1e9f4be8ecb6950"
},
"name": "Delivery",
"slug": "delivery",
"icon": "🛵",
"seoKeywords": [
"delivery",
"entrega",
"motoboy",
"comida",
"aplicativo",
"rápido"
],
"description": "Serviços de delivery, entregadores e aplicativos de entrega",
"isActive": true
}]

View File

@ -244,45 +244,3 @@ if (page.Status == PageStatus.Creating || page.Status == PageStatus.Rejected)
```
This architecture supports a production-ready SaaS application with complex business rules, payment integration, and content moderation workflows.
# Project instructions
## ctx — use before reading files
This project has `ctx` available in PATH. Use it to understand the codebase **before** reading files directly. It produces compact markdown summaries that cost far fewer tokens than raw file content.
### When to use
**Always use `ctx` first** when you need to:
- Understand project structure → `ctx csharp project` or `ctx react project`
- Understand a file's structure → `ctx csharp outline <file>` or `ctx react outline <file>`
- Check build errors → `ctx csharp errors` or `ctx react errors`
- Understand git state → `ctx git`
- Detect what stack this project uses → `ctx auto detect`
### Workflow
1. **Start of session:** run `ctx auto detect` to see what's here, then `ctx <stack> project` for an overview.
2. **Before reading a file:** run `ctx <stack> outline <file>` first. Only read the full file if the outline isn't enough (e.g., you need to see method body logic).
3. **After making changes:** run `ctx <stack> errors` instead of `dotnet build` or `tsc` — the output is pre-filtered to only relevant diagnostics.
4. **Before committing:** run `ctx git` for a compact diff summary.
### Available commands
```
ctx auto detect # detect stack(s) in current directory
ctx auto project # run project summary for all detected stacks
ctx csharp project # .NET solution overview (projects, refs, packages)
ctx csharp outline <file.cs> # file structure without method bodies
ctx csharp errors # filtered dotnet build output (errors + top warnings)
ctx git # branch, status, recent commits, diff summary
```
### Important
- `ctx` output is a **summary**, not the full picture. If you need implementation details (method bodies, exact logic, specific lines), read the file directly.
- Do not run `ctx` commands that don't match the project stack. Use `ctx auto detect` if unsure.
- `ctx csharp errors` assumes `dotnet restore` was already run. If you get restore errors, run `dotnet restore` first, then `ctx csharp errors`.

View File

@ -1,394 +0,0 @@
---
title: "BCards vs LinkTree: A Alternativa Brasileira para Profissionais"
description: "Descubra por que o BCards é a melhor alternativa brasileira ao LinkTree para profissionais e empresas. Comparação completa de recursos, preços e diferenciais."
keywords: "linktree, link tree, alternativa ao linktree, melhor que linktree, bcards, cartão digital, bio links, página de links, linktree brasil, linktree brasileiro"
author: "BCards"
date: 2025-11-02
lastmod: 2025-11-02
image: "/images/artigos/bcards-vs-linktree-hero.jpg"
---
# BCards vs LinkTree: A Alternativa Brasileira para Profissionais
Se você está procurando uma alternativa ao LinkTree, você chegou ao lugar certo. O BCards é uma solução brasileira desenvolvida especificamente para profissionais e empresas que querem uma presença digital profissional sem depender de plataformas internacionais.
## O Que São Páginas de Bio Links?
Antes de compararmos, vamos entender o conceito. Ferramentas como LinkTree e BCards são páginas de "bio links" - uma única URL que centraliza todos os seus links importantes:
- Redes sociais (Instagram, LinkedIn, Facebook)
- WhatsApp para contato direto
- Site ou portfólio
- Loja online ou produtos
- Localização física
- E muito mais
A ideia é simples: ao invés de ter apenas um link na bio do Instagram, você tem uma página com todos os seus links organizados.
## Por Que Escolher uma Alternativa Brasileira ao LinkTree?
### 🇧🇷 1. Suporte em Português (de Verdade)
**LinkTree:**
- Suporte majoritariamente em inglês
- Fuso horário dos EUA
- Documentação traduzida automaticamente
**BCards:**
- Suporte 100% em português
- Atendimento no horário comercial brasileiro
- Documentação escrita para o mercado brasileiro
- Entendimento de necessidades locais
### 💰 2. Preços em Real (Sem Surpresas)
**LinkTree:**
- Preços em dólar (USD)
- Sujeito a variação cambial
- IOF em pagamentos internacionais (6.38%)
- Preço final imprevisível
**BCards:**
- Preços fixos em Real (R$)
- Sem variação cambial
- Sem taxas de IOF
- Pagamento via Pix, cartão brasileiro
**Exemplo Real:**
- LinkTree Pro: $9 USD/mês ≈ R$ 45-50 (variável)
- BCards Básico: R$ 12,90/mês (fixo)
### 🎯 3. URLs Profissionais e Semânticas
**LinkTree:**
```
linktr.ee/seunome
```
- Domínio genérico
- Não transmite profissionalismo
- Óbvio que é uma ferramenta de terceiros
**BCards:**
```
bcards.site/page/tecnologia/seunome
bcards.site/page/advocacia/drjoao
bcards.site/page/saude/drmaria
```
- URLs categorizadas por profissão
- SEO otimizado com categoria
- Aparência mais profissional
## Comparação Técnica: LinkTree vs BCards
### Recursos Disponíveis
| Recurso | LinkTree Free | LinkTree Pro ($9/mês) | BCards Grátis | BCards Básico (R$ 12,90/mês) |
|---------|---------------|----------------------|---------------|----------------------------|
| **Número de Links** | Ilimitado | Ilimitado | 5 | 15 |
| **Temas** | Básicos | Avançados | Básicos | Todos |
| **Analytics** | Básico | Detalhado | Básico | Detalhado |
| **Remover Logo** | ❌ | ✅ | ✅ (Grátis!) | ✅ |
| **URLs Customizáveis** | Limitado | ✅ | ✅ | ✅ |
| **Suporte em Português** | ❌ | ❌ | ✅ | ✅ |
| **Pagamento em Real** | ❌ | ❌ | ✅ | ✅ |
| **Categorização por Profissão** | ❌ | ❌ | ✅ | ✅ |
| **Moderação Humana** | ❌ | ❌ | ✅ | ✅ |
### Diferenciais Técnicos do BCards
#### 1. Sistema de Categorias Profissionais
O BCards organiza perfis por categoria profissional, o que oferece vantagens:
**SEO:**
- Google indexa melhor URLs categorizadas
- Buscar "advogado + cidade" pode trazer seu BCard
- Autoridade topical (especialização por nicho)
**Profissionalismo:**
- URL mostra sua área de atuação
- Facilita encontrar profissionais similares
- Networking dentro da categoria
**Exemplos:**
```
bcards.site/page/advocacia/dr-pedro-silva
bcards.site/page/tecnologia/joao-dev
bcards.site/page/saude/dra-ana-cardiologia
bcards.site/page/beleza/salao-mariana
```
#### 2. Moderação Humana (Sem Conteúdo Inapropriado)
**LinkTree:**
- Moderação automática (quando há)
- Permite conteúdo adulto em alguns planos
- Risco de associação com perfis inadequados
**BCards:**
- Moderação humana de todos os perfis
- Políticas claras de conteúdo profissional
- Ambiente seguro para profissionais sérios
- Rejeição de conteúdo inapropriado
**Por que isso importa?**
- Sua marca não fica ao lado de conteúdo duvidoso
- Transmite profissionalismo
- Ideal para advogados, médicos, contadores, etc.
#### 3. Integração com Mercado Brasileiro
**LinkTree:**
- Links para Venmo, CashApp (EUA)
- Integração com plataformas americanas
- Checkout Shopify (internacional)
**BCards:**
- Foco em WhatsApp Business (essencial no Brasil)
- Integração futura com Pix
- Mercado Livre, Mercado Pago, PagSeguro
- Google Maps para localização (crucial para negócios locais)
## Casos de Uso: Quando BCards é Melhor?
### ✅ Ideal para BCards:
1. **Advogados e Profissionais Liberais**
- URL profissional com categoria
- Moderação garante ambiente sério
- Preço acessível em Real
2. **Pequenos Negócios Locais**
- Integração com Google Maps
- WhatsApp Business como principal contato
- Preços em moeda local
3. **Profissionais de Saúde**
- Ambiente moderado e profissional
- Links para agendamento local
- Conformidade com políticas do CFM
4. **Prestadores de Serviços**
- Categorização por área
- Fácil de compartilhar localmente
- Suporte em português
### 🤔 Quando Considerar LinkTree:
1. **Influenciadores Internacionais**
- Audiência global
- Receita em dólar
- Integrações com plataformas americanas
2. **E-commerce Internacional**
- Vendas para fora do Brasil
- Shopify, Amazon global
- Múltiplas moedas
## Recursos Exclusivos do BCards
### 1. Links de Produtos com Preview Automático
Adicione links de produtos e o BCards captura automaticamente:
- Imagem do produto
- Preço
- Descrição
- Botão direto para compra
**Suportado:**
- Mercado Livre
- Shopee
- Amazon Brasil
- Lojas com Open Graph
### 2. Sistema de Preview para Moderação
Antes de seu perfil ficar público:
- Revisão humana do conteúdo
- Feedback se algo precisa ser ajustado
- Link de preview para você testar
- Aprovação rápida (geralmente em 24h)
### 3. Analytics Focado no Mercado Brasileiro
**Métricas relevantes:**
- Cliques por link
- Horários de maior acesso (fuso brasileiro)
- Origem do tráfego (Instagram, WhatsApp, etc.)
- Dispositivos (maioria mobile no Brasil)
## Preços Comparados (Detalhado)
### LinkTree
**Free:**
- Links ilimitados
- Temas básicos
- Analytics limitado
- Logo LinkTree presente
**Pro ($9 USD/mês ≈ R$ 45-50):**
- Temas premium
- Analytics avançado
- Remove logo
- Links prioritários
- **Problema:** Preço varia com dólar + IOF
**Premium ($24 USD/mês ≈ R$ 120-135):**
- Tudo do Pro
- Integrações avançadas
- Suporte prioritário
### BCards
**Gratuito:**
- 5 links
- Temas básicos
- Analytics básico
- **Sem logo BCards** (diferencial!)
**Básico (R$ 12,90/mês fixo):**
- 15 links
- Todos os temas
- Analytics detalhado
- Suporte em português
**Premium (R$ 29,90/mês fixo):**
- Links ilimitados
- Temas personalizados
- Logo personalizado
- Cores customizadas
- Suporte prioritário
- Upload de PDFs (até 5 arquivos)
**Economia Real:**
- LinkTree Pro anual: ~$108 + IOF = ~R$ 540-600
- BCards Básico anual: R$ 118,80
- **Economia: ~R$ 420/ano**
## Migração do LinkTree para BCards
### É Fácil Migrar?
**Sim! Processo simples:**
1. **Crie sua conta BCards** (2 minutos)
2. **Copie seus links do LinkTree** (5 minutos)
3. **Cole no BCards** (5 minutos)
4. **Ajuste tema e cores** (5 minutos)
5. **Atualize bio das redes sociais** (2 minutos)
**Total: ~20 minutos**
### Posso Testar Antes de Migrar?
Sim! O plano gratuito do BCards permite:
- Criar perfil completo
- Testar 5 links principais
- Ver como funciona
- Decidir se vale a pena
**Sem risco:**
- Não precisa cancelar LinkTree antes
- Teste paralelamente
- Migre quando se sentir confortável
## Perguntas Frequentes: BCards vs LinkTree
### O BCards funciona com Instagram, TikTok, YouTube?
Sim! Funciona com qualquer rede social que permite um link na bio:
- Instagram ✅
- TikTok ✅
- YouTube ✅
- LinkedIn ✅
- Facebook ✅
- Twitter/X ✅
### Posso usar domínio próprio?
**LinkTree:** Apenas em planos premium ($24/mês)
**BCards:** Em desenvolvimento (plano Premium futuro)
Atualmente: `bcards.site/page/categoria/seunome`
### E se eu já tenho linktr.ee nas minhas mídias impressas?
Você pode:
1. Manter LinkTree redirecionando para BCards temporariamente
2. Atualizar gradualmente materiais impressos
3. Usar QR Code do BCards (gera automaticamente)
### O BCards tem app mobile?
Não é necessário! É 100% web:
- Funciona perfeitamente no mobile
- Não ocupa espaço no celular
- Atualiza instantaneamente
- Sem necessidade de instalar nada
### Posso cancelar a qualquer momento?
Sim! Sem fidelidade:
- Cancele quando quiser
- Sem multa
- Sem burocracia
- Dados não são deletados (voltam ao plano grátis)
## Conclusão: Vale a Pena Trocar?
### ✅ Vale a pena trocar do LinkTree para BCards se:
- Você é profissional ou empresa brasileira
- Quer economia real (3-5x mais barato)
- Prefere suporte em português
- Busca URL mais profissional
- Quer ambiente moderado e sério
- Precisa de pagamento em Real sem surpresas
### 🤔 Talvez LinkTree seja melhor se:
- Sua audiência é majoritariamente internacional
- Você vende em dólar
- Precisa integrações específicas americanas
- Já tem grande investimento na marca "linktr.ee"
## Comece Agora
### Teste Gratuitamente
Não precisa acreditar na nossa palavra. Teste você mesmo:
1. **Crie conta grátis** (sem cartão de crédito)
2. **Configure seus 5 links principais**
3. **Escolha um tema profissional**
4. **Compartilhe e veja os resultados**
Se gostar, faça upgrade para ter mais links. Se não gostar, não paga nada.
**Pronto para dar o próximo passo?** [Criar meu BCard grátis](https://bcards.site)
---
## Comparação Rápida (TL;DR)
| Aspecto | LinkTree | BCards |
|---------|----------|--------|
| **Preço (plano básico)** | ~R$ 45-50/mês (varia) | R$ 12,90/mês (fixo) |
| **Suporte** | Inglês | Português |
| **Pagamento** | Dólar + IOF | Real (Pix/Cartão BR) |
| **URL** | linktr.ee/nome | bcards.site/categoria/nome |
| **Moderação** | Automática | Humana |
| **Foco** | Global | Brasil |
| **Remover logo (grátis)** | ❌ | ✅ |
**Economia anual:** ~R$ 420 escolhendo BCards
---
## Recursos Adicionais
- [Tutorial: Como Criar um BCard](/tutoriais/tecnologia/como-criar-um-bcard)
- [BCards para Advogados](/tutoriais/advocacia/como-advogados-podem-usar-bcards)
- [Transformação Digital](/artigos/transformacao-digital-pequenos-negocios)
- [Fale com nosso suporte](/support)

View File

@ -1,208 +0,0 @@
---
title: "Transformação Digital para Pequenos Negócios: Por Onde Começar?"
description: "Descubra como a transformação digital pode revolucionar seu pequeno negócio e quais são os primeiros passos para entrar no mundo digital"
keywords: "transformação digital, pequenos negócios, digitalização, empreendedorismo, tecnologia"
author: "BCards"
date: 2025-11-02
lastmod: 2025-11-02
image: "/images/artigos/transformacao-digital-hero.jpg"
---
# Transformação Digital para Pequenos Negócios: Por Onde Começar?
A transformação digital não é mais uma opção, mas uma necessidade para pequenos negócios que querem se manter competitivos. Mas o que exatamente significa "transformação digital" e como um pequeno negócio pode embarcar nessa jornada sem gastar fortunas?
## O Que É Transformação Digital?
Transformação digital é o processo de usar tecnologias digitais para criar novos processos de negócio, cultura e experiências do cliente — ou modificar os existentes — para atender às mudanças nas demandas do mercado.
Para pequenos negócios, isso pode ser tão simples quanto:
- Ter uma presença online profissional
- Aceitar pagamentos digitais
- Usar redes sociais para marketing
- Digitalizar processos internos
- Oferecer atendimento online
## Por Que Pequenos Negócios Precisam Se Digitalizar?
### 1. Alcance Maior
Com presença digital, você não está mais limitado à sua localização física. Clientes de qualquer lugar podem encontrar e conhecer seu negócio.
### 2. Custos Menores
Marketing digital é significativamente mais barato que publicidade tradicional. Uma campanha no Instagram pode custar centenas de vezes menos que um anúncio em jornal.
### 3. Competitividade
Seus concorrentes já estão online. Não estar presente digitalmente significa perder clientes para quem está.
### 4. Conveniência para o Cliente
Clientes modernos esperam poder:
- Encontrar informações online
- Fazer pedidos pelo WhatsApp
- Pagar com Pix ou cartão
- Ver avaliações de outros clientes
## Os 5 Primeiros Passos da Transformação Digital
### 1. Crie uma Presença Digital Básica
Você não precisa de um site complexo. Comece com:
**Mínimo Necessário:**
- Uma página de links profissional (como o BCards)
- Perfil no Instagram ou Facebook
- WhatsApp Business configurado
**Por que isso funciona:**
- É gratuito ou muito barato
- Pode ser feito em 1 dia
- Já coloca você no mapa digital
### 2. Organize Seus Contatos Digitais
Centralize todos os seus pontos de contato:
- WhatsApp para pedidos
- Instagram para divulgação
- Email para orçamentos
- Link para catálogo de produtos
**Solução simples:** Use um cartão digital (BCard) que concentra todos esses links em um só lugar. Assim, você compartilha um único link e o cliente escolhe como prefere contatar você.
### 3. Aceite Pagamentos Digitais
Configure:
- Pix (essencial!)
- Maquininha de cartão
- Link de pagamento (Mercado Pago, PagSeguro)
**Resultado:** Você não perde vendas porque "o cliente não tem dinheiro trocado".
### 4. Use as Redes Sociais Estrategicamente
Não precisa estar em todas. Escolha UMA ou DUAS e faça bem:
**Para produtos físicos:** Instagram + Facebook
**Para serviços profissionais:** LinkedIn + Instagram
**Para comércio local:** Facebook + Google Meu Negócio
**Dica de ouro:** Poste regularmente (mesmo que seja 2-3x por semana). Consistência > Quantidade.
### 5. Peça Avaliações e Depoimentos
Clientes satisfeitos são seu melhor marketing. Peça:
- Avaliações no Google
- Comentários no Instagram
- Depoimentos em vídeo (WhatsApp)
**Como pedir:** "Se você ficou satisfeito com nosso serviço, pode deixar uma avaliação no Google? Isso nos ajuda muito!"
## Ferramentas Essenciais (e Baratas!)
### Gratuitas
- **Google Meu Negócio** - Apareça no Google Maps
- **WhatsApp Business** - Atendimento profissional
- **Canva** - Design de posts e materiais
- **BCards** - Página de links profissional (plano grátis disponível)
### Investimento Baixo (menos de R$ 50/mês)
- **Instagram Ads** - Impulsione suas publicações
- **Hotmart/Eduzz** - Venda produtos digitais
- **Mercado Pago** - Links de pagamento
- **BCards Premium** - Cartão digital profissional completo
## Erros Comuns a Evitar
### ❌ Erro 1: Querer Fazer Tudo de Uma Vez
Não tente criar site, app, loja virtual e estar em todas as redes sociais simultaneamente. Comece pequeno e expanda gradualmente.
### ❌ Erro 2: Comprar Ferramentas Caras Demais
Você não precisa de um site de R$ 10.000 quando está começando. Use ferramentas simples primeiro e evolua conforme cresce.
### ❌ Erro 3: Não Ter um Ponto Central
Ter Instagram, Facebook, WhatsApp é ótimo. Mas onde você manda as pessoas? Tenha UM lugar que centraliza tudo (um BCard, por exemplo).
### ❌ Erro 4: Não Medir Resultados
Use as ferramentas gratuitas de analytics:
- Instagram Insights
- Google Analytics
- Relatórios do WhatsApp Business
## Casos de Sucesso Inspiradores
### Padaria da Dona Maria
**Antes:** Apenas loja física, dependia de clientes que passavam na frente
**Depois:**
- BCard com cardápio e WhatsApp
- QR Code nas embalagens
- Pedidos pelo WhatsApp aumentaram 60%
- Clientes de bairros vizinhos começaram a pedir
### Consultório do Dr. Pedro (Dentista)
**Antes:** Agendamentos apenas por telefone em horário comercial
**Depois:**
- Sistema de agendamento online
- BCard com links para WhatsApp, Google Maps e Instagram
- Reduziu trabalho da secretária em 40%
- Mais agendamentos fora do horário comercial
### Loja de Roupas da Ana
**Antes:** Vendas apenas presenciais
**Depois:**
- Catálogo no Instagram
- BCard com link para catálogo completo
- WhatsApp Business para pedidos
- Vendas online representam 35% do faturamento
## Próximos Passos Para Seu Negócio
### Semana 1: Organização
- Liste todos os seus pontos de contato atuais
- Crie um BCard com todos os links
- Configure WhatsApp Business
### Semana 2: Presença Online
- Atualize perfis de redes sociais
- Adicione o BCard na bio
- Tire fotos profissionais dos produtos/serviços
### Semana 3: Divulgação
- Crie QR Code do seu BCard
- Coloque em materiais impressos
- Peça avaliações de clientes fiéis
### Semana 4: Otimização
- Analise o que funcionou
- Ajuste e melhore
- Planeje próximos passos
## Conclusão
A transformação digital para pequenos negócios não precisa ser cara ou complicada. Comece com o básico:
1. Presença online simples (BCard + redes sociais)
2. Facilite o contato (WhatsApp Business)
3. Aceite pagamentos digitais (Pix + cartão)
4. Peça avaliações de clientes
5. Evolua gradualmente
O importante é começar. Cada pequeno passo digital que você dá coloca seu negócio à frente dos concorrentes que ainda não se moveram.
**Pronto para dar o primeiro passo?** [Crie seu BCard profissional grátis](https://bcards.site) e comece sua transformação digital hoje!
---
## Recursos Adicionais
- [Tutorial: Como Criar um BCard](/tutoriais/tecnologia/como-criar-um-bcard)
- [BCards para Advogados](/tutoriais/advocacia/como-advogados-podem-usar-bcards)
- [Fale com nosso suporte](/support)

View File

@ -1,197 +0,0 @@
---
title: "BCards para Advogados: Guia Completo"
description: "Descubra como advogados podem usar o BCards para fortalecer sua presença digital, atrair mais clientes e organizar todos os seus contatos profissionais"
keywords: "advogado, advocacia, cartão digital, marketing jurídico, presença online, bcards"
author: "BCards"
date: 2025-11-02
lastmod: 2025-11-02
image: "/images/tutoriais/advocacia/advogados-bcards-hero.jpg"
category: "advocacia"
---
# BCards para Advogados: Guia Completo
Na era digital, ter uma presença online profissional é essencial para advogados que querem se destacar e atrair mais clientes. O BCards é a solução perfeita para centralizar todos os seus contatos e informações profissionais em um único lugar.
## Por que Advogados Precisam de um BCard?
### 1. Credibilidade Profissional
Um cartão digital transmite modernidade e profissionalismo. Seus clientes verão que você está atualizado com as tecnologias atuais.
### 2. Facilidade de Contato
Centralize todas as formas de contato em um único link:
- WhatsApp para consultas rápidas
- Email profissional
- Telefone do escritório
- Endereço com localização no mapa
- Redes sociais profissionais (LinkedIn)
### 3. Marketing Jurídico Eficiente
Divulgue seus serviços de forma ética e profissional:
- Link para artigos jurídicos que você escreve
- Vídeos educativos no YouTube
- Depoimentos de clientes satisfeitos
- Áreas de atuação
## Como Configurar seu BCard Profissional
### Passo 1: Informações Essenciais
Inclua em seu perfil:
**Informações Básicas:**
- Nome completo e OAB
- Foto profissional (de terno/blazer)
- Bio concisa e profissional
- Áreas de especialização
**Exemplo de Bio:**
> "Dr. João Silva - OAB/SP 123.456
> Advogado especialista em Direito do Consumidor
> Atuação em todo território nacional
> Mais de 15 anos de experiência"
### Passo 2: Links Estratégicos
Organize seus links por ordem de importância:
1. **WhatsApp Business** - Para consultas rápidas
2. **Agendar Consulta** - Link para sistema de agendamento
3. **Áreas de Atuação** - Página explicando seus serviços
4. **Blog Jurídico** - Artigos e conteúdo educativo
5. **LinkedIn** - Networking profissional
6. **Localização** - Google Maps do seu escritório
### Passo 3: Conteúdo Educativo
Advogados podem se destacar compartilhando conhecimento:
- Artigos sobre direitos do consumidor
- Vídeos explicando processos jurídicos
- FAQ com perguntas frequentes
- Guias práticos para clientes
## Estratégias de Marketing para Advogados
### 1. Use no Cartão de Visitas
Adicione um QR Code do seu BCard no cartão de visitas físico. Assim, as pessoas podem salvar todos os seus contatos instantaneamente.
### 2. Assinatura de Email
Inclua o link do seu BCard na assinatura de todos os emails profissionais.
**Exemplo:**
```
Dr. João Silva
Advogado - OAB/SP 123.456
📱 Todos os meus contatos: bcards.site/page/advocacia/joao-silva
```
### 3. Redes Sociais
- Instagram: coloque na bio
- LinkedIn: adicione na seção "Sobre"
- Facebook: fixe uma publicação com o link
### 4. Materiais Impressos
Adicione QR Codes em:
- Folders informativos
- Cartazes do escritório
- Apresentações em palestras
- Documentos entregues a clientes
## Links Recomendados para Advogados
### Essenciais
- ✅ WhatsApp Business (atendimento)
- ✅ Email profissional
- ✅ Telefone do escritório
- ✅ Localização (Google Maps)
- ✅ LinkedIn
### Opcionais mas Recomendados
- 📝 Blog jurídico
- 📺 Canal no YouTube (vídeos educativos)
- 📅 Sistema de agendamento online
- 📄 Portfólio de casos (quando permitido)
- 💬 Depoimentos de clientes
- 📖 E-books e materiais gratuitos
## Ética e Boas Práticas
### O que Fazer ✅
- Mantenha uma apresentação sóbria e profissional
- Use foto com traje formal
- Divulgue conteúdo educativo
- Informe número da OAB sempre
- Seja transparente sobre áreas de atuação
### O que Evitar ❌
- Promessas de resultado garantido
- Comparações com outros advogados
- Preços divulgados publicamente (consulte a OAB)
- Linguagem sensacionalista
- Imagens inadequadas
## Casos de Uso Reais
### Caso 1: Dra. Maria - Direito de Família
A Dra. Maria aumentou suas consultas em 40% após criar seu BCard. Ela incluiu:
- Link para agendar consulta online
- Artigos sobre divórcio e pensão alimentícia
- Depoimentos de clientes (com autorização)
- WhatsApp exclusivo para novos casos
### Caso 2: Dr. Pedro - Direito Trabalhista
O Dr. Pedro usa seu BCard para:
- Compartilhar guias sobre direitos trabalhistas
- Vídeos curtos explicando rescisão, FGTS, etc.
- Formulário para pré-avaliação de casos
- Links para suas redes sociais educativas
## Planos Recomendados
### Para Advogados Iniciantes
**Plano Básico (R$ 12,90/mês)**
- Até 15 links
- Temas profissionais
- Analytics para acompanhar acessos
### Para Escritórios Estabelecidos
**Plano Premium (R$ 29,90/mês)**
- Links ilimitados
- Logo do escritório
- Cores personalizadas
- Suporte prioritário
- Upload de PDFs para contratos, petições e materiais exclusivos
## Conclusão
O BCards é uma ferramenta essencial para advogados modernos que querem:
- Facilitar o contato com clientes
- Apresentar-se de forma profissional
- Centralizar todas as informações em um só lugar
- Aumentar a captação de clientes
A melhor parte? É rápido de configurar e totalmente compatível com as regras da OAB para marketing jurídico.
**Pronto para modernizar sua presença digital?** [Crie seu BCard profissional agora!](https://bcards.site)
---
## Recursos Adicionais
- [Tutorial: Como Criar um BCard](/tutoriais/tecnologia/como-criar-um-bcard)
- [Suporte BCards](/support)
- [Políticas de Privacidade](/privacidade)

View File

@ -1,148 +0,0 @@
---
title: "Como Criar um BCard Profissional em 5 Minutos"
description: "Aprenda passo a passo como criar seu cartão digital profissional com BCards e centralize todos os seus links em um único lugar"
keywords: "bcards, cartão digital, tutorial, tecnologia, links, página profissional"
author: "BCards"
date: 2025-11-02
lastmod: 2025-11-02
image: "/images/tutoriais/tecnologia/criar-bcard-hero.jpg"
category: "tecnologia"
---
# Como Criar um BCard Profissional em 5 Minutos
Criar um cartão digital profissional nunca foi tão fácil! Neste tutorial, vamos mostrar como você pode ter sua página de links profissional em apenas 5 minutos.
## O que é um BCard?
O BCard é sua página profissional na internet, onde você centraliza todos os seus links importantes:
- Redes sociais (Instagram, LinkedIn, Facebook)
- WhatsApp para contato
- Site ou portfólio
- Links de produtos ou serviços
- E muito mais!
## Passo 1: Faça seu Cadastro
1. Acesse [bcards.site](https://bcards.site)
2. Clique em "Criar meu BCard grátis"
3. Escolha fazer login com Google ou Microsoft
4. Pronto! Sua conta está criada
> **Dica:** Use o mesmo email que você usa profissionalmente para facilitar o gerenciamento
## Passo 2: Escolha seu Tema
Temos diversos temas profissionais disponíveis:
- **Minimalista**: Clean e elegante
- **Moderno**: Cores vibrantes e design atual
- **Profissional**: Sóbrio e corporativo
- **Criativo**: Para quem quer se destacar
Escolha o tema que mais combina com sua personalidade ou marca!
## Passo 3: Adicione seus Links
Agora é hora de adicionar os links mais importantes:
1. Clique em "Adicionar Link"
2. Escolha o tipo (Instagram, WhatsApp, Site, etc.)
3. Cole o link
4. Dê um título descritivo
5. Salve!
### Tipos de Links Suportados
- Redes Sociais (Instagram, Facebook, LinkedIn, TikTok)
- Contato (WhatsApp, Email, Telefone)
- Sites e Portfólios
- Vídeos (YouTube, Vimeo)
- Documentos e PDFs
- Links personalizados
## Passo 4: Personalize seu Perfil
Deixe seu BCard com a sua cara:
- Adicione uma foto de perfil profissional
- Escreva uma bio atrativa
- Configure cores personalizadas (plano Premium)
- Adicione seu logo (plano Premium)
## Passo 5: Compartilhe!
Agora que está pronto, é hora de compartilhar:
- Seu link será: `bcards.site/page/{categoria}/{seu-nome}`
- Adicione na bio do Instagram
- Coloque na assinatura de email
- Compartilhe em grupos de WhatsApp
- Adicione ao cartão de visitas físico com QR Code
## Dicas Profissionais
### 1. Mantenha Atualizado
Revise seus links regularmente e remova os que não são mais relevantes.
### 2. Use Descrições Claras
Ao invés de "Clique aqui", use "Veja meu portfólio completo" ou "Agende uma consulta".
### 3. Priorize os Links Mais Importantes
Coloque os links mais relevantes no topo. As pessoas geralmente clicam nos primeiros links.
### 4. Teste Regularmente
Clique em todos os seus links periodicamente para garantir que estão funcionando.
## Planos e Recursos
### Plano Gratuito
- Até 5 links
- Temas básicos
- Analytics básico
### Plano Básico (R$ 12,90/mês)
- Até 15 links
- Todos os temas
- Analytics detalhado
### Plano Premium (R$ 29,90/mês)
- Links ilimitados
- Temas personalizados
- Logo personalizado
- Suporte prioritário
- Upload de PDFs para materiais extras
## Conclusão
Criar seu BCard profissional é rápido, fácil e pode transformar a forma como você se apresenta online. Em apenas 5 minutos, você tem uma página profissional que centraliza todos os seus contatos e links importantes.
**Pronto para começar?** [Crie seu BCard grátis agora!](https://bcards.site)
---
## Perguntas Frequentes
### Posso mudar meu tema depois?
Sim! Você pode mudar o tema quantas vezes quiser, a qualquer momento.
### Como faço para adicionar um QR Code?
No dashboard, clique em "Baixar QR Code" para fazer download do código QR do seu BCard.
### Posso ter mais de um BCard?
No momento, cada conta pode ter apenas um BCard ativo.
### Precisa de mais ajuda?
Entre em contato com nosso [suporte](https://bcards.site/support) ou confira outros tutoriais!

View File

@ -5,17 +5,15 @@ EXPOSE 8080
EXPOSE 8443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/BCards.Web/BCards.Web.csproj", "src/BCards.Web/"]
RUN dotnet restore "src/BCards.Web/BCards.Web.csproj"
COPY . .
WORKDIR "/src/src/BCards.Web"
RUN dotnet build "BCards.Web.csproj" -c ${BUILD_CONFIGURATION} -o /app/build
RUN dotnet build "BCards.Web.csproj" -c Release -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "BCards.Web.csproj" -c ${BUILD_CONFIGURATION} -o /app/publish /p:UseAppHost=false
RUN dotnet publish "BCards.Web.csproj" -c Release -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app

View File

@ -16,9 +16,9 @@ Um clone profissional do LinkTree desenvolvido em ASP.NET Core MVC, focado no me
- **Renderização SSR**: SEO-friendly
### 🎯 Planos e Pricing (Estratégia Decoy)
- **Básico** (R$ 12,90/mês): 5 links, temas básicos, analytics simples
- **Profissional** (R$ 25,90/mês): 15 links, todos os temas, analytics avançado, domínio personalizado *(DECOY)*
- **Premium** (R$ 29,90/mês): Links ilimitados, temas customizáveis, analytics completo, múltiplos domínios, upload de PDFs
- **Básico** (R$ 9,90/mês): 5 links, temas básicos, analytics simples
- **Profissional** (R$ 24,90/mês): 15 links, todos os temas, analytics avançado, domínio personalizado *(DECOY)*
- **Premium** (R$ 29,90/mês): Links ilimitados, temas customizáveis, analytics completo, múltiplos domínios
## 🛠️ Tecnologias
@ -94,8 +94,8 @@ Edite `appsettings.json` ou `appsettings.Development.json`:
1. Crie uma conta no [Stripe](https://stripe.com)
2. Configure os produtos e preços:
- Básico: R$ 12,90/mês
- Profissional: R$ 25,90/mês
- Básico: R$ 9,90/mês
- Profissional: R$ 24,90/mês
- Premium: R$ 29,90/mês
3. Configure webhooks para: `/webhook/stripe`
4. Eventos necessários:

File diff suppressed because it is too large Load Diff

View File

@ -1,59 +0,0 @@
# Plano de Novos Artigos
Data de criação: 2026-04-30
## LuzLinks — 4 artigos
- [x] **Como criar sua bio de fé do zero**
`Content/Tenants/luzlinks/Artigos/como-criar-sua-bio-de-fe-do-zero.pt-BR.md`
- [x] **Como divulgar cultos e eventos pelo WhatsApp com um único link**
`Content/Tenants/luzlinks/Artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp.pt-BR.md`
- [x] **Por que pastores precisam de presença digital**
`Content/Tenants/luzlinks/Artigos/por-que-pastores-precisam-de-presenca-digital.pt-BR.md`
- [x] **Como receber dízimos online no seu ministério**
`Content/Tenants/luzlinks/Artigos/como-receber-dizimos-online-no-seu-ministerio.pt-BR.md`
## SpicyLinks — 4 artigos
- [x] **Como configurar sua bio para ganhar mais seguidores**
`Content/Tenants/spicylinks/Artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores.pt-BR.md`
- [x] **Os melhores links para colocar na sua bio do Instagram**
`Content/Tenants/spicylinks/Artigos/os-melhores-links-para-sua-bio-do-instagram.pt-BR.md`
- [x] **Como criadores de conteúdo ganham dinheiro com links de afiliados**
`Content/Tenants/spicylinks/Artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados.pt-BR.md`
- [x] **SpicyLinks vs Linktree: qual é melhor para criadores?**
`Content/Tenants/spicylinks/Artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores.pt-BR.md`
---
## Melhorias de qualidade (SEO / estrutura)
### LuzLinks — aplicar em todos
- [ ] bio de fé do zero — "Para quem é" + prós/contras + Leia também
- [ ] WhatsApp + cultos — "Para quem é" + prós/contras + Leia também
- [ ] Pastores e digital — "Para quem é" + prós/contras + Leia também
- [ ] Dízimos online — "Para quem é" + prós/contras + Leia também
### SpicyLinks — aplicar em todos
- [ ] Bio e seguidores — "Para quem é" + prós/contras + Leia também
- [ ] Melhores links bio — "Para quem é" + prós/contras + Leia também
- [ ] Afiliados criadores — "Para quem é" + prós/contras + Leia também
- [ ] SpicyLinks vs Linktree — "Para quem é" + prós/contras + Leia também
### BCards — analisar e ajustar artigos existentes
- [ ] bcards-para-corretores-de-imoveis
- [ ] bcards-vs-linktree
- [ ] transformacao-digital-pequenos-negocios
- [ ] Tutoriais/advocacia/bcards-para-escritorios-de-advocacia
- [ ] Tutoriais/advocacia/como-advogados-podem-usar-bcards
- [ ] Tutoriais/tecnologia/bcards-para-freelancers-de-ti
- [ ] Tutoriais/tecnologia/como-criar-um-bcard

View File

@ -1,57 +0,0 @@
version: '3.8'
configs:
bcards-appsettings:
external: true
services:
bcards-app:
image: registry.redecarneir.us/bcards:latest
networks:
- bcards-net
deploy:
replicas: 4
placement:
max_replicas_per_node: 2
update_config:
parallelism: 1
order: stop-first
delay: 10s
monitor: 60s
failure_action: rollback
rollback_config:
parallelism: 0
delay: 5s
configs:
- source: bcards-appsettings
target: /app/appsettings.Production.json
mode: 0444
volumes:
- type: bind
source: /opt/bcards-content/bcards
target: /app/Content/Tenants/bcards
read_only: true
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
MongoDb__ConnectionString: mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/BCardsDB?replicaSet=rs0&authSource=admin
MongoDb__DatabaseName: BCardsDB
Serilog__OpenSearchUrl: http://141.148.162.114:19201
Serilog__OpenSearchFallback: http://129.146.116.218:19202
Logging__LogLevel__Default: Information
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- published: 8080
target: 8080
protocol: tcp
mode: ingress
networks:
bcards-net:
external: true

View File

@ -1,57 +0,0 @@
version: '3.8'
configs:
luzlinks-appsettings:
external: true
services:
app:
image: registry.redecarneir.us/bcards:latest
networks:
- bcards-net
deploy:
replicas: 2
placement:
max_replicas_per_node: 1
update_config:
parallelism: 1
order: stop-first
delay: 10s
monitor: 60s
failure_action: rollback
rollback_config:
parallelism: 0
delay: 5s
configs:
- source: luzlinks-appsettings
target: /app/appsettings.Production.json
mode: 0444
volumes:
- type: bind
source: /opt/bcards-content/luzlinks
target: /app/Content/Tenants/luzlinks
read_only: true
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
MongoDb__ConnectionString: mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/LuzLinksDB?replicaSet=rs0&authSource=admin
MongoDb__DatabaseName: LuzLinksDB
Serilog__OpenSearchUrl: http://141.148.162.114:19201
Serilog__OpenSearchFallback: http://129.146.116.218:19202
Logging__LogLevel__Default: Information
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- published: 8083
target: 8080
protocol: tcp
mode: ingress
networks:
bcards-net:
external: true

View File

@ -1,57 +0,0 @@
version: '3.8'
configs:
spicylinks-appsettings:
external: true
services:
app:
image: registry.redecarneir.us/bcards:latest
networks:
- bcards-net
deploy:
replicas: 2
placement:
max_replicas_per_node: 1
update_config:
parallelism: 1
order: stop-first
delay: 10s
monitor: 60s
failure_action: rollback
rollback_config:
parallelism: 0
delay: 5s
configs:
- source: spicylinks-appsettings
target: /app/appsettings.Production.json
mode: 0444
volumes:
- type: bind
source: /opt/bcards-content/spicylinks
target: /app/Content/Tenants/spicylinks
read_only: true
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://+:8080
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
MongoDb__ConnectionString: mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/SpicyLinksDB?replicaSet=rs0&authSource=admin
MongoDb__DatabaseName: SpicyLinksDB
Serilog__OpenSearchUrl: http://141.148.162.114:19201
Serilog__OpenSearchFallback: http://129.146.116.218:19202
Logging__LogLevel__Default: Information
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- published: 8082
target: 8080
protocol: tcp
mode: ingress
networks:
bcards-net:
external: true

View File

@ -1,116 +0,0 @@
# Multi-tenant Nginx config
# Deploy to: /etc/nginx/sites-available/tenants.conf
# Symlink: ln -s /etc/nginx/sites-available/tenants.conf /etc/nginx/sites-enabled/tenants.conf
#
# Requires: certbot certificates for each domain
# certbot --nginx -d bcards.site -d www.bcards.site
# certbot --nginx -d spicylinks.site -d www.spicylinks.site
# certbot --nginx -d luzlinks.site -d www.luzlinks.site
# ─── bcards.site → :8080 ───────────────────────────────────────────────────
upstream bcards {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name bcards.site www.bcards.site;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name bcards.site www.bcards.site;
ssl_certificate /etc/letsencrypt/live/bcards.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bcards.site/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10M;
location / {
proxy_pass http://bcards;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 120s;
}
}
# ─── spicylinks.site → :8082 ───────────────────────────────────────────────
upstream spicylinks {
server 127.0.0.1:8082;
keepalive 32;
}
server {
listen 80;
server_name spicylinks.site www.spicylinks.site;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name spicylinks.site www.spicylinks.site;
ssl_certificate /etc/letsencrypt/live/spicylinks.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/spicylinks.site/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10M;
location / {
proxy_pass http://spicylinks;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 120s;
}
}
# ─── luzlinks.site → :8083 ─────────────────────────────────────────────────
upstream luzlinks {
server 127.0.0.1:8083;
keepalive 32;
}
server {
listen 80;
server_name luzlinks.site www.luzlinks.site;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name luzlinks.site www.luzlinks.site;
ssl_certificate /etc/letsencrypt/live/luzlinks.site/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/luzlinks.site/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10M;
location / {
proxy_pass http://luzlinks;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_read_timeout 120s;
}
}

BIN
saida.md

Binary file not shown.

View File

@ -1,67 +0,0 @@
using BCards.Web.Areas.Tutoriais.Models.ViewModels;
using BCards.Web.Areas.Tutoriais.Services;
using Microsoft.AspNetCore.Mvc;
namespace BCards.Web.Areas.Artigos.Controllers;
[Area("Artigos")]
public class ArtigosController : Controller
{
private readonly IMarkdownService _markdownService;
private readonly ILogger<ArtigosController> _logger;
public ArtigosController(
IMarkdownService markdownService,
ILogger<ArtigosController> logger)
{
_markdownService = markdownService;
_logger = logger;
}
// GET /artigos
public async Task<IActionResult> Index()
{
var artigos = await _markdownService
.GetAllArticlesAsync("Artigos", "pt-BR");
return View(artigos);
}
// GET /artigos/{slug}
public async Task<IActionResult> Article(string slug)
{
// Sanitização
slug = slug.Replace("..", "").Replace("/", "").Replace("\\", "");
try
{
var article = await _markdownService.GetArticleAsync(
$"Artigos/{slug}",
"pt-BR"
);
if (article == null)
{
_logger.LogWarning("Artigo não encontrado: {Slug}", slug);
return NotFound();
}
// Buscar outros artigos para "Leia também"
article.RelatedArticles = await _markdownService
.GetAllArticlesAsync("Artigos", "pt-BR");
article.RelatedArticles = article.RelatedArticles
.Where(a => a.Slug != slug)
.OrderByDescending(a => a.Date)
.Take(3)
.ToList();
return View(article);
}
catch (FileNotFoundException)
{
_logger.LogWarning("Arquivo markdown não encontrado: {Slug}", slug);
return NotFound();
}
}
}

View File

@ -1,253 +0,0 @@
@model BCards.Web.Areas.Tutoriais.Models.ViewModels.ArticleViewModel
@{
ViewData["Title"] = Model.Metadata.Title;
}
@section Head {
<!-- Meta Tags SEO -->
<meta name="description" content="@Model.Metadata.Description">
<meta name="keywords" content="@Model.Metadata.Keywords">
<meta name="author" content="@Model.Metadata.Author">
<meta name="robots" content="index, follow">
<link rel="canonical" href="@Url.Action("Article", "Artigos", new { area = "Artigos", slug = Model.Slug }, Context.Request.Scheme)">
<!-- Open Graph -->
<meta property="og:type" content="article">
<meta property="og:title" content="@Model.Metadata.Title">
<meta property="og:description" content="@Model.Metadata.Description">
<meta property="og:image" content="@Model.Metadata.Image">
<meta property="og:url" content="@Url.Action("Article", "Artigos", new { area = "Artigos", slug = Model.Slug }, Context.Request.Scheme)">
<meta property="article:published_time" content="@Model.Metadata.Date.ToString("yyyy-MM-ddTHH:mm:ssZ")">
<meta property="article:modified_time" content="@Model.Metadata.LastMod.ToString("yyyy-MM-ddTHH:mm:ssZ")">
<meta property="article:author" content="@Model.Metadata.Author">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="@Model.Metadata.Title">
<meta name="twitter:description" content="@Model.Metadata.Description">
<meta name="twitter:image" content="@Model.Metadata.Image">
<!-- Schema.org JSON-LD -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "Article",
"headline": "@Model.Metadata.Title",
"description": "@Model.Metadata.Description",
"image": "@Model.Metadata.Image",
"datePublished": "@Model.Metadata.Date.ToString("yyyy-MM-dd")",
"dateModified": "@Model.Metadata.LastMod.ToString("yyyy-MM-dd")",
"author": {
"@@type": "Person",
"name": "@Model.Metadata.Author"
},
"publisher": {
"@@type": "Organization",
"name": "BCards",
"logo": {
"@@type": "ImageObject",
"url": "https://bcards.site/logo.png"
}
}
}
</script>
<!-- BreadcrumbList Schema -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "BreadcrumbList",
"itemListElement": [
{
"@@type": "ListItem",
"position": 1,
"name": "Início",
"item": "https://bcards.site"
},
{
"@@type": "ListItem",
"position": 2,
"name": "Artigos",
"item": "https://bcards.site/artigos"
},
{
"@@type": "ListItem",
"position": 3,
"name": "@Model.Metadata.Title"
}
]
}
</script>
}
<div class="container py-5">
<div class="row">
<div class="col-lg-8">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Início</a></li>
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Artigos", new { area = "Artigos" })">Artigos</a></li>
<li class="breadcrumb-item active" aria-current="page">@Model.Metadata.Title</li>
</ol>
</nav>
<!-- Article Header -->
<article>
<header class="mb-4">
<div class="d-flex align-items-center mb-2">
<span class="badge bg-success me-2">✨ Artigo</span>
<span class="text-muted small"><i class="far fa-clock me-1"></i> @Model.Metadata.ReadingTimeMinutes min de leitura</span>
</div>
<h1 class="display-5 mb-3">@Model.Metadata.Title</h1>
<p class="lead text-muted">@Model.Metadata.Description</p>
<div class="d-flex align-items-center text-muted small mb-3">
<span class="me-3"><i class="fas fa-user me-1"></i> @Model.Metadata.Author</span>
<span class="me-3"><i class="fas fa-calendar me-1"></i> @Model.Metadata.Date.ToString("dd/MM/yyyy")</span>
<span><i class="fas fa-sync me-1"></i> Atualizado em @Model.Metadata.LastMod.ToString("dd/MM/yyyy")</span>
</div>
</header>
@if (!string.IsNullOrEmpty(Model.Metadata.Image))
{
<img src="@Model.Metadata.Image" class="img-fluid rounded mb-4" alt="@Model.Metadata.Title">
}
<!-- Article Content -->
<div class="article-content">
@Html.Raw(Model.HtmlContent)
</div>
</article>
<!-- CTA -->
<div class="alert alert-primary mt-5" role="alert">
<h4 class="alert-heading"><i class="fas fa-rocket me-2"></i> Pronto para transformar seu negócio?</h4>
<p class="mb-3">Crie seu cartão digital profissional e comece a atrair mais clientes hoje mesmo!</p>
<a href="/" class="btn btn-primary">Criar meu BCard grátis</a>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 20px;">
<!-- Related Articles -->
@if (Model.RelatedArticles.Any())
{
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title mb-3"><i class="fas fa-newspaper me-2"></i> Leia Também</h5>
<div class="list-group list-group-flush">
@foreach (var related in Model.RelatedArticles)
{
<a href="@Url.Action("Article", "Artigos", new { area = "Artigos", slug = related.Slug })" class="list-group-item list-group-item-action border-0 px-0">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">@related.Title</h6>
</div>
<small class="text-muted"><i class="far fa-clock me-1"></i> @related.ReadingTimeMinutes min</small>
</a>
}
</div>
</div>
</div>
}
<!-- Tutoriais CTA -->
<div class="card border-0 shadow-sm bg-light mb-4">
<div class="card-body text-center">
<i class="fas fa-book-open fa-3x text-primary mb-3"></i>
<h5 class="card-title">Quer aprender mais?</h5>
<p class="card-text small text-muted">Acesse nossos tutoriais práticos</p>
<a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })" class="btn btn-sm btn-primary">Ver Tutoriais</a>
</div>
</div>
<!-- Help Card -->
<div class="card border-0 shadow-sm bg-light">
<div class="card-body text-center">
<i class="fas fa-question-circle fa-3x text-primary mb-3"></i>
<h5 class="card-title">Precisa de ajuda?</h5>
<p class="card-text small text-muted">Entre em contato com nosso suporte</p>
<a href="/Support" class="btn btn-sm btn-outline-primary">Falar com suporte</a>
</div>
</div>
</div>
</div>
</div>
</div>
@section Styles {
<style>
.article-content {
font-size: 1.1rem;
line-height: 1.8;
}
.article-content h1,
.article-content h2,
.article-content h3,
.article-content h4,
.article-content h5,
.article-content h6 {
margin-top: 2rem;
margin-bottom: 1rem;
font-weight: 600;
}
.article-content h2 {
font-size: 1.75rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #dee2e6;
}
.article-content h3 {
font-size: 1.5rem;
}
.article-content p {
margin-bottom: 1.5rem;
}
.article-content img {
max-width: 100%;
height: auto;
border-radius: 0.375rem;
margin: 1.5rem 0;
}
.article-content ul,
.article-content ol {
margin-bottom: 1.5rem;
padding-left: 2rem;
}
.article-content li {
margin-bottom: 0.5rem;
}
.article-content code {
background-color: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.9em;
}
.article-content pre {
background-color: #f8f9fa;
padding: 1rem;
border-radius: 0.375rem;
overflow-x: auto;
}
.article-content blockquote {
border-left: 4px solid #198754;
padding-left: 1rem;
margin-left: 0;
font-style: italic;
color: #6c757d;
}
.article-content table {
width: 100%;
margin-bottom: 1.5rem;
border-collapse: collapse;
}
.article-content table th,
.article-content table td {
padding: 0.75rem;
border: 1px solid #dee2e6;
}
.article-content table th {
background-color: #f8f9fa;
font-weight: 600;
}
</style>
}

View File

@ -1,79 +0,0 @@
@model List<BCards.Web.Areas.Tutoriais.Models.ArticleMetadata>
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = $"Artigos {tenant.SiteName} - Inspiração e Conhecimento";
}
<div class="container py-5">
<div class="row mb-5">
<div class="col-lg-8 mx-auto text-center">
<h1 class="display-4 mb-3">✨ Artigos @tenant.SiteName</h1>
<p class="lead text-muted">Insights, tendências e inspiração para transformar sua presença digital</p>
</div>
</div>
@if (Model.Any())
{
<div class="row g-4">
@foreach (var artigo in Model)
{
<div class="col-md-6 col-lg-4">
<div class="card h-100 border-0 shadow-sm hover-shadow">
@if (!string.IsNullOrEmpty(artigo.Image))
{
<img src="@artigo.Image" class="card-img-top" alt="@artigo.Title" style="height: 200px; object-fit: cover;" onerror="this.remove()">
}
<div class="card-body">
<h5 class="card-title">@artigo.Title</h5>
<p class="card-text text-muted small">@artigo.Description</p>
<div class="d-flex justify-content-between align-items-center text-muted small">
<span><i class="far fa-clock me-1"></i> @artigo.ReadingTimeMinutes min</span>
<span>@artigo.Date.ToString("dd/MM/yyyy")</span>
</div>
</div>
<div class="card-footer bg-transparent border-0">
<a href="@Url.Action("Article", "Artigos", new { area = "Artigos", slug = artigo.Slug })" class="btn btn-sm btn-primary w-100">
Ler artigo <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
}
</div>
}
else
{
<div class="text-center py-5">
<i class="fas fa-newspaper fa-4x text-muted mb-3"></i>
<h3 class="text-muted">Nenhum artigo disponível ainda</h3>
<p class="text-muted">Em breve teremos conteúdo inspirador para você!</p>
</div>
}
<!-- CTA -->
<div class="row mt-5">
<div class="col-lg-8 mx-auto">
<div class="card bg-primary text-white border-0 shadow">
<div class="card-body text-center p-5">
<h3 class="mb-3">Quer ver tutoriais práticos?</h3>
<p class="mb-4">Acesse nossa seção de tutoriais e aprenda passo a passo como usar o @tenant.SiteName</p>
<a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })" class="btn btn-light btn-lg">
<i class="fas fa-book-open me-2"></i> Ver Tutoriais
</a>
</div>
</div>
</div>
</div>
</div>
@section Styles {
<style>
.hover-shadow {
transition: box-shadow 0.3s ease-in-out;
}
.hover-shadow:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
</style>
}

View File

@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}

View File

@ -20,18 +20,10 @@ public class SupportFabViewComponent : ViewComponent
try
{
var userId = UserClaimsPrincipal?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// Não mostrar botão de ajuda para usuários não autenticados
if (string.IsNullOrEmpty(userId))
{
_logger.LogDebug("SupportFab não exibido - usuário não autenticado");
return Content(string.Empty);
}
var options = await _supportService.GetAvailableOptionsAsync(userId);
_logger.LogDebug("SupportFab invocado para usuário {UserId} - Opções: Rating={CanRate}, Form={CanUseContactForm}, Telegram={CanAccessTelegram}",
userId, options.CanRate, options.CanUseContactForm, options.CanAccessTelegram);
userId ?? "anônimo", options.CanRate, options.CanUseContactForm, options.CanAccessTelegram);
return View(options);
}

View File

@ -1,111 +0,0 @@
using BCards.Web.Areas.Tutoriais.Models;
using BCards.Web.Areas.Tutoriais.Services;
using BCards.Web.Repositories;
using Microsoft.AspNetCore.Mvc;
namespace BCards.Web.Areas.Tutoriais.Controllers;
[Area("Tutoriais")]
public class TutoriaisController : Controller
{
private readonly IMarkdownService _markdownService;
private readonly ICategoryRepository _categoryRepository;
private readonly ILogger<TutoriaisController> _logger;
public TutoriaisController(
IMarkdownService markdownService,
ICategoryRepository categoryRepository,
ILogger<TutoriaisController> logger)
{
_markdownService = markdownService;
_categoryRepository = categoryRepository;
_logger = logger;
}
// GET /tutoriais
public async Task<IActionResult> Index()
{
var categories = await _categoryRepository.GetAllActiveAsync();
var tutoriaisPorCategoria = new Dictionary<string, List<ArticleMetadata>>();
foreach (var category in categories)
{
var artigos = await _markdownService
.GetArticlesByCategoryAsync(category.Slug, "pt-BR");
if (artigos.Any())
{
tutoriaisPorCategoria[category.Slug] = artigos;
}
}
ViewBag.Categories = categories;
return View(tutoriaisPorCategoria);
}
// GET /tutoriais/{categoria}
public async Task<IActionResult> Category(string categoria)
{
// Validar categoria existe
var category = await _categoryRepository.GetBySlugAsync(categoria);
if (category == null)
{
_logger.LogWarning("Categoria não encontrada: {Categoria}", categoria);
return NotFound();
}
var artigos = await _markdownService
.GetArticlesByCategoryAsync(categoria, "pt-BR");
ViewBag.Category = category;
return View(artigos);
}
// GET /tutoriais/{categoria}/{slug}
public async Task<IActionResult> Article(string categoria, string slug)
{
// Sanitização (segurança contra path traversal)
categoria = categoria.Replace("..", "").Replace("/", "").Replace("\\", "");
slug = slug.Replace("..", "").Replace("/", "").Replace("\\", "");
// Validar categoria existe
var category = await _categoryRepository.GetBySlugAsync(categoria);
if (category == null)
{
_logger.LogWarning("Categoria não encontrada: {Categoria}", categoria);
return NotFound();
}
try
{
var article = await _markdownService.GetArticleAsync(
$"Tutoriais/{categoria}/{slug}",
"pt-BR"
);
if (article == null)
{
_logger.LogWarning("Artigo não encontrado: {Categoria}/{Slug}", categoria, slug);
return NotFound();
}
// Buscar artigos relacionados da mesma categoria
article.RelatedArticles = await _markdownService
.GetArticlesByCategoryAsync(categoria, "pt-BR");
// Remover o artigo atual dos relacionados
article.RelatedArticles = article.RelatedArticles
.Where(a => a.Slug != slug)
.Take(3)
.ToList();
ViewBag.Category = category;
return View(article);
}
catch (FileNotFoundException)
{
_logger.LogWarning("Arquivo markdown não encontrado: {Categoria}/{Slug}", categoria, slug);
return NotFound();
}
}
}

View File

@ -1,16 +0,0 @@
namespace BCards.Web.Areas.Tutoriais.Models;
public class ArticleMetadata
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Keywords { get; set; } = string.Empty;
public string Author { get; set; } = "BCards";
public DateTime Date { get; set; }
public DateTime LastMod { get; set; }
public string Image { get; set; } = string.Empty;
public string Culture { get; set; } = "pt-BR";
public string? Category { get; set; } // Apenas para tutoriais
public int ReadingTimeMinutes { get; set; }
public string Slug { get; set; } = string.Empty;
}

View File

@ -1,10 +0,0 @@
namespace BCards.Web.Areas.Tutoriais.Models.ViewModels;
public class ArticleViewModel
{
public ArticleMetadata Metadata { get; set; } = new();
public string HtmlContent { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
public DateTime LastModified { get; set; }
public List<ArticleMetadata> RelatedArticles { get; set; } = new();
}

View File

@ -1,11 +0,0 @@
using BCards.Web.Areas.Tutoriais.Models;
using BCards.Web.Areas.Tutoriais.Models.ViewModels;
namespace BCards.Web.Areas.Tutoriais.Services;
public interface IMarkdownService
{
Task<ArticleViewModel?> GetArticleAsync(string relativePath, string culture);
Task<List<ArticleMetadata>> GetArticlesByCategoryAsync(string category, string culture);
Task<List<ArticleMetadata>> GetAllArticlesAsync(string baseFolder, string culture);
}

View File

@ -1,244 +0,0 @@
using BCards.Web.Areas.Tutoriais.Models;
using BCards.Web.Areas.Tutoriais.Models.ViewModels;
using BCards.Web.Configuration;
using Markdig;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace BCards.Web.Areas.Tutoriais.Services;
public class MarkdownService : IMarkdownService
{
private readonly IMemoryCache _cache;
private readonly ILogger<MarkdownService> _logger;
private readonly string _contentBasePath;
private readonly MarkdownPipeline _markdownPipeline;
private readonly IDeserializer _yamlDeserializer;
public MarkdownService(
IMemoryCache cache,
ILogger<MarkdownService> logger,
IWebHostEnvironment environment,
IOptions<TenantSettings> tenantSettings)
{
_cache = cache;
_logger = logger;
_contentBasePath = Path.Combine(
environment.ContentRootPath, "Content", "Tenants", tenantSettings.Value.ContentFolder);
// Pipeline Markdig com extensões avançadas
_markdownPipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions() // Tables, footnotes, etc.
.UseAutoLinks() // Auto-link URLs
.UseEmphasisExtras() // ~~strikethrough~~
.UseGenericAttributes() // {#id .class}
.DisableHtml() // Segurança: bloqueia HTML inline
.Build();
// Deserializador YAML
_yamlDeserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build();
}
public async Task<ArticleViewModel?> GetArticleAsync(string relativePath, string culture)
{
var cacheKey = $"Article_{relativePath}_{culture}";
// Verificar cache
if (_cache.TryGetValue(cacheKey, out ArticleViewModel? cachedArticle))
{
_logger.LogDebug("Artigo encontrado no cache: {Path}", relativePath);
return cachedArticle;
}
// Construir caminho completo
var fullPath = Path.Combine(_contentBasePath, $"{relativePath}.{culture}.md");
if (!File.Exists(fullPath))
{
_logger.LogWarning("Arquivo não encontrado: {Path}", fullPath);
return null;
}
try
{
var content = await File.ReadAllTextAsync(fullPath);
var (metadata, markdownContent) = ExtractFrontmatter(content);
if (metadata == null)
{
_logger.LogError("Frontmatter inválido em: {Path}", fullPath);
return null;
}
// Processar markdown → HTML
var htmlContent = Markdown.ToHtml(markdownContent, _markdownPipeline);
// Calcular tempo de leitura (200 palavras/minuto)
var wordCount = markdownContent.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
metadata.ReadingTimeMinutes = Math.Max(1, wordCount / 200);
// Extrair slug do path
metadata.Slug = Path.GetFileNameWithoutExtension(
Path.GetFileNameWithoutExtension(relativePath.Split('/').Last())
);
metadata.Culture = culture;
var article = new ArticleViewModel
{
Metadata = metadata,
HtmlContent = htmlContent,
Slug = metadata.Slug,
LastModified = File.GetLastWriteTimeUtc(fullPath)
};
// Cache por 1 hora
_cache.Set(cacheKey, article, TimeSpan.FromHours(1));
_logger.LogInformation("Artigo processado e cacheado: {Path}", relativePath);
return article;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao processar artigo: {Path}", fullPath);
return null;
}
}
public async Task<List<ArticleMetadata>> GetArticlesByCategoryAsync(string category, string culture)
{
var cacheKey = $"CategoryArticles_{category}_{culture}";
if (_cache.TryGetValue(cacheKey, out List<ArticleMetadata>? cached))
{
return cached ?? new List<ArticleMetadata>();
}
var categoryPath = Path.Combine(_contentBasePath, "Tutoriais", category);
if (!Directory.Exists(categoryPath))
{
_logger.LogWarning("Diretório de categoria não encontrado: {Path}", categoryPath);
return new List<ArticleMetadata>();
}
var articles = new List<ArticleMetadata>();
var files = Directory.GetFiles(categoryPath, $"*.{culture}.md");
foreach (var file in files)
{
try
{
var content = await File.ReadAllTextAsync(file);
var (metadata, _) = ExtractFrontmatter(content);
if (metadata != null)
{
var slug = Path.GetFileNameWithoutExtension(
Path.GetFileNameWithoutExtension(file)
);
metadata.Slug = slug;
metadata.Culture = culture;
metadata.Category = category;
articles.Add(metadata);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao processar arquivo: {File}", file);
}
}
// Ordenar por data (mais recentes primeiro)
articles = articles.OrderByDescending(a => a.Date).ToList();
_cache.Set(cacheKey, articles, TimeSpan.FromHours(1));
return articles;
}
public async Task<List<ArticleMetadata>> GetAllArticlesAsync(string baseFolder, string culture)
{
var cacheKey = $"AllArticles_{baseFolder}_{culture}";
if (_cache.TryGetValue(cacheKey, out List<ArticleMetadata>? cached))
{
return cached ?? new List<ArticleMetadata>();
}
var folderPath = Path.Combine(_contentBasePath, baseFolder);
if (!Directory.Exists(folderPath))
{
_logger.LogWarning("Pasta não encontrada: {Path}", folderPath);
return new List<ArticleMetadata>();
}
var articles = new List<ArticleMetadata>();
var files = Directory.GetFiles(folderPath, $"*.{culture}.md", SearchOption.AllDirectories);
foreach (var file in files)
{
try
{
var content = await File.ReadAllTextAsync(file);
var (metadata, _) = ExtractFrontmatter(content);
if (metadata != null)
{
var slug = Path.GetFileNameWithoutExtension(
Path.GetFileNameWithoutExtension(file)
);
metadata.Slug = slug;
metadata.Culture = culture;
articles.Add(metadata);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao processar arquivo: {File}", file);
}
}
articles = articles.OrderByDescending(a => a.Date).ToList();
_cache.Set(cacheKey, articles, TimeSpan.FromHours(1));
return articles;
}
private (ArticleMetadata? metadata, string content) ExtractFrontmatter(string fileContent)
{
var lines = fileContent.Split('\n');
if (lines.Length < 3 || !lines[0].Trim().Equals("---"))
{
_logger.LogWarning("Frontmatter não encontrado (deve começar com ---)");
return (null, fileContent);
}
var endIndex = Array.FindIndex(lines, 1, line => line.Trim().Equals("---"));
if (endIndex == -1)
{
_logger.LogWarning("Frontmatter mal formatado (falta --- de fechamento)");
return (null, fileContent);
}
try
{
var yamlContent = string.Join('\n', lines[1..endIndex]);
var metadata = _yamlDeserializer.Deserialize<ArticleMetadata>(yamlContent);
var markdownContent = string.Join('\n', lines[(endIndex + 1)..]);
return (metadata, markdownContent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao deserializar YAML frontmatter");
return (null, fileContent);
}
}
}

View File

@ -1,251 +0,0 @@
@model BCards.Web.Areas.Tutoriais.Models.ViewModels.ArticleViewModel
@{
var category = ViewBag.Category as BCards.Web.Models.Category;
ViewData["Title"] = Model.Metadata.Title;
}
@section Head {
<!-- Meta Tags SEO -->
<meta name="description" content="@Model.Metadata.Description">
<meta name="keywords" content="@Model.Metadata.Keywords">
<meta name="author" content="@Model.Metadata.Author">
<meta name="robots" content="index, follow">
<link rel="canonical" href="@Url.Action("Article", "Tutoriais", new { area = "Tutoriais", categoria = category?.Slug, slug = Model.Slug }, Context.Request.Scheme)">
<!-- Open Graph -->
<meta property="og:type" content="article">
<meta property="og:title" content="@Model.Metadata.Title">
<meta property="og:description" content="@Model.Metadata.Description">
<meta property="og:image" content="@Model.Metadata.Image">
<meta property="og:url" content="@Url.Action("Article", "Tutoriais", new { area = "Tutoriais", categoria = category?.Slug, slug = Model.Slug }, Context.Request.Scheme)">
<meta property="article:published_time" content="@Model.Metadata.Date.ToString("yyyy-MM-ddTHH:mm:ssZ")">
<meta property="article:modified_time" content="@Model.Metadata.LastMod.ToString("yyyy-MM-ddTHH:mm:ssZ")">
<meta property="article:author" content="@Model.Metadata.Author">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="@Model.Metadata.Title">
<meta name="twitter:description" content="@Model.Metadata.Description">
<meta name="twitter:image" content="@Model.Metadata.Image">
<!-- Schema.org JSON-LD -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "Article",
"headline": "@Model.Metadata.Title",
"description": "@Model.Metadata.Description",
"image": "@Model.Metadata.Image",
"datePublished": "@Model.Metadata.Date.ToString("yyyy-MM-dd")",
"dateModified": "@Model.Metadata.LastMod.ToString("yyyy-MM-dd")",
"author": {
"@@type": "Person",
"name": "@Model.Metadata.Author"
},
"publisher": {
"@@type": "Organization",
"name": "BCards",
"logo": {
"@@type": "ImageObject",
"url": "https://bcards.site/logo.png"
}
}
}
</script>
<!-- BreadcrumbList Schema -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "BreadcrumbList",
"itemListElement": [
{
"@@type": "ListItem",
"position": 1,
"name": "Início",
"item": "https://bcards.site"
},
{
"@@type": "ListItem",
"position": 2,
"name": "Tutoriais",
"item": "https://bcards.site/tutoriais"
},
{
"@@type": "ListItem",
"position": 3,
"name": "@category?.Name",
"item": "https://bcards.site/tutoriais/@category?.Slug"
},
{
"@@type": "ListItem",
"position": 4,
"name": "@Model.Metadata.Title"
}
]
}
</script>
}
<div class="container py-5">
<div class="row">
<div class="col-lg-8">
<!-- Breadcrumb -->
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">Início</a></li>
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })">Tutoriais</a></li>
<li class="breadcrumb-item"><a href="@Url.Action("Category", "Tutoriais", new { area = "Tutoriais", categoria = category?.Slug })">@category?.Name</a></li>
<li class="breadcrumb-item active" aria-current="page">@Model.Metadata.Title</li>
</ol>
</nav>
<!-- Article Header -->
<article>
<header class="mb-4">
<div class="d-flex align-items-center mb-2">
<span class="badge bg-primary me-2">@category?.Icon @category?.Name</span>
<span class="text-muted small"><i class="far fa-clock me-1"></i> @Model.Metadata.ReadingTimeMinutes min de leitura</span>
</div>
<h1 class="display-5 mb-3">@Model.Metadata.Title</h1>
<p class="lead text-muted">@Model.Metadata.Description</p>
<div class="d-flex align-items-center text-muted small mb-3">
<span class="me-3"><i class="fas fa-user me-1"></i> @Model.Metadata.Author</span>
<span class="me-3"><i class="fas fa-calendar me-1"></i> @Model.Metadata.Date.ToString("dd/MM/yyyy")</span>
<span><i class="fas fa-sync me-1"></i> Atualizado em @Model.Metadata.LastMod.ToString("dd/MM/yyyy")</span>
</div>
</header>
@if (!string.IsNullOrEmpty(Model.Metadata.Image))
{
<img src="@Model.Metadata.Image" class="img-fluid rounded mb-4" alt="@Model.Metadata.Title">
}
<!-- Article Content -->
<div class="article-content">
@Html.Raw(Model.HtmlContent)
</div>
</article>
<!-- CTA -->
<div class="alert alert-primary mt-5" role="alert">
<h4 class="alert-heading"><i class="fas fa-rocket me-2"></i> Pronto para criar seu BCard?</h4>
<p class="mb-3">Agora que você aprendeu como funciona, que tal criar seu próprio cartão digital profissional?</p>
<a href="/" class="btn btn-primary">Criar meu BCard grátis</a>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 20px;">
<!-- Related Articles -->
@if (Model.RelatedArticles.Any())
{
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title mb-3"><i class="fas fa-book-open me-2"></i> Tutoriais Relacionados</h5>
<div class="list-group list-group-flush">
@foreach (var related in Model.RelatedArticles)
{
<a href="@Url.Action("Article", "Tutoriais", new { area = "Tutoriais", categoria = category?.Slug, slug = related.Slug })" class="list-group-item list-group-item-action border-0 px-0">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">@related.Title</h6>
</div>
<small class="text-muted"><i class="far fa-clock me-1"></i> @related.ReadingTimeMinutes min</small>
</a>
}
</div>
</div>
</div>
}
<!-- Help Card -->
<div class="card border-0 shadow-sm bg-light">
<div class="card-body text-center">
<i class="fas fa-question-circle fa-3x text-primary mb-3"></i>
<h5 class="card-title">Precisa de ajuda?</h5>
<p class="card-text small text-muted">Entre em contato com nosso suporte se tiver dúvidas</p>
<a href="/Support" class="btn btn-sm btn-outline-primary">Falar com suporte</a>
</div>
</div>
</div>
</div>
</div>
</div>
@section Styles {
<style>
.article-content {
font-size: 1.1rem;
line-height: 1.8;
}
.article-content h1,
.article-content h2,
.article-content h3,
.article-content h4,
.article-content h5,
.article-content h6 {
margin-top: 2rem;
margin-bottom: 1rem;
font-weight: 600;
}
.article-content h2 {
font-size: 1.75rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #dee2e6;
}
.article-content h3 {
font-size: 1.5rem;
}
.article-content p {
margin-bottom: 1.5rem;
}
.article-content img {
max-width: 100%;
height: auto;
border-radius: 0.375rem;
margin: 1.5rem 0;
}
.article-content ul,
.article-content ol {
margin-bottom: 1.5rem;
padding-left: 2rem;
}
.article-content li {
margin-bottom: 0.5rem;
}
.article-content code {
background-color: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-size: 0.9em;
}
.article-content pre {
background-color: #f8f9fa;
padding: 1rem;
border-radius: 0.375rem;
overflow-x: auto;
}
.article-content blockquote {
border-left: 4px solid #0d6efd;
padding-left: 1rem;
margin-left: 0;
font-style: italic;
color: #6c757d;
}
.article-content table {
width: 100%;
margin-bottom: 1.5rem;
border-collapse: collapse;
}
.article-content table th,
.article-content table td {
padding: 0.75rem;
border: 1px solid #dee2e6;
}
.article-content table th {
background-color: #f8f9fa;
font-weight: 600;
}
</style>
}

View File

@ -1,74 +0,0 @@
@model List<BCards.Web.Areas.Tutoriais.Models.ArticleMetadata>
@{
var category = ViewBag.Category as BCards.Web.Models.Category;
ViewData["Title"] = $"Tutoriais de {category?.Name} - BCards";
}
<div class="container py-5">
<div class="row mb-5">
<div class="col-lg-8 mx-auto text-center">
<span class="display-1">@category?.Icon</span>
<h1 class="display-5 mb-3">Tutoriais de @category?.Name</h1>
<p class="lead text-muted">@category?.Description</p>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center">
<li class="breadcrumb-item"><a href="/">Início</a></li>
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })">Tutoriais</a></li>
<li class="breadcrumb-item active" aria-current="page">@category?.Name</li>
</ol>
</nav>
</div>
</div>
@if (Model.Any())
{
<div class="row g-4">
@foreach (var artigo in Model)
{
<div class="col-md-6 col-lg-4">
<div class="card h-100 border-0 shadow-sm hover-shadow">
@if (!string.IsNullOrEmpty(artigo.Image))
{
<img src="@artigo.Image" class="card-img-top" alt="@artigo.Title" style="height: 200px; object-fit: cover;">
}
<div class="card-body">
<h5 class="card-title">@artigo.Title</h5>
<p class="card-text text-muted small">@artigo.Description</p>
<div class="d-flex justify-content-between align-items-center text-muted small">
<span><i class="far fa-clock me-1"></i> @artigo.ReadingTimeMinutes min</span>
<span>@artigo.Date.ToString("dd/MM/yyyy")</span>
</div>
</div>
<div class="card-footer bg-transparent border-0">
<a href="@Url.Action("Article", "Tutoriais", new { area = "Tutoriais", categoria = category?.Slug, slug = artigo.Slug })" class="btn btn-sm btn-primary w-100">
Ler tutorial <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
}
</div>
}
else
{
<div class="text-center py-5">
<i class="fas fa-book-open fa-4x text-muted mb-3"></i>
<h3 class="text-muted">Nenhum tutorial disponível nesta categoria</h3>
<p class="text-muted">Em breve teremos tutoriais para @category?.Name!</p>
<a href="@Url.Action("Index", "Tutoriais", new { area = "Tutoriais" })" class="btn btn-primary mt-3">
<i class="fas fa-arrow-left me-2"></i> Voltar para tutoriais
</a>
</div>
}
</div>
@section Styles {
<style>
.hover-shadow {
transition: box-shadow 0.3s ease-in-out;
}
.hover-shadow:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
</style>
}

View File

@ -1,84 +0,0 @@
@model Dictionary<string, List<BCards.Web.Areas.Tutoriais.Models.ArticleMetadata>>
@{
ViewData["Title"] = "Tutoriais BCards - Aprenda a usar o BCards";
var categories = ViewBag.Categories as List<BCards.Web.Models.Category>;
}
<div class="container py-5">
<div class="row mb-5">
<div class="col-lg-8 mx-auto text-center">
<h1 class="display-4 mb-3">📚 Tutoriais BCards</h1>
<p class="lead text-muted">Aprenda a usar o BCards e maximize seus resultados com guias práticos por categoria</p>
</div>
</div>
@if (Model.Any())
{
@foreach (var categorySlug in Model.Keys)
{
var category = categories?.FirstOrDefault(c => c.Slug == categorySlug);
var artigos = Model[categorySlug];
if (category != null && artigos.Any())
{
<div class="mb-5">
<div class="d-flex align-items-center mb-3">
<span class="fs-2 me-2">@category.Icon</span>
<h2 class="mb-0">@category.Name</h2>
<a href="@Url.Action("Category", "Tutoriais", new { area = "Tutoriais", categoria = category.Slug })" class="ms-auto btn btn-sm btn-outline-primary">
Ver todos <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
<p class="text-muted mb-4">@category.Description</p>
<div class="row g-4">
@foreach (var artigo in artigos.Take(3))
{
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm hover-shadow">
@if (!string.IsNullOrEmpty(artigo.Image))
{
<img src="@artigo.Image" class="card-img-top" alt="@artigo.Title" style="height: 200px; object-fit: cover;">
}
<div class="card-body">
<h5 class="card-title">@artigo.Title</h5>
<p class="card-text text-muted small">@artigo.Description</p>
<div class="d-flex justify-content-between align-items-center text-muted small">
<span><i class="far fa-clock me-1"></i> @artigo.ReadingTimeMinutes min</span>
<span>@artigo.Date.ToString("dd/MM/yyyy")</span>
</div>
</div>
<div class="card-footer bg-transparent border-0">
<a href="@Url.Action("Article", "Tutoriais", new { area = "Tutoriais", categoria = category.Slug, slug = artigo.Slug })" class="btn btn-sm btn-primary w-100">
Ler tutorial <i class="fas fa-arrow-right ms-1"></i>
</a>
</div>
</div>
</div>
}
</div>
</div>
<hr class="my-5">
}
}
}
else
{
<div class="text-center py-5">
<i class="fas fa-book-open fa-4x text-muted mb-3"></i>
<h3 class="text-muted">Nenhum tutorial disponível ainda</h3>
<p class="text-muted">Em breve teremos tutoriais incríveis para você!</p>
</div>
}
</div>
@section Styles {
<style>
.hover-shadow {
transition: box-shadow 0.3s ease-in-out;
}
.hover-shadow:hover {
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
</style>
}

View File

@ -1,3 +0,0 @@
@{
Layout = "_Layout";
}

View File

@ -10,7 +10,6 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.43.0" />
<PackageReference Include="MongoDB.Driver" Version="3.4.2" />
<PackageReference Include="Stripe.net" Version="48.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="8.0.4" />
@ -32,19 +31,12 @@
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" />
<PackageReference Include="AspNetCore.HealthChecks.MongoDb" Version="7.0.0" />
<PackageReference Include="AspNetCore.DataProtection.MongoDB" Version="8.0.0" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.resx" />
</ItemGroup>
<ItemGroup>
<None Update="Content\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Testing'">
<DefineConstants>$(DefineConstants);TESTING</DefineConstants>
</PropertyGroup>

View File

@ -1,12 +0,0 @@
namespace BCards.Web.Configuration;
public class LinkTypeConfig
{
public string Icon { get; set; } = "";
public string Label { get; set; } = "";
public string Prefix { get; set; } = "https://";
public string? VisualPrefix { get; set; }
public string Placeholder { get; set; } = "";
public string Instructions { get; set; } = "";
public string Color { get; set; } = "bg-primary";
}

View File

@ -1,8 +0,0 @@
namespace BCards.Web.Configuration;
public class TenantFeature
{
public string Icon { get; set; } = "";
public string Title { get; set; } = "";
public string Description { get; set; } = "";
}

View File

@ -1,49 +0,0 @@
namespace BCards.Web.Configuration;
public class TenantSettings
{
public string SiteName { get; set; } = "BCards";
public string SiteDescription { get; set; } = "Crie sua página profissional com links organizados. A melhor alternativa para ter sua bio / links. Criada para profissionais e empresas no Brasil.";
public string Tagline { get; set; } = "Crie sua bios / links Profissional";
public string SupportEmail { get; set; } = "suporte@bcards.site";
public string ContentFolder { get; set; } = "bcards";
public bool AgeGated { get; set; } = false;
public string UrlExample { get; set; } = "bcards.site/corretor/seu-nome";
public string DpoEmail { get; set; } = "dpo@bcards.site";
public List<LinkTypeConfig> AllowedLinkTypes { get; set; } = new();
// Hero section
public string HeroHeadline { get; set; } = "Sua presença digital profissional em um só lugar";
public string HeroDescription { get; set; } = "Organize todos os seus links, portfólio, contatos e redes sociais em uma página única e elegante.";
public string HeroCtaText { get; set; } = "Criar Minha Página Grátis";
// Features section
public string FeaturesHeadline { get; set; } = "Por que escolher o {SiteName}?";
public List<TenantFeature> Features { get; set; } = new();
// Bottom CTA section
public string CtaHeadline { get; set; } = "Pronto para começar?";
public string CtaDescription { get; set; } = "Crie sua página agora mesmo e comece a organizar seus links.";
public string CtaButtonText { get; set; } = "Começar Grátis";
// SEO / Layout
public string MetaKeywords { get; set; } = "cartão digital, página de links, bio links, linktree brasil, página profissional";
public string FooterTagline { get; set; } = "Sua presença online, simplificada.";
// Branding / Colors
public string HeroGradient { get; set; } = "linear-gradient(135deg, #667eea 0%, #764ba2 100%)";
public string PrimaryColor { get; set; } = "#0d6efd";
public string PrimaryColorDark { get; set; } = "#0a58ca";
// Category seeding (se vazio, usa os padrões do BCards)
public List<CategorySeedItem> DefaultCategories { get; set; } = new();
}
public class CategorySeedItem
{
public string Name { get; set; } = "";
public string Slug { get; set; } = "";
public string Icon { get; set; } = "";
public string Description { get; set; } = "";
public List<string> SeoKeywords { get; set; } = new();
}

View File

@ -1,255 +0,0 @@
---
title: "Como Corretores de Imóveis Usam o BCards para Fechar Mais Negócios"
description: "Guia prático para corretores de imóveis organizarem sua presença digital com uma bio link profissional — do CRECI ao WhatsApp, portfólio de imóveis e redes sociais."
keywords: "corretor de imóveis digital, bio link corretor, CRECI online, captação de clientes imóveis, presença digital imobiliária"
author: "Equipe BCards"
date: 2026-03-29
lastMod: 2026-03-29
image: "/images/artigos/corretor-imoveis.jpg"
culture: "pt-BR"
category: "negocios"
---
# Como Corretores de Imóveis Usam o BCards para Fechar Mais Negócios
Imagine um potencial comprador que acabou de ver um imóvel seu no Instagram. Ele quer saber mais: quem é você, que outros imóveis tem, como entrar em contato agora mesmo. Ele clica na sua bio — e encontra apenas um link para o site da imobiliária onde você trabalha, que não tem seu nome em lugar nenhum.
Oportunidade perdida.
A captação de clientes imobiliários mudou. Hoje, o primeiro contato acontece online — no Instagram, no YouTube, no Google. O corretor que converte mais é o que reduz o atrito entre "interesse" e "contato".
---
## A Realidade do Corretor de Imóveis no Brasil
O mercado imobiliário brasileiro tem mais de 400 mil corretores registrados no COFECI. A maioria concorre nas mesmas plataformas — portais de anúncios, redes sociais, grupos de WhatsApp — com os mesmos imóveis e as mesmas fotos.
O que diferencia não é o imóvel. É a confiança que o corretor consegue construir antes do primeiro contato.
Uma bio link profissional é uma das formas mais simples de estabelecer essa confiança.
---
## O que um BCards de Corretor Deve Ter
### Informações que Geram Confiança
**1. Seu nome completo e CRECI**
O CRECI visível na bio link é sinal imediato de profissionalismo. Para compradores e vendedores que recebem abordagens de toda sorte de pessoas, ver o número do CRECI ali, no início, elimina objeções antes que elas existam.
```
João Silva | Corretor de Imóveis
CRECI-SP 123456
```
**2. Especialização**
Corretores que se posicionam em um nicho específico atraem clientes mais qualificados e cobram mais bem. Seja direto:
```
Especialista em imóveis residenciais em Campinas e região
```
ou
```
Imóveis de alto padrão — Alphaville e Tamboré
```
ou
```
Imóveis comerciais e salas no centro de São Paulo
```
**3. Regiões de atuação**
Compradores pesquisam por localização. Deixe claro onde você atua para não gerar contatos fora do seu mercado.
---
## Os Links Essenciais para Corretores
### 1. WhatsApp Comercial (Primeiro Link)
O WhatsApp é onde a negociação começa. Coloque como primeiro link com mensagem pré-preenchida:
```
Título: 💬 Falar com João Corretor
URL: https://wa.me/5511999999999?text=Olá João, vi seu perfil e gostaria de saber mais sobre os imóveis disponíveis
```
A mensagem pré-preenchida filtra curiosos e já contextualiza o contato. Quem clica e envia está genuinamente interessado.
### 2. Portfólio de Imóveis
Sua página com imóveis disponíveis — seja no portal da imobiliária, no seu site próprio, ou num perfil específico no Viva Real / ZAP:
```
Título: 🏠 Ver Imóveis Disponíveis
URL: https://zapimoveis.com.br/corretor/joao-silva
```
### 3. Instagram
Corretores que postam conteúdo de valor no Instagram (tours virtuais, dicas de compra, bastidores do mercado) constroem audiência qualificada. Inclua o link:
```
Título: 📸 Ver Imóveis no Instagram
URL: https://instagram.com/joaosilvacorretor
```
### 4. LinkedIn
Para corretores focados em imóveis comerciais ou alto padrão, o LinkedIn tem peso diferente. Clientes corporativos pesquisam por aí:
```
Título: 💼 Perfil Profissional no LinkedIn
URL: https://linkedin.com/in/joaosilva-corretor
```
### 5. YouTube ou Canal de Vídeos
Tours virtuais e vídeos de bairro têm alta conversão. Se você produz esse conteúdo, inclua:
```
Título: 🎥 Tours Virtuais e Dicas de Imóveis
URL: https://youtube.com/@joaosilvacorretor
```
### 6. Verificação do CRECI
O Conselho Regional de Corretores de Imóveis disponibiliza consulta pública online. Incluir o link direto para consulta do seu CRECI é um diferencial pouco usado, mas extremamente eficaz para credibilidade:
```
Título: ✅ Verificar meu CRECI
URL: https://creci-sp.org.br/consulta-corretor/...
```
Poucos fazem isso. Quem faz se destaca imediatamente.
### 7. Email Profissional
Para comunicação formal e documentação:
```
Título: ✉️ Enviar Email
URL: mailto:joao@jsilva-imoveis.com.br
```
---
## Como Usar o BCards Para Diferentes Tipos de Negociação
### Captação de Proprietários
Quando você aborda um proprietário que quer vender, precisa demonstrar profissionalismo em segundos. Compartilhe seu BCards logo no início da conversa:
*"Meu nome é João Silva, sou corretor com CRECI-SP 123456. Aqui está minha página com meus contatos e os últimos imóveis que comercializei."*
A página bem feita faz seu trabalho por você enquanto o cliente consulta com calma.
### Atendimento a Compradores
Compradores pesquisam online antes de entrar em contato. Quando alguém chega até você por indicação ou pelo Instagram, ter um BCards organizado confirma a decisão deles:
- Veem que você tem CRECI
- Veem os imóveis disponíveis
- Veem reviews ou histórico
- Escolhem como entrar em contato
### Parcerias com Outros Corretores
Corretores frequentemente fecham negócios em parceria. Uma página profissional facilita apresentações a colegas de outras imobiliárias:
```
bcards.site/negocios/joao-silva-corretor
```
---
## Erros Comuns de Corretores na Presença Digital
### ❌ Usar apenas o perfil da imobiliária
Quando você sai da imobiliária, perde toda a presença digital construída. Construa sua marca pessoal, paralela à da empresa onde trabalha.
### ❌ Link na bio que leva para todos os imóveis do portal
O cliente clicou para te conhecer, não para navegar em 15 mil anúncios. Direcione para seus imóveis específicos ou para seu perfil no portal.
### ❌ WhatsApp sem mensagem pré-preenchida
"Olá" como primeira mensagem exige que você pergunte o contexto. Uma mensagem pré-preenchida já chega com contexto — e aumenta a taxa de resposta.
### ❌ Não atualizar os links quando os imóveis são vendidos
Nada pior para a credibilidade do que imóveis "disponíveis" que já foram vendidos. Mantenha os links atualizados — é a mesma lógica de manter anúncios honestos.
### ❌ Bio sem CRECI
Sem CRECI visível, você parece igual a qualquer pessoa que vende imóvel sem habilitação. É a credencial mais importante que você tem — use-a.
---
## Slug e URL: Como Aparecer na Busca
Escolha um slug que combine seu nome com a especialização:
```
joao-silva-corretor → bcards.site/negocios/joao-silva-corretor
marina-costa-alphaville → bcards.site/negocios/marina-costa-alphaville
pedro-alves-comercial-sp → bcards.site/negocios/pedro-alves-comercial-sp
```
Evite slugs genéricos como `corretor123` — você vai precisar ditar esse link para clientes.
---
## Integrando o BCards ao Seu Processo Comercial
**No cartão de visita físico:**
Coloque o QR code ou a URL do seu BCards. Substituir o site da imobiliária pelo seu link pessoal transforma cada cartão em um funil direto para você.
**Na assinatura de email:**
```
João Silva | Corretor de Imóveis CRECI-SP 123456
📱 (11) 99999-9999
🔗 bcards.site/negocios/joao-silva-corretor
```
**No rodapé dos materiais de apresentação de imóveis:**
Toda proposta, laudo ou apresentação que você envia por email deve ter seu BCards no rodapé.
**Nos stories do Instagram:**
Use o sticker de link para direcionar para seu BCards em vez do perfil da imobiliária.
---
## Construindo Credibilidade com Avaliações
O mercado imobiliário é movido por indicação. Uma forma de digitalizar isso é incluir na sua bio link:
- Link para suas avaliações no Google Meu Negócio
- Link para recomendações no LinkedIn
- Depoimento em vídeo de cliente satisfeito no YouTube
Compradores e vendedores pesquisam por corretores no Google antes de ligar. Uma avaliação de 4.8 com 50 reviews aparece nos resultados — e faz toda a diferença.
---
## Conclusão
O mercado imobiliário é competitivo, mas poucos corretores investem em presença digital própria. A maioria depende dos portais e da imobiliária onde trabalha.
Um BCards profissional com CRECI visível, WhatsApp otimizado e portfólio atualizado diferencia você antes mesmo do primeiro contato — e mantém sua presença digital intacta independentemente de onde você trabalha.
**Próximos passos:**
1. Crie sua conta no BCards
2. Defina seu slug com nome e especialização
3. Adicione CRECI, WhatsApp e portfólio de imóveis
4. Atualize a bio do Instagram com o novo link
5. Inclua o link no cartão de visita e na assinatura de email
[Criar meu BCards de corretor →](https://bcards.site/)
---
**Última atualização:** Março 2026

View File

@ -1,395 +0,0 @@
---
title: "BCards vs LinkTree: Compare e Escolha a Melhor Alternativa Brasileira"
description: "Comparação completa entre BCards e LinkTree. Descubra qual plataforma oferece melhor custo-benefício, mais funcionalidades e suporte em português para sua página de links profissional."
keywords: "linktree, alternativa ao linktree, bcards, página de links, linktree brasil, comparação linktree, melhor que linktree"
author: "Equipe BCards"
date: 2025-01-15
lastMod: 2025-01-15
image: "/images/artigos/bcards-vs-linktree.jpg"
culture: "pt-BR"
---
# BCards vs LinkTree: Compare e Escolha a Melhor Alternativa Brasileira
Se você está procurando uma alternativa ao LinkTree, provavelmente já percebeu que existem diversas opções no mercado. Mas qual delas oferece o melhor custo-benefício para profissionais e empresas brasileiras?
Neste artigo, vamos fazer uma comparação honesta e detalhada entre **BCards** e **LinkTree**, analisando funcionalidades, preços, suporte e muito mais para ajudá-lo a tomar a melhor decisão.
## O Que São Plataformas de Bio Links?
Antes de mergulharmos na comparação, vamos entender o conceito. Plataformas como LinkTree e BCards permitem que você crie uma página única com múltiplos links, perfeita para compartilhar em suas redes sociais (especialmente Instagram, TikTok e Twitter, onde você tem espaço limitado para links).
Em vez de escolher apenas um link na sua bio, você direciona seus seguidores para uma página centralizada com todos os seus links importantes: site, blog, produtos, redes sociais, portfólio e muito mais.
## Visão Geral: BCards vs LinkTree
### LinkTree: O Pioneer Global
O LinkTree foi uma das primeiras plataformas de bio links a ganhar popularidade mundial. Fundado na Austrália, hoje é usado por milhões de criadores de conteúdo, influenciadores e empresas ao redor do mundo.
**Principais Características:**
- Interface simples e intuitiva
- Grande reconhecimento de marca internacional
- Diversas integrações com plataformas globais
- Suporte em inglês
### BCards: A Alternativa Brasileira
O BCards é uma plataforma brasileira desenvolvida especificamente para atender às necessidades do mercado nacional. Criada por profissionais que entendem os desafios locais, oferece funcionalidades pensadas para o público brasileiro.
**Principais Características:**
- Totalmente em português
- Suporte local e personalizado
- Preços em reais (sem variação cambial)
- URLs organizadas por categoria profissional
- Foco no mercado brasileiro e latino-americano
## Comparação Detalhada de Funcionalidades
### 1. Estrutura de URLs
**LinkTree:**
- Formato: `linktr.ee/seuusuario`
- URL genérica para todos os usuários
**BCards:**
- Formato: `bcards.site/page/{categoria}/{seu-slug}`
- Exemplos:
- `bcards.site/page/advocacia/maria-silva`
- `bcards.site/page/tecnologia/joao-dev`
- `bcards.site/page/saude/dra-ana-cardiologista`
**Vantagem BCards:** URLs hierárquicas melhoram significativamente o SEO e a credibilidade profissional. Quando alguém visita seu link, já sabe qual é sua área de atuação antes mesmo de abrir a página.
### 2. Personalização Visual
**LinkTree:**
- Plano gratuito: Temas básicos limitados
- Planos pagos: Mais opções de personalização
- Temas pré-definidos
**BCards:**
- Plano Básico: 5+ temas profissionais
- Plano Profissional: 10+ temas premium
- Plano Premium: Temas customizáveis + editor CSS
**Vantagem BCards:** Maior flexibilidade de personalização mesmo nos planos mais acessíveis, permitindo que sua página reflita sua identidade visual.
### 3. Quantidade de Links
**LinkTree:**
- Plano gratuito: Links ilimitados
- Destaque para 1 link por vez (feature paga)
**BCards:**
- Plano Básico (R$ 12,90): 5 links
- Plano Profissional (R$ 25,90): 15 links
- Plano Premium (R$ 29,90): Links ilimitados
**Empate:** LinkTree oferece links ilimitados gratuitamente, mas limita recursos de destaque. BCards oferece estrutura mais organizada com categorias, mas limita quantidade nos planos básicos.
### 4. Analytics e Métricas
**LinkTree:**
- Plano gratuito: Métricas básicas de cliques
- Planos pagos: Analytics avançado, integração com Google Analytics, rastreamento de conversões
**BCards:**
- Todos os planos: Contadores de cliques por link
- Dashboards com métricas de desempenho
- Relatórios de visitantes
**Empate:** Ambas as plataformas oferecem analytics suficientes para a maioria dos usuários. LinkTree tem vantagem em integrações avançadas, BCards oferece simplicidade e clareza nos dados.
### 5. Integrações
**LinkTree:**
- Integrações com diversas plataformas globais
- Facebook Pixel, Google Analytics, TikTok, Spotify, Apple Music
- Mais de 100 integrações
**BCards:**
- Integrações com principais plataformas brasileiras
- Google Analytics
- Redes sociais principais (Instagram, Facebook, WhatsApp)
- Foco em ferramentas relevantes para o mercado brasileiro
**Vantagem LinkTree:** Maior quantidade de integrações com plataformas internacionais, ideal para criadores de conteúdo global.
### 6. Sistema de Moderação e Qualidade
**LinkTree:**
- Moderação automatizada
- Políticas de uso global
**BCards:**
- Sistema de moderação humanizada
- Análise de conteúdo antes da ativação
- Garantia de qualidade das páginas públicas
**Vantagem BCards:** A moderação manual garante que todas as páginas ativas mantenham um padrão de qualidade, protegendo a reputação da plataforma e dos usuários.
## Comparação de Preços (Janeiro 2025)
### LinkTree
**Free (Gratuito):**
- Links ilimitados
- Temas básicos
- Métricas limitadas
- Branding LinkTree visível
**Starter (US$ 5/mês = ~R$ 25-30/mês*):**
- Remove branding
- Mais opções de temas
- Agendamento de links
- Analytics básico
**Pro (US$ 9/mês = ~R$ 45-55/mês*):**
- Analytics avançado
- Priorização de links
- Vídeos de fundo
- Integrações avançadas
**Premium (US$ 24/mês = ~R$ 120-145/mês*):**
- Todas as features Pro
- Suporte prioritário
- Features de e-commerce
- Mais integrações
*Valores aproximados sujeitos à variação cambial
### BCards
**Básico (R$ 12,90/mês):**
- 5 links organizados
- Temas básicos
- Analytics essenciais
- URL categorizada
- Suporte em português
**Profissional (R$ 25,90/mês):**
- 15 links organizados
- Todos os temas premium
- Analytics completo
- Suporte prioritário
**Premium (R$ 29,90/mês):**
- Links ilimitados
- Temas customizáveis
- Editor CSS avançado
- Analytics detalhado
- Suporte VIP
### Análise de Custo-Benefício
Para usuários brasileiros, o BCards oferece vantagens significativas:
1. **Sem variação cambial**: Preços fixos em reais
2. **Custo inicial menor**: R$ 12,90 vs ~R$ 25-30 (Starter do LinkTree)
3. **Plano Premium mais acessível**: R$ 29,90 vs ~R$ 120-145 (Premium do LinkTree)
4. **Suporte em português**: Sem barreira linguística
**Exemplo prático:**
Um advogado que precisa de uma página profissional com 10 links:
- **LinkTree Pro**: ~R$ 45-55/mês (R$ 540-660/ano)
- **BCards Profissional**: R$ 25,90/mês (R$ 310,80/ano)
- **Economia**: R$ 241-361/ano (44-55% de economia)
## Quando Escolher LinkTree?
O LinkTree pode ser a melhor opção se você:
1. **Precisa de visibilidade internacional**: Marca reconhecida globalmente
2. **Cria conteúdo em inglês**: Audiência internacional
3. **Necessita de integrações específicas**: Plataformas não populares no Brasil
4. **Quer começar gratuitamente**: Plano free com links ilimitados
5. **Trabalha com e-commerce global**: Integrações avançadas de vendas
## Quando Escolher BCards?
O BCards é ideal se você:
1. **Atua no mercado brasileiro**: Profissionais liberais, empresas locais
2. **Valoriza suporte em português**: Comunicação clara e rápida
3. **Busca melhor custo-benefício**: Preços competitivos em reais
4. **Quer URLs profissionais**: Estrutura categorizada melhora SEO
5. **Precisa de personalização**: Temas customizáveis por preço acessível
6. **Valoriza qualidade**: Sistema de moderação garante padrão elevado
## Casos de Uso Práticos
### Caso 1: Advogada Especializada em Direito de Família
**Escolha: BCards**
Maria Silva, advogada em São Paulo, escolheu BCards porque:
- URL profissional: `bcards.site/page/advocacia/maria-silva-direito-familia`
- Preço fixo em reais (sem surpresas)
- Suporte em português para ajustar sua página
- Sistema de moderação garante credibilidade profissional
**Resultado:** Aumento de 40% em consultas via página de bio nos primeiros 3 meses.
### Caso 2: Influenciador de Tecnologia Global
**Escolha: LinkTree**
João Tech, criador de conteúdo com audiência internacional, escolheu LinkTree porque:
- Marca reconhecida globalmente
- Integrações com Patreon, Ko-fi, e outras plataformas internacionais
- Audiência em múltiplos países
- Necessidade de features de e-commerce global
**Resultado:** Facilidade para monetização internacional e reconhecimento da marca LinkTree entre seguidores estrangeiros.
### Caso 3: Personal Trainer Local
**Escolha: BCards**
Ana Fitness, personal trainer em Belo Horizonte, escolheu BCards porque:
- Atende apenas clientes locais
- Precisava de página profissional sem custo alto
- URL categorizada: `bcards.site/page/saude/ana-personal-trainer-bh`
- Todos os clientes falam português
**Resultado:** Redução de 60% no custo mensal comparado ao LinkTree Pro, mantendo todas as funcionalidades necessárias.
## Fatores Técnicos: SEO e Performance
### SEO (Otimização para Motores de Busca)
**BCards vantagens:**
- URLs hierárquicas descritivas
- Estrutura de categorias melhora indexação
- Conteúdo em português nativo
- Menor concorrência em buscas locais
**LinkTree vantagens:**
- Autoridade de domínio global mais alta
- Maior reconhecimento de marca
- Backlinks naturais de usuários internacionais
### Performance e Velocidade
Ambas as plataformas oferecem:
- Carregamento rápido (< 2 segundos)
- Responsividade mobile
- Uptime confiável (99%+)
## Suporte ao Cliente
### LinkTree
- Suporte em inglês
- Base de conhecimento extensa
- Comunidade global ativa
- Suporte prioritário apenas em planos premium
### BCards
- Suporte em português
- Atendimento personalizado
- Suporte prioritário desde plano Profissional
- Compreensão do contexto local brasileiro
**Vantagem BCards:** Para usuários que não dominam inglês ou preferem suporte local, o BCards oferece experiência significativamente melhor.
## Segurança e Privacidade
### LinkTree
- Certificado SSL
- Conformidade com GDPR (Europa)
- Políticas de privacidade internacionais
### BCards
- Certificado SSL
- Políticas alinhadas com LGPD (Brasil)
- Dados hospedados no Brasil
**Vantagem BCards:** Para empresas que precisam estar em conformidade com LGPD, ter dados hospedados no Brasil pode ser uma vantagem regulatória.
## Limitações de Cada Plataforma
### LinkTree Limitações
- Custos em dólar (variação cambial)
- Suporte não é em português
- Alguns recursos avançados são muito caros
- Foco global pode não atender necessidades locais
### BCards Limitações
- Menor reconhecimento internacional
- Menos integrações com plataformas globais
- Comunidade menor (plataforma mais nova)
- Não tem plano gratuito com links ilimitados
## Tabela Comparativa Resumida
| Característica | LinkTree | BCards |
|---------------|----------|---------|
| **Preço inicial** | Gratuito (limitado) | R$ 12,90/mês |
| **Plano Pro** | ~R$ 45-55/mês | R$ 25,90/mês |
| **Plano Premium** | ~R$ 120-145/mês | R$ 29,90/mês |
| **Moeda** | Dólar (USD) | Real (BRL) |
| **Links ilimitados** | Grátis | R$ 29,90/mês |
| **Suporte** | Inglês | Português |
| **URL** | linktr.ee/usuario | bcards.site/page/categoria/usuario |
| **Moderação** | Automatizada | Humanizada |
| **Foco** | Global | Brasil/América Latina |
| **Integrações** | 100+ | Principais |
| **Personalização** | Boa (paga) | Excelente (todos planos) |
| **SEO** | Autoridade global | URLs categorizadas |
## Migração: Como Trocar do LinkTree para BCards
Se você já usa LinkTree e está considerando migrar para BCards, o processo é simples:
1. **Exporte seus links**: Copie títulos e URLs
2. **Crie conta no BCards**: Escolha sua categoria profissional
3. **Configure sua página**: Adicione links, escolha tema
4. **Submeta para moderação**: Aguarde aprovação (24-48h)
5. **Atualize suas redes sociais**: Troque o link da bio
**Dica:** Mantenha ambas as páginas ativas durante 1-2 semanas de transição para garantir que todos os seguidores vejam o novo link.
## Conclusão: Qual Escolher?
Não existe resposta única. A melhor escolha depende do seu contexto:
**Escolha LinkTree se:**
- Você tem audiência internacional significativa
- Cria conteúdo em inglês
- Necessita de integrações específicas globais
- Quer começar gratuitamente (com limitações)
- Reconhecimento de marca internacional é importante
**Escolha BCards se:**
- Você atua principalmente no Brasil
- Valoriza suporte em português
- Busca melhor custo-benefício
- Quer URL profissional categorizada
- Prefere moderação humanizada
- Deseja evitar variação cambial
Para a maioria dos **profissionais brasileiros, pequenas empresas e criadores de conteúdo local**, o **BCards oferece melhor custo-benefício, suporte mais personalizado e funcionalidades adequadas às necessidades do mercado nacional**.
Para **criadores de conteúdo global, influenciadores internacionais e empresas que atuam em múltiplos países**, o **LinkTree pode ser a escolha mais adequada** devido ao reconhecimento internacional da marca.
## Próximos Passos
Pronto para criar sua página de links profissional?
1. **Defina suas necessidades**: Quantos links? Qual seu público?
2. **Avalie seu orçamento**: Preço fixo ou variável?
3. **Considere suporte**: Português ou inglês?
4. **Teste a plataforma**: Crie uma página e veja qual interface prefere
5. **Decida e comece**: Ambas são boas opções, escolha a melhor para você
**Experimente BCards gratuitamente** - [Criar conta agora](https://bcards.site/)
---
**Última atualização:** Janeiro 2025
*Este artigo é baseado em informações públicas das plataformas e nossa análise independente. Os preços podem variar. Consulte os sites oficiais para informações atualizadas.*
*Disclaimer: Somos a equipe BCards, mas nos esforçamos para apresentar uma comparação honesta e imparcial. Apresentamos vantagens e limitações de ambas as plataformas para ajudá-lo a tomar a melhor decisão para seu caso específico.*

View File

@ -1,339 +0,0 @@
---
title: "Transformação Digital para Pequenos Negócios: Comece Hoje Mesmo"
description: "Guia prático sobre como pequenos negócios podem iniciar sua jornada de transformação digital sem grandes investimentos. Aprenda estratégias simples e eficazes."
keywords: "transformação digital, pequenos negócios, digitalização, presença online, marketing digital"
author: "Equipe BCards"
date: 2025-01-10
lastMod: 2025-01-10
image: "/images/artigos/transformacao-digital.jpg"
culture: "pt-BR"
---
# Transformação Digital para Pequenos Negócios: Comece Hoje Mesmo
A transformação digital não é mais um luxo reservado apenas para grandes empresas. Hoje, pequenos negócios podem (e devem) aproveitar as ferramentas digitais para crescer, alcançar novos clientes e competir no mercado moderno.
Neste artigo, você vai descobrir como iniciar sua jornada digital com investimento mínimo e resultados máximos.
## O Que É Transformação Digital?
Transformação digital é o processo de integrar tecnologias digitais em todas as áreas do seu negócio, mudando fundamentalmente como você opera e entrega valor aos clientes.
Para pequenos negócios, isso não significa gastar milhões em sistemas complexos. Significa:
- Ter presença online profissional
- Facilitar o contato com clientes
- Organizar informações de forma acessível
- Automatizar processos simples
- Usar dados para tomar decisões melhores
## Por Que Pequenos Negócios Precisam se Digitalizar?
### 1. Seus Clientes Estão Online
Mais de 70% dos brasileiros usam internet diariamente. Quando precisam de um produto ou serviço, a primeira ação é pesquisar online. Se você não está lá, está invisível.
### 2. Competitividade
Seus concorrentes já estão se digitalizando. Ficar de fora significa perder espaço no mercado.
### 3. Redução de Custos
Ferramentas digitais frequentemente custam menos que métodos tradicionais e alcançam mais pessoas.
### 4. Melhor Experiência do Cliente
Clientes valorizam conveniência: encontrar informações rapidamente, entrar em contato facilmente, agendar serviços online.
## Os 5 Pilares da Transformação Digital para Pequenos Negócios
### 1. Presença Online Profissional
**Problema:** Você não tem site, ou seu site está desatualizado.
**Solução Simples:**
- Crie uma página profissional de links (bio link)
- Organize todos seus canais em um único lugar
- Facilite o acesso a WhatsApp, Instagram, serviços
**Custo:** A partir de R$ 12,90/mês
**Resultado:** Clientes encontram suas informações facilmente, você parece mais profissional.
### 2. Relacionamento com Clientes
**Problema:** Dificuldade em manter contato constante com clientes.
**Solução Simples:**
- Use WhatsApp Business (gratuito)
- Organize contatos com etiquetas
- Configure mensagens automáticas
- Crie catálogo de produtos
**Custo:** Gratuito
**Resultado:** Comunicação mais profissional e organizada.
### 3. Divulgação Estratégica
**Problema:** Propaganda cara e ineficiente.
**Solução Simples:**
- Crie perfis profissionais em redes sociais
- Poste conteúdo relevante regularmente
- Use Instagram e Facebook Ads (começando com R$ 5/dia)
- Peça avaliações no Google Meu Negócio
**Custo:** Pode começar com R$ 0 (orgânico) ou R$ 150/mês (anúncios básicos)
**Resultado:** Mais visibilidade, novos clientes, crescimento constante.
### 4. Gestão e Organização
**Problema:** Controle manual de vendas, estoque, finanças.
**Solução Simples:**
- Use planilhas Google (gratuito)
- Adote um sistema de gestão simples (muitos têm versão gratuita)
- Organize documentos na nuvem (Google Drive/Dropbox)
**Custo:** Gratuito a R$ 30/mês
**Resultado:** Menos tempo perdido, mais controle, decisões baseadas em dados.
### 5. Pagamentos Digitais
**Problema:** Perder vendas por aceitar apenas dinheiro.
**Solução Simples:**
- Aceite Pix (gratuito)
- Use maquininha de cartão mobile
- Crie links de pagamento online
**Custo:** Taxas apenas sobre vendas (2-5%)
**Resultado:** Venda mais, facilite a vida dos clientes, profissionalize seu negócio.
## Passo a Passo: Comece Sua Transformação Digital Hoje
### Semana 1: Presença Online Básica
**Dia 1-2: Configure WhatsApp Business**
- Baixe o app WhatsApp Business
- Configure perfil profissional com horário de atendimento
- Crie mensagens automáticas
- Adicione catálogo de produtos/serviços
**Dia 3-4: Crie Perfis Profissionais**
- Instagram Business
- Facebook Page
- Google Meu Negócio
**Dia 5-7: Organize Seus Links**
- Crie página profissional de links (ex: BCards)
- Adicione todos os canais de contato
- Coloque o link na bio de todas as redes sociais
### Semana 2: Conteúdo e Engajamento
**Dias 8-10: Planeje Conteúdo**
- Defina 3 tipos de conteúdo para postar
- Exemplos: Dicas, bastidores, promoções
- Crie calendário simples (3 posts por semana)
**Dias 11-14: Comece a Postar**
- Publique primeiro conteúdo
- Responda todos os comentários
- Peça para amigos e clientes seguirem
### Semana 3: Organização Interna
**Dias 15-17: Digitalize Processos**
- Crie planilha de vendas no Google Sheets
- Liste todos os clientes em planilha de contatos
- Configure backup de fotos importantes na nuvem
**Dias 18-21: Configure Pagamentos**
- Cadastre Pix
- Avalie opções de maquininha mobile
- Crie links de pagamento
### Semana 4: Primeiras Campanhas
**Dias 22-25: Marketing Digital Básico**
- Poste sobre promoção especial
- Impulsione post no Instagram/Facebook (R$ 20)
- Monitore resultados
**Dias 26-30: Analise e Ajuste**
- Veja quais posts tiveram mais engajamento
- Identifique de onde vieram novos clientes
- Planeje próximo mês
## Ferramentas Essenciais (Maioria Gratuitas)
### Comunicação
- **WhatsApp Business**: Grátis
- **Google Workspace** (e-mail profissional): R$ 12/usuário/mês
### Organização
- **Google Drive**: 15GB grátis
- **Trello** (gestão de tarefas): Grátis
- **Google Sheets**: Grátis
### Presença Online
- **BCards** (página de links): A partir de R$ 12,90/mês
- **Instagram/Facebook**: Grátis
- **Google Meu Negócio**: Grátis
### Marketing
- **Canva** (design): Plano gratuito robusto
- **Meta Business Suite**: Grátis
- **Google Analytics**: Grátis
### Pagamentos
- **Pix**: Grátis
- **Mercado Pago**: Taxas sobre vendas
- **PagSeguro**: Taxas sobre vendas
**Investimento total inicial:** R$ 0 a R$ 50/mês
## Erros Comuns a Evitar
### 1. Tentar Fazer Tudo de Uma Vez
**Erro:** Criar 10 perfis, começar blog, loja virtual, tudo junto.
**Solução:** Comece com o básico, domine, depois expanda.
### 2. Não Ter Consistência
**Erro:** Postar muito uma semana, depois sumir por meses.
**Solução:** Defina frequência realista e mantenha.
### 3. Ignorar Clientes Online
**Erro:** Não responder comentários e mensagens rapidamente.
**Solução:** Defina horários para checar e responder (3x ao dia).
### 4. Não Mensurar Resultados
**Erro:** Investir sem saber o que funciona.
**Solução:** Acompanhe métricas básicas (seguidores, curtidas, vendas).
### 5. Copiar Concorrentes sem Personalidade
**Erro:** Ser uma cópia genérica.
**Solução:** Mostre sua personalidade, conte sua história única.
## Casos de Sucesso Reais
### Caso 1: Doceria da Dona Maria
**Antes:**
- Vendia apenas para vizinhos
- Divulgação boca a boca
- Faturamento: R$ 2.000/mês
**Ações:**
- Criou Instagram com fotos profissionais dos doces
- Configurou WhatsApp Business com catálogo
- Começou a aceitar encomendas por mensagem
- Criou página de links para facilitar contato
**Depois (6 meses):**
- Alcance em 3 bairros diferentes
- 1.200 seguidores no Instagram
- Faturamento: R$ 6.500/mês (+225%)
**Investimento:** R$ 30/mês (internet + ferramentas)
### Caso 2: Oficina do João
**Antes:**
- Clientes apenas por indicação
- Sem presença online
- Dificuldade em mostrar serviços
**Ações:**
- Criou Google Meu Negócio
- Pediu avaliações dos clientes satisfeitos
- Criou página de links com serviços e preços
- Postou fotos de antes/depois dos carros
**Depois (4 meses):**
- 5-7 novos clientes por mês via Google
- 4.8 estrelas no Google (15 avaliações)
- Aumento de 40% no faturamento
**Investimento:** R$ 0 (todas ferramentas gratuitas)
## Próximos Passos Após os Primeiros 30 Dias
### Mês 2-3: Consolidação
- Refine processos que funcionaram
- Descarte o que não deu resultado
- Aumente frequência de posts
- Comece a investir pequenas quantias em anúncios
### Mês 4-6: Expansão
- Considere criar site próprio
- Expanda para novas plataformas (TikTok, LinkedIn)
- Crie programa de fidelidade digital
- Automatize mais processos
### Mês 7-12: Maturidade
- Analise dados para decisões estratégicas
- Invista em ferramentas mais robustas
- Contrate especialistas para áreas específicas
- Expanda equipe digital se necessário
## Checklist da Transformação Digital
Use esta checklist para acompanhar seu progresso:
**Fundamentos (Primeiras 2 semanas):**
- [ ] WhatsApp Business configurado
- [ ] Instagram Business criado
- [ ] Facebook Page ativa
- [ ] Google Meu Negócio cadastrado
- [ ] Página de links profissional criada
- [ ] Todos os links atualizados nas redes sociais
**Conteúdo (Primeiro mês):**
- [ ] Calendário de conteúdo criado
- [ ] Pelo menos 12 posts publicados
- [ ] Todas as mensagens respondidas em até 24h
- [ ] Primeiras avaliações recebidas
**Organização (Primeiros 2 meses):**
- [ ] Planilha de vendas funcionando
- [ ] Backup de arquivos na nuvem
- [ ] Processos principais documentados
- [ ] Sistema de pagamento digital ativo
**Marketing (Primeiros 3 meses):**
- [ ] Pelo menos 1 campanha paga testada
- [ ] Análise de métricas semanalmente
- [ ] Estratégia de conteúdo refinada
- [ ] Base de clientes digitais crescendo
## Conclusão: O Momento É Agora
A transformação digital não é sobre tecnologia complicada ou investimentos massivos. É sobre adaptar seu negócio para o mundo moderno, onde clientes esperam:
- **Encontrar você facilmente online**
- **Entrar em contato rapidamente**
- **Ver seu trabalho/produtos**
- **Fazer negócio de forma conveniente**
Você não precisa fazer tudo perfeitamente desde o início. Precisa começar.
Cada pequeno passo digital é um passo na direção certa:
- Primeira postagem no Instagram
- Primeiro cliente que te encontrou online
- Primeira venda via mensagem
- Primeira avaliação positiva
Esses pequenos passos se acumulam. Em 6 meses, você olhará para trás e ficará surpreso com a transformação.
**Comece hoje. Seu futuro digital está a um clique de distância.**
---
**Sobre o BCards:** Ajudamos pequenos negócios e profissionais a terem presença online profissional de forma simples e acessível. Crie sua página de links agora mesmo.
[Começar minha transformação digital →](https://bcards.site/)

View File

@ -1,269 +0,0 @@
---
title: "BCards para Escritórios de Advocacia: Gerenciando Múltiplos Sócios e Áreas"
description: "Como escritórios de advocacia com múltiplos sócios podem usar o BCards para organizar a presença digital de cada advogado e das diferentes áreas de atuação."
keywords: "escritório advocacia, sócios advogados, marketing jurídico, gestão digital escritório, OAB digital"
author: "Equipe BCards"
date: 2026-01-20
lastMod: 2026-01-20
image: "/images/tutoriais/escritorio-advocacia.jpg"
culture: "pt-BR"
category: "advocacia"
---
# BCards para Escritórios de Advocacia: Gerenciando Múltiplos Sócios e Áreas
Escritórios com mais de um advogado enfrentam um desafio específico: como centralizar a presença digital sem perder a identidade individual de cada sócio? O BCards resolve isso de forma elegante com o suporte a múltiplas páginas por conta.
## O Problema dos Escritórios Tradicionais
Cenário comum:
- O sócio João tem o LinkedIn desatualizado
- A sócia Maria usa o Instagram pessoal como contato profissional
- O escritório tem um site, mas ninguém lembra a URL para falar rápido
- Cada advogado associado indica um WhatsApp diferente
- Novos clientes ficam confusos sobre quem contatar
**Resultado:** clientes em potencial desistem antes de falar com qualquer sócio.
## A Estratégia com BCards para Escritórios
### Estrutura Recomendada
Um escritório bem organizado no BCards pode ter:
```
bcards.site/advocacia/escritorio-santos-silva ← Página geral do escritório
bcards.site/advocacia/dr-jose-santos-empresarial ← Sócio 1 (Direito Empresarial)
bcards.site/advocacia/dra-ana-silva-trabalhista ← Sócia 2 (Direito Trabalhista)
bcards.site/advocacia/dr-pedro-costa-familia ← Associado (Família e Sucessões)
```
Cada página tem propósito diferente:
| Página | Público | Links Principais |
|---|---|---|
| Escritório geral | Clientes sem área definida | Site, WhatsApp geral, email geral, localização |
| Sócio empresarial | Empresas e contratos | LinkedIn, email direto, agenda online |
| Sócia trabalhista | Empregados e empregadores | WhatsApp, email, artigos publicados |
| Associado família | Pessoas físicas | WhatsApp, email, localização |
---
## Configurando a Página Principal do Escritório
### Informações Básicas
**Nome da Página:**
```
Santos & Silva Advogados
```
**Slug sugerido:**
```
santos-silva-advogados
```
**Descrição (respeitando a OAB):**
```
Escritório de advocacia especializado em Direito Empresarial,
Trabalhista e de Família. Atendimento presencial e online.
São Paulo/SP | OAB/SP - Registro Ativo
```
### Links Essenciais da Página do Escritório
1. **📍 Localização** — Google Maps do escritório
2. **📞 WhatsApp de Triagem** — Para primeiro contato e direcionamento
3. **✉️ Email de Contato** — contato@santossilva.adv.br
4. **🌐 Site do Escritório** — www.santossilva.adv.br
5. **💼 Área Empresarial** — Link para a página do Dr. Santos
6. **👷 Área Trabalhista** — Link para a página da Dra. Silva
7. **👨‍👩‍👧 Área de Família** — Link para a página do Dr. Costa
8. **📅 Agendar Consulta** — Link do sistema de agenda
> **Dica OAB:** Usar links para páginas de sócios específicos é uma forma elegante de direcionar o cliente para a área correta sem fazer promessas ou comparações.
---
## Configurando a Página Individual de Cada Sócio
Cada sócio deve ter sua própria página com foco na especialização.
### Exemplo: Sócio de Direito Empresarial
**Slug:** `dr-jose-santos-empresarial`
**Descrição:**
```
Advogado | Direito Empresarial e Contratos
OAB/SP 123.456
Especialista em M&A, Contratos Comerciais e Recuperação Judicial
Mestre em Direito Empresarial — FGV Direito SP
```
**Links recomendados:**
1. 💼 LinkedIn (perfil profissional atualizado)
2. 📧 Email direto do sócio
3. 📅 Calendly para agendamento
4. 📄 Artigos publicados (JusBrasil, Migalhas)
5. 🌐 Página do Escritório (link de volta)
6. 📍 Localização do escritório
---
### Exemplo: Sócia de Direito Trabalhista
**Slug:** `dra-ana-silva-trabalhista`
**Descrição:**
```
Advogada Trabalhista | OAB/SP 234.567
Representação de Empregados e Empregadores
Pós-graduada em Direito do Trabalho — PUC-SP
Atendimento presencial e online em todo o Brasil
```
**Links recomendados:**
1. 📱 WhatsApp Business (trabalhista)
2. 📧 Email direto
3. 📸 Instagram jurídico educativo
4. 📄 Canal YouTube (dicas trabalhistas)
5. 🌐 Página do Escritório
6. 📅 Agendar Consulta Inicial
---
## Como Coordenar a Comunicação Visual
Mesmo com páginas individuais, é possível manter identidade visual consistente:
### Temas Coordenados (Plano Premium)
- **Escritório geral:** Tema Profissional (azul marinho)
- **Cada sócio:** Mesmo tema, cores ligeiramente personalizadas
- **Associados:** Tema Clássico (mais neutro)
### Fotos de Perfil Padronizadas
Invista em um ensaio fotográfico único para todos os advogados:
- Mesmo estilo de foto (fundo neutro, vestimenta formal)
- Mesmo enquadramento
- Mesma paleta de cores no fundo
Resultado: aspecto de equipe coesa em todas as páginas.
---
## Integrando o BCards com a Estratégia Digital do Escritório
### 1. Cartão de Visita Digital
No verso do cartão físico impresso, inclua o QR Code do BCards:
```
QR Code → bcards.site/advocacia/dr-jose-santos-empresarial
```
Quando o cliente escanear, tem todos os contatos na palma da mão.
### 2. Assinatura de Email
```
Dr. José Santos | Santos & Silva Advogados
OAB/SP 123.456 | Direito Empresarial
📱 (11) 99999-9999
🔗 bcards.site/advocacia/dr-jose-santos-empresarial
```
### 3. Bio em Redes Sociais (LinkedIn)
```
Advogado Empresarial | OAB/SP 123.456
Santos & Silva Advogados
M&A | Contratos | Recuperação Judicial
🔗 Todos os contatos e publicações:
bcards.site/advocacia/dr-jose-santos-empresarial
```
### 4. Eventos e Congressos
Ao participar de eventos jurídicos, compartilhe seu BCards:
- Inclua o link no material de apresentação
- Coloque no crachá (QR Code impresso)
- Compartilhe no grupo do evento
---
## Gerenciando o Plano Premium para Escritórios
Com o **Plano Premium** (R$ 29,90/mês), cada advogado pode ter **até 15 páginas**.
**Estratégia para escritórios em crescimento:**
Um único sócio com conta Premium pode criar:
- 1 página geral do escritório
- 1 página por especialização
- 1 página para cada filial ou cidade de atuação
- 1 página para eventos ou projetos especiais
**Exemplo de uso completo:**
```
santos-silva-advogados ← Escritório SP
santos-silva-advogados-rj ← Escritório RJ (se houver)
dr-jose-santos-empresarial ← Sócio principal
dr-jose-santos-ingles ← Página em inglês para clientes internacionais
santos-silva-contratos ← Especialização em contratos
santos-silva-ma ← Especialização em M&A
```
---
## Checklist para Escritórios
**Configuração inicial:**
- [ ] Criar conta com email profissional do escritório
- [ ] Definir slug padrão para o escritório
- [ ] Criar página geral do escritório
- [ ] Criar página individual para cada sócio
- [ ] Alinhar temas visuais entre as páginas
- [ ] Padronizar fotos de perfil
- [ ] Testar todos os links antes de publicar
- [ ] Submeter todas as páginas para moderação
**Comunicação:**
- [ ] Atualizar bio do LinkedIn de cada sócio
- [ ] Atualizar assinatura de email
- [ ] Imprimir QR Code para cartões de visita
- [ ] Atualizar bio do Instagram do escritório (se houver)
**Manutenção mensal:**
- [ ] Verificar se todos os links continuam funcionando
- [ ] Atualizar publicações e artigos recentes
- [ ] Revisar dados de contato (mudança de celular, email)
- [ ] Analisar métricas: quais links mais clicados
---
## Conclusão
O BCards permite que escritórios de advocacia profissionalizem sua presença digital com uma estrutura clara: uma página geral como porta de entrada e páginas individuais para cada sócio, organizadas por especialização.
**Benefícios para o escritório:**
- ✅ Clientes chegam ao advogado certo mais rapidamente
- ✅ Identidade visual consistente em toda equipe
- ✅ Facilita o trabalho em rede (networking entre áreas)
- ✅ Atualização simples sem precisar de desenvolvedor
- ✅ Analytics por sócio para medir performance individual
**Lembre-se:** Todas as páginas devem respeitar o Provimento OAB n° 205/2021 sobre publicidade na advocacia.
[Criar sua página agora →](https://bcards.site/)
---
**Referências:**
- Provimento OAB n° 205/2021
- Código de Ética e Disciplina da OAB
**Última atualização:** Janeiro 2026

View File

@ -1,435 +0,0 @@
---
title: "BCards para Advogados: Guia Completo de Marketing Digital Ético"
description: "Como advogados podem usar o BCards de forma profissional e ética, respeitando as normas da OAB sobre publicidade jurídica digital."
keywords: "advogados, marketing jurídico, publicidade advocacia, OAB, marketing digital advogados"
author: "Equipe BCards"
date: 2025-01-14
lastMod: 2025-01-14
image: "/images/tutoriais/advogados-bcards.jpg"
culture: "pt-BR"
category: "advocacia"
---
# BCards para Advogados: Guia Completo de Marketing Digital Ético
O marketing digital se tornou essencial para advogados que desejam expandir sua base de clientes. No entanto, a advocacia possui regras específicas estabelecidas pela OAB sobre publicidade.
Neste guia, você aprenderá como usar o BCards de forma profissional, ética e em conformidade com o Código de Ética da OAB.
## Por Que Advogados Precisam de Presença Digital?
### Seus Clientes Pesquisam Online
Quando alguém precisa de um advogado:
1. Pesquisa no Google
2. Pede indicações em redes sociais
3. Verifica perfis e avaliações online
Se você não está online, está invisível para potenciais clientes.
### Credibilidade Profissional
Uma presença digital organizada transmite:
- Profissionalismo
- Confiabilidade
- Acessibilidade
- Modernidade
### Concorrência
Muitos advogados já estão online. Ficar de fora significa perder espaço no mercado.
## Regras da OAB Sobre Publicidade Digital
Antes de criar sua página, é fundamental conhecer as regras do **Provimento n° 205/2021** da OAB:
### ✅ Permitido:
- Informar sobre áreas de atuação
- Divulgar títulos e especializações
- Publicar conteúdo educativo
- Compartilhar experiência profissional
- Indicar formas de contato
### ❌ Proibido:
- Garantir resultados
- Captação de clientela (mercantilização)
- Publicidade agressiva ou sensacionalista
- Promessas enganosas
- Orçamento sem análise do caso
- Comparações depreciativas com colegas
**Princípio fundamental:** Discrição, sobriedade e informação precisa.
## Como Configurar Seu BCards Profissional
### 1. Informações Básicas
#### Nome da Página
Use seu nome profissional completo seguido de "Advogado(a)" ou sua especialização:
**Exemplos adequados:**
- Dra. Maria Silva - Advogada
- João Santos | Direito Empresarial
- Ana Costa - Advocacia Trabalhista
**Evite:**
- "O Melhor Advogado"
- "Ganhe Sua Causa Garantido"
- "Advogado Nota 10"
#### Slug (URL)
Crie uma URL profissional:
**Bons exemplos:**
- `bcards.site/page/advocacia/maria-silva-advogada`
- `bcards.site/page/advocacia/joao-santos-empresarial`
- `bcards.site/page/advocacia/dra-ana-costa`
#### Descrição
Seja claro, objetivo e profissional:
**Exemplo adequado:**
```
Advogada especializada em Direito de Família e Sucessões.
OAB/SP 123.456 | Mestre em Direito Civil pela USP.
Atendimento presencial e online.
```
**Evite:**
```
A melhor advogada! Ganho 99% dos casos!
Atendo urgências a qualquer hora!
```
### 2. Links Profissionais
#### Links Essenciais para Advogados
**1. WhatsApp Business**
```
Título: Agendar Consulta
URL: https://wa.me/5511999999999?text=Olá, gostaria de agendar uma consulta
```
**2. Email Profissional**
```
Título: Contato por Email
URL: mailto:contato@seuescritorio.com.br
```
**3. Site ou Blog**
```
Título: Nosso Site
URL: https://seuescritorio.com.br
```
**4. LinkedIn**
```
Título: Perfil Profissional
URL: https://linkedin.com/in/seu-perfil
```
**5. Instagram Profissional**
```
Título: Instagram Jurídico
URL: https://instagram.com/seu.escritorio
```
**6. Artigos Jurídicos**
```
Título: Artigos e Publicações
URL: Link para seu blog ou Medium
```
**7. Google Meu Negócio**
```
Título: Avaliações e Localização
URL: Link do Google Maps do escritório
```
**8. Agendamento Online** (se usar)
```
Título: Agendar Horário
URL: Link do Calendly ou sistema de agenda
```
### 3. Escolha do Tema
Para advogados, recomendamos temas sóbrios e profissionais:
- **Clássico**: Fundo branco, clean
- **Profissional**: Tons de azul marinho ou cinza
- **Minimalista**: Ultra clean e objetivo
**Evite:**
- Cores muito vibrantes
- Animações excessivas
- Designs infantis ou descontraídos demais
### 4. Foto de Perfil
Use uma foto profissional:
- Fundo neutro
- Vestimenta formal
- Boa iluminação
- Expressão profissional mas acessível
**Evite:**
- Fotos de festas
- Selfies casuais
- Fotos em ambientes inadequados
## Conteúdo para Redes Sociais (Integrando com BCards)
Seu BCards será o hub central. Nas redes sociais, compartilhe:
### Conteúdo Educativo Permitido
**1. Dicas Jurídicas Gerais**
```
"Você sabia que tem até 2 anos para reclamar de vícios aparentes
no imóvel comprado? Entenda seus direitos!"
```
**2. Explicações de Leis**
```
"A Nova Lei de Licitações trouxe mudanças importantes.
Resumo das principais alterações: [thread]"
```
**3. Mitos e Verdades**
```
"Mito ou Verdade: É preciso registrar união estável em cartório?
MITO. Entenda por quê..."
```
**4. Casos Anônimos (com aprendizado)**
```
"Caso recente (sem identificação): Cliente conseguiu reaver valor
pago indevidamente em plano de saúde. Como? [explica processo genérico]"
```
### O Que NUNCA Postar
❌ "Acabei de ganhar mais uma causa! Somos imbatíveis!"
❌ "100% de sucesso em processos trabalhistas!"
❌ "Seu processo pode valer R$ 50.000! Entre em contato!"
❌ "Melhor advogado da cidade! Comprovado!"
❌ Fotos de audiências ou fóruns sem autorização
## Estratégia de Marketing Digital Ético
### 1. Marketing de Conteúdo
Crie conteúdo valioso:
- Artigos em blog
- Posts educativos em redes
- E-books sobre temas específicos
- Vídeos explicativos (YouTube, Instagram)
**Exemplo de calendário mensal:**
- Semana 1: Post sobre mudança legislativa
- Semana 2: Dica prática sobre direitos
- Semana 3: Explicação de conceito jurídico
- Semana 4: Resposta a dúvida comum
### 2. SEO Local para Advogados
Otimize para buscas locais:
- Google Meu Negócio completo
- URL no BCards com sua cidade (se relevante)
- Conteúdo mencionando região de atuação
**Exemplo:**
```
Advogada especializada em Direito de Família em São Paulo.
Atendimento na Zona Sul e Centro.
```
### 3. Peça Avaliações (com Cuidado)
É permitido ter avaliações no Google Meu Negócio, desde que:
- Sejam espontâneas (não solicite diretamente)
- Não há oferecimento de benefícios por avaliação
- Não são fabricadas
**Forma adequada:**
Após finalizar caso bem-sucedido, você pode:
```
"Ficamos felizes em ajudá-lo(a). Caso queira, sua opinião
sobre nosso atendimento é muito valiosa."
```
### 4. Networking Digital
Conecte-se com:
- Outros advogados (não concorrentes diretos)
- Profissionais complementares (contadores, corretores)
- Associações e entidades de classe
## Estrutura Completa: Exemplo Real
### Dra. Ana Costa - Direito Trabalhista
**URL BCards:**
`bcards.site/page/advocacia/dra-ana-costa-trabalhista`
**Descrição:**
```
Advogada Trabalhista | OAB/SP 234.567
Especialista em Direitos do Trabalhador
Mestre em Direito do Trabalho pela PUC-SP
Atendimento em São Paulo e online
```
**Links na página:**
1. 📱 WhatsApp: Agendar Consulta
2. 📧 Email: contato@anacosta.adv.br
3. 🌐 Site: www.anacosta.adv.br
4. 💼 LinkedIn: Perfil Profissional
5. 📸 Instagram: @dra.anacosta.trabalhista
6. 📄 Blog: Artigos sobre Direito Trabalhista
7. 📍 Localização: Escritório no Google Maps
8. 🕒 Agendar: Sistema de agendamento online
**Bio nas redes sociais:**
```
Dra. Ana Costa | Advogada Trabalhista 👩‍⚖️
OAB/SP 234.567
Defendendo direitos dos trabalhadores
📍 São Paulo | Atendimento presencial e online
🔗 Todos os contatos: [link BCards]
```
## Ferramentas Complementares
### 1. Gestão de Processos
- Projuris (jurídico)
- Astrea (gestão processual)
### 2. Agendamento
- Calendly
- Google Calendar
### 3. Comunicação
- WhatsApp Business
- Email profissional (Gmail Workspace)
### 4. Conteúdo
- Canva (design)
- Grammarly (revisão de texto)
### 5. Analytics
- Google Analytics (site)
- Meta Business Suite (redes sociais)
- BCards Analytics (cliques nos links)
## Monitoramento e Métricas
Acompanhe mensalmente:
**No BCards:**
- Número de visitantes
- Cliques em cada link
- Horários de maior acesso
**Nas Redes Sociais:**
- Crescimento de seguidores
- Engajamento (curtidas, comentários, compartilhamentos)
- Alcance de posts
**No Negócio:**
- Novos clientes vindos do online
- Taxa de conversão (visita → consulta)
- ROI (retorno sobre investimento em ads)
## Riscos e Como Evitá-los
### Risco 1: Infração Ética
**Como evitar:**
- Revise tudo antes de publicar
- Não garanta resultados
- Seja discreto e profissional
### Risco 2: Exposição Inadequada
**Como evitar:**
- Nunca divulgue detalhes de casos reais identificáveis
- Proteja o sigilo profissional sempre
- Peça autorização antes de mencionar qualquer caso
### Risco 3: Comentários Negativos
**Como evitar:**
- Responda sempre com profissionalismo
- Não discuta casos públicos
- Se necessário, leve discussão para privado
### Risco 4: Concorrência Desleal
**Como evitar:**
- Nunca critique colegas
- Foque em seu diferencial, não em defeitos alheios
- Colabore, não compita de forma negativa
## Checklist de Conformidade OAB
Antes de publicar sua página, verifique:
- [ ] Informações são verídicas?
- [ ] Títulos e especializações estão corretos?
- [ ] Não há promessas de resultado?
- [ ] Linguagem é sóbria e profissional?
- [ ] Não há captação mercantil de clientela?
- [ ] Fotos são profissionais?
- [ ] Links levam a conteúdo apropriado?
- [ ] OAB e número de inscrição estão visíveis?
- [ ] Especialização é reconhecida pela OAB (se mencionar)?
## Dúvidas Frequentes de Advogados
**P: Posso oferecer primeira consulta gratuita?**
R: Sim, desde que seja informação verdadeira e não caracterize captação irregular.
**P: Posso mencionar clientes famosos que atendi?**
R: Não, a menos que tenha autorização expressa e por escrito do cliente.
**P: Posso colocar preços no BCards?**
R: Não é recomendado. Orçamentos devem ser feitos após análise do caso específico.
**P: Posso fazer anúncios pagos no Google/Instagram?**
R: Sim, desde que o conteúdo respeite as normas éticas da OAB.
**P: Preciso colocar minha OAB no BCards?**
R: Sim, é obrigatório identificar número e seccional da OAB.
## Conclusão
O BCards é uma ferramenta poderosa para advogados modernizarem sua presença digital mantendo a ética profissional.
**Principais vantagens para advogados:**
- ✅ URL profissional e categorizada
- ✅ Centralização de todos os contatos
- ✅ Facilita marketing de conteúdo ético
- ✅ Transmite credibilidade
- ✅ Compatível com normas da OAB
- ✅ Analytics para medir resultados
**Lembre-se:**
- Priorize discrição e sobriedade
- Foque em educar, não em vender
- Seja transparente e honesto
- Respeite sempre as normas da OAB
Sua presença digital pode ser profissional, eficiente e ética ao mesmo tempo.
**Pronto para criar sua página profissional?**
[Criar meu BCards para Advocacia →](https://bcards.site/)
---
**Referências:**
- Provimento OAB n° 205/2021
- Código de Ética e Disciplina da OAB
**Disclaimer:** Este artigo tem caráter informativo. Para dúvidas específicas sobre ética profissional, consulte a OAB de sua seccional.
**Última atualização:** Janeiro 2025

View File

@ -1,364 +0,0 @@
---
title: "BCards para Freelancers de TI: Como Fechar Mais Projetos com uma Bio Profissional"
description: "Guia prático para desenvolvedores, designers e profissionais de TI que trabalham como freelancers usarem o BCards para atrair clientes e fechar mais projetos."
keywords: "freelancer TI, desenvolvedor freelance, bio links dev, portfolio desenvolvedor, freelancer tecnologia brasil"
author: "Equipe BCards"
date: 2026-01-22
lastMod: 2026-01-22
image: "/images/tutoriais/freelancer-ti.jpg"
culture: "pt-BR"
category: "tecnologia"
---
# BCards para Freelancers de TI: Como Fechar Mais Projetos com uma Bio Profissional
Você passa horas desenvolvendo projetos incríveis, mas quando um cliente em potencial pede "onde posso ver seu trabalho?", você envia um link do GitHub que a maioria não consegue interpretar, ou pior — uma pasta no Google Drive mal organizada.
O BCards resolve esse problema de forma elegante.
## Por Que Freelancers de TI Precisam de uma Bio Link
### O Momento Decisivo
Imagine o cenário: você está em um evento de networking, uma startup interessante pergunta sobre seu trabalho. Você tem 30 segundos. O que você envia?
**Sem BCards:**
```
github.com/seuusuario (só devs entendem)
behance.net/seuusuario (só designers)
linkedin.com/in/seuusuario (corporativo demais)
seusite.com (se existir, se estiver atualizado)
```
**Com BCards:**
```
bcards.site/tecnologia/joao-dev-fullstack
• Portfolio visual
• GitHub
• LinkedIn
• WhatsApp para proposta
• Calendly para call
• Email profissional
```
Tudo em uma URL que você memoriza e dita no telefone.
---
## O Que Colocar na Sua Página de Freelancer TI
### Informações Principais
**Nome e especialização:**
```
João Silva | Dev Full Stack React + Node.js
```
ou
```
Marina Costa — UX/UI Designer • Figma • Framer
```
ou
```
Pedro Alves • DevOps & Cloud AWS/GCP
```
**Slug recomendado:**
```
joao-silva-fullstack
marina-costa-ux
pedro-alves-devops
```
**Descrição (seja específico — clientes não contratam generalistas):**
✅ Bom exemplo:
```
Desenvolvedor Full Stack com 6 anos de experiência.
Especialidade: aplicações React + Node.js para SaaS e e-commerce.
Atendimento remoto para startups e empresas brasileiras.
Disponível para novos projetos em abril/2026.
```
❌ Evitar:
```
Desenvolvedor apaixonado por tecnologia e inovação
que busca novos desafios e oportunidades de crescimento.
```
---
## Os 8 Links Essenciais para Freelancers de TI
### 1. WhatsApp para Propostas
```
Título: 💬 Solicitar Orçamento
URL: https://wa.me/5511999999999?text=Olá João, vi seu perfil no BCards e gostaria de conversar sobre um projeto
```
> Pré-preencha a mensagem. Facilita o primeiro contato e já filtra leads sérios.
### 2. Portfólio Visual
```
Título: 🎨 Ver Portfólio
URL: https://seuportfolio.com
```
> Se não tiver site próprio, use: Behance, Read.cv, Notion público ou até um Figma compartilhado.
### 3. GitHub
```
Título: 💻 Repositórios GitHub
URL: https://github.com/seuusuario
```
> Só inclua se o perfil estiver apresentável: README atualizado, projetos fixados relevantes.
### 4. LinkedIn
```
Título: 💼 LinkedIn Profissional
URL: https://linkedin.com/in/seu-perfil
```
### 5. Agendamento de Call
```
Título: 📅 Agendar uma Call
URL: https://calendly.com/seuusuario/30min
```
> Use Calendly gratuito. Isso elimina a ladainha de "que hora você pode?".
### 6. Email Profissional
```
Título: ✉️ Enviar Email
URL: mailto:joao@seudominio.dev
```
> Invista em um domínio próprio. joao@gmail.com vs joao@jsilva.dev — a diferença de percepção é enorme.
### 7. Currículo ou Proposta Padrão (PDF)
```
Título: 📄 Baixar Currículo/Portfólio
URL: https://drive.google.com/...
```
> Tenha um PDF bem feito, de no máximo 2 páginas, sempre atualizado.
### 8. Depoimentos ou Case de Sucesso
```
Título: ⭐ Depoimentos de Clientes
URL: https://linkedin.com/in/seu-perfil#recommendations
```
> Recomendações do LinkedIn são a prova social mais confiável para B2B.
---
## Estratégias Avançadas para Fechar Mais Projetos
### 1. Mencione sua Disponibilidade
Clientes odeiam entrar em contato e descobrir que você está ocupado por 6 meses. Seja direto na descrição:
```
✅ Disponível para projetos a partir de maio/2026
🕐 Capacidade atual: 20h/semana
```
Atualize isso mensalmente. É um diferencial enorme.
### 2. Especialização > Generalismo
Um freelancer de "Python, Java, PHP, React, Angular, Vue, mobile, backend, frontend, dados e cloud" não passa confiança.
Escolha 1-2 tecnologias onde você é realmente forte e faça sua página comunicar isso:
**Para dev:**
```
Especialista em React + TypeScript para aplicações B2B
```
**Para designer:**
```
UX Design para produtos digitais B2C — do discovery ao handoff
```
**Para DevOps:**
```
Infraestrutura AWS para startups em fase de escala
```
### 3. Mostre Resultados, Não Tarefas
Na descrição e no portfólio, fale sobre impacto:
❌ "Desenvolvi uma aplicação de e-commerce"
✅ "Desenvolvi uma aplicação de e-commerce que aumentou a conversão do cliente em 32%"
❌ "Redesenhei o app de delivery"
✅ "Redesenhei o app de delivery — avaliação na App Store subiu de 3.1 para 4.6"
### 4. Link de "Como Trabalho Comigo"
Crie uma página Notion pública (gratuita) explicando seu processo de trabalho:
- Como você cobra
- Formas de pagamento aceitas
- Prazo médio de projetos
- Ferramentas que usa para comunicação
- O que você precisa do cliente para começar
Adicione como link no BCards:
```
Título: 📋 Como trabalho comigo
URL: https://notion.so/seu-processo
```
Isso filtra clientes problemáticos antes de qualquer call.
---
## Usando o Analytics do BCards Para Otimizar
Após sua página estar ativa, monitore no Dashboard:
### Métricas que Importam
**Taxa de clique por link:**
- Se o "Solicitar Orçamento" tem poucos cliques → sua descrição não está convencendo
- Se o "Portfolio" tem muitos cliques mas "WhatsApp" tem poucos → seu portfolio não está convertendo
**Volume de visitantes:**
- Picos após posts no LinkedIn = LinkedIn está trazendo resultado
- Picos após eventos = networking presencial funciona para você
### Ciclo de Otimização
```
1. Atualize a descrição (mais específica)
2. Aguarde 2 semanas
3. Compare métricas
4. Teste outra variação
5. Repita
```
---
## Configuração por Perfil de Freelancer
### Desenvolvedor Backend
```
Links prioritários:
1. GitHub (projetos relevantes pinados)
2. WhatsApp para proposta
3. LinkedIn
4. Calendly para call técnica
5. Artigos no Dev.to ou Medium
```
### Desenvolvedor Frontend / Full Stack
```
Links prioritários:
1. Portfolio visual (site próprio ou Behance)
2. WhatsApp para proposta
3. GitHub
4. LinkedIn
5. Calendly
```
### UX/UI Designer
```
Links prioritários:
1. Behance ou Dribbble (trabalhos visuais)
2. Figma Community (se tiver projetos compartilhados)
3. WhatsApp para proposta
4. LinkedIn
5. Calendly
```
### DevOps / Cloud
```
Links prioritários:
1. LinkedIn (certificações visíveis)
2. GitHub (scripts e automações)
3. Blog técnico (Medium, Hashnode, Dev.to)
4. Email profissional
5. Calendly para call técnica
```
### QA / Tester
```
Links prioritários:
1. LinkedIn
2. Portfolio de relatórios de bugs (Notion público)
3. GitHub (scripts de automação de testes)
4. WhatsApp ou Email
5. Calendly
```
---
## Erros Comuns de Freelancers de TI
### ❌ Slug genérico
`bcards.site/tecnologia/dev` — sem personalidade, difícil de lembrar
✅ Use seu nome + especialização: `joao-silva-fullstack`
### ❌ GitHub com projetos de faculdade
Projetos de "calculadora", "lista de tarefas" ou fork sem contribuições passam imagem de iniciante.
✅ Fixe seus 6 melhores projetos. Arquive o resto.
### ❌ Sem foto de perfil profissional
Freelancers sem foto parecem bot ou perfil falso.
✅ Qualquer foto com boa iluminação e fundo limpo serve. Não precisa ser ensaio fotográfico.
### ❌ Bio genérica com buzzwords
"Profissional apaixonado por inovação e tecnologia que busca desafios"
✅ Seja específico: linguagens, tipos de projeto, nível de experiência, disponibilidade.
### ❌ Não atualizar a disponibilidade
Nada pior que entrar em contato e descobrir que a pessoa está ocupada por 3 meses.
✅ Atualize todo mês: "Disponível a partir de [mês]"
---
## Checklist Final
**Antes de publicar:**
- [ ] Slug tem seu nome + especialização?
- [ ] Descrição menciona tecnologias específicas?
- [ ] Descrição menciona disponibilidade atual?
- [ ] Todos os links testados e funcionando?
- [ ] GitHub está apresentável (se incluído)?
- [ ] Foto de perfil nítida e profissional?
- [ ] WhatsApp ou email para contato incluído?
- [ ] Calendly ou outra forma de agendamento incluído?
**Após aprovação:**
- [ ] Bio do LinkedIn atualizada com link BCards?
- [ ] Bio do GitHub atualizada (`README.md` de perfil)?
- [ ] Instagram bio atualizada (se usar para trabalho)?
- [ ] Assinatura de email atualizada?
---
## Conclusão
Freelancers de TI que centralizam sua presença em um BCards profissional fecham projetos mais rápido porque eliminam a fricção do primeiro contato.
Em vez de mandar links espalhados, você manda uma URL só — limpa, profissional, memorável.
**Próximos passos:**
1. Crie sua conta no BCards
2. Configure sua página em 10 minutos
3. Atualize todas as suas bios com o novo link
4. Monitore os cliques e ajuste a copy
[Criar meu BCards de freelancer →](https://bcards.site/)
---
**Tempo de leitura:** 10 minutos
**Dificuldade:** Iniciante a Intermediário
**Última atualização:** Janeiro 2026

View File

@ -1,359 +0,0 @@
---
title: "Como Criar seu BCard em 5 Minutos: Tutorial Completo"
description: "Aprenda passo a passo como criar sua página profissional de links no BCards. Tutorial completo para iniciantes com capturas de tela e dicas práticas."
keywords: "tutorial bcards, criar página de links, bio link, tutorial passo a passo"
author: "Equipe BCards"
date: 2025-01-12
lastMod: 2025-01-12
image: "/images/tutoriais/criar-bcard.jpg"
culture: "pt-BR"
category: "tecnologia"
---
# Como Criar seu BCard em 5 Minutos: Tutorial Completo
Criar sua página profissional de links no BCards é mais fácil do que você imagina. Neste tutorial, vou te guiar passo a passo desde o cadastro até a publicação da sua página.
## O Que Você Vai Precisar
- Email válido
- Conta no Google ou Microsoft (para login rápido)
- 5 minutos do seu tempo
- Links que você quer compartilhar
## Passo 1: Criar Sua Conta
### 1.1. Acesse o Site
Vá para [bcards.site](https://bcards.site) e clique em **"Entrar"** no menu superior.
### 1.2. Escolha o Método de Login
Você tem duas opções:
**Opção A: Login com Google**
- Clique em "Entrar com Google"
- Selecione sua conta
- Autorize o acesso
**Opção B: Login com Microsoft**
- Clique em "Entrar com Microsoft"
- Insira suas credenciais
- Autorize o acesso
**Dica:** Usar login social é mais rápido e seguro (sem necessidade de criar nova senha).
## Passo 2: Acessar o Dashboard
Após fazer login, você será redirecionado para seu **Dashboard**. Este é o painel de controle onde você gerencia sua página.
No Dashboard você verá:
- Botão "Criar Minha Página" (se é sua primeira vez)
- Estatísticas (após criar a página)
- Links para editar sua página
## Passo 3: Criar Sua Página
### 3.1. Clique em "Criar Minha Página"
Você verá um formulário com vários campos. Vamos preencher cada um:
### 3.2. Informações Básicas
**Nome da Página:**
- Digite como você quer ser identificado
- Exemplo: "João Silva", "Maria Design", "Advocacia Santos"
- Este será o título principal da sua página
**Categoria:**
- Selecione a categoria que melhor representa sua atividade
- Exemplos disponíveis:
- Tecnologia
- Advocacia
- Saúde
- Educação
- Marketing
- E muitas outras...
**Dica:** A categoria fará parte da sua URL e ajuda no SEO.
**Slug (URL Personalizada):**
- Este será o final da sua URL
- Formato final: `bcards.site/page/{categoria}/{seu-slug}`
- Use apenas letras minúsculas, números e hifens
- Sem espaços, acentos ou caracteres especiais
**Exemplos:**
- `joao-silva-dev` → bcards.site/page/tecnologia/joao-silva-dev
- `maria-designer` → bcards.site/page/design/maria-designer
- `dra-ana-pediatra` → bcards.site/page/saude/dra-ana-pediatra
### 3.3. Descrição
Digite uma breve descrição sobre você ou seu negócio:
**Bom exemplo:**
```
Desenvolvedora Full Stack especializada em React e Node.js.
Ajudo empresas a criarem aplicações web modernas e escaláveis.
```
**Evite:**
```
Desenvolvedora
```
(muito curto, sem contexto)
**Dica:** Use 2-3 frases. Seja claro e direto.
### 3.4. Escolha do Tema Visual
Role até a seção "Tema" e selecione o visual da sua página:
**Temas disponíveis (variam por plano):**
- **Clássico**: Fundo branco limpo, ideal para profissionais
- **Dark**: Fundo escuro, moderno
- **Gradiente**: Cores vibrantes em degradê
- **Minimalista**: Ultra clean
- **Profissional**: Sóbrio e corporativo
Você pode trocar o tema depois a qualquer momento.
### 3.5. Adicionar Links
Agora vamos adicionar os links que aparecerão na sua página.
**Para adicionar um link:**
1. Clique em **"Adicionar Link"**
2. Preencha os campos:
- **Título**: Nome que aparecerá no botão
- **URL**: Endereço completo (começando com https://)
- **Ícone** (opcional): Escolha um ícone para o link
**Exemplo de Link:**
```
Título: Meu Portfólio
URL: https://meusite.com.br
Ícone: fa-briefcase
```
**Tipos de Links Comuns:**
- WhatsApp: `https://wa.me/5511999999999`
- Instagram: `https://instagram.com/seuusuario`
- Facebook: `https://facebook.com/suapagina`
- LinkedIn: `https://linkedin.com/in/seuusuario`
- YouTube: `https://youtube.com/@seucanal`
- Site: `https://seusite.com.br`
- Email: `mailto:seu@email.com`
**Dica:** Organize links por importância. Os primeiros aparecem no topo da página.
### 3.6. Reordenar Links
Você pode arrastar e soltar os links para mudar a ordem de exibição. Os links mais importantes devem ficar no topo.
## Passo 4: Personalização Avançada (Opcional)
### 4.1. Foto de Perfil
Upload de uma foto profissional (recomendado):
- Tamanho ideal: 400x400 pixels
- Formato: JPG ou PNG
- Peso: Até 2MB
### 4.2. Cores Personalizadas (Plano Premium)
Se você tem plano Premium, pode personalizar:
- Cor dos botões
- Cor do fundo
- Cor do texto
- Fontes
### 4.3. Redes Sociais
Adicione seus perfis sociais na seção específica. Eles aparecerão como ícones na sua página.
## Passo 5: Salvar e Submeter para Moderação
### 5.1. Revisar Tudo
Antes de salvar, revise:
- ✅ Nome está correto?
- ✅ URL (slug) está como você quer?
- ✅ Todos os links funcionam?
- ✅ Descrição está clara?
- ✅ Tema escolhido te agrada?
### 5.2. Salvar
Clique no botão **"Salvar Página"** no final do formulário.
### 5.3. Submeter para Moderação
Após salvar, você verá sua página em modo de **preview** (visualização).
Para torná-la pública, clique em **"Submeter para Moderação"**.
**O Que Acontece Agora?**
1. Nossa equipe revisa sua página (24-48 horas)
2. Verificamos se segue as diretrizes da comunidade
3. Você recebe email quando for aprovada
4. Sua página fica pública!
## Passo 6: Visualizar e Compartilhar
### 6.1. Token de Preview
Enquanto aguarda aprovação, você pode visualizar sua página usando o **token de preview**:
1. No Dashboard, clique em "Gerar Token de Preview"
2. Copie o link gerado
3. Abra em uma aba privada ou compartilhe com amigos
**Exemplo de link de preview:**
```
bcards.site/page/tecnologia/joao-dev?preview=ABC123XYZ
```
### 6.2. Após Aprovação
Quando sua página for aprovada:
- Ela estará acessível publicamente
- URL final: `bcards.site/page/{categoria}/{seu-slug}`
- Você pode compartilhar em suas redes sociais!
## Passo 7: Atualizar Bio das Redes Sociais
Com sua página aprovada, atualize a bio de todas as suas redes:
### Instagram
1. Vá em "Editar Perfil"
2. Cole seu link BCards no campo "Website"
3. Salve
### TikTok
1. Editar Perfil
2. Cole o link em "Bio"
3. Salve
### Twitter/X
1. Editar Perfil
2. Cole o link em "Website"
3. Salve
### LinkedIn
1. Editar Perfil
2. Cole na seção "Informações de Contato"
3. Salve
## Dicas de Boas Práticas
### ✅ Faça:
- Use foto profissional nítida
- Escreva descrição clara e objetiva
- Teste todos os links antes de publicar
- Mantenha a página atualizada
- Responda mensagens rapidamente
- Use call-to-action nos títulos dos links
### ❌ Evite:
- Links quebrados ou incorretos
- Descrição muito longa ou muito curta
- Excesso de links (foco no essencial)
- Informações enganosas
- Conteúdo que viola diretrizes
## Atualizando Sua Página Depois
Você pode editar sua página a qualquer momento:
1. Acesse o Dashboard
2. Clique em "Editar Página"
3. Faça as alterações
4. Salve
**Importante:** Mudanças significativas podem exigir nova moderação.
## Planos e Upgrades
### Plano Básico (R$ 12,90/mês)
- 5 links
- Temas básicos
- Analytics essenciais
### Plano Profissional (R$ 25,90/mês)
- 15 links
- Todos os temas
- Analytics completo
- Suporte prioritário
### Plano Premium (R$ 29,90/mês)
- Links ilimitados
- Customização total
- Temas exclusivos
- Editor CSS
- Suporte VIP
Para fazer upgrade:
1. Dashboard → "Meu Plano"
2. Escolha o plano desejado
3. Preencha dados de pagamento
4. Confirme
## Analisando Resultados
Após sua página estar ativa, acompanhe as métricas no Dashboard:
- **Total de visitantes**: Quantas pessoas acessaram
- **Cliques por link**: Quais links são mais populares
- **Origem do tráfico**: De onde vieram os visitantes
Use esses dados para otimizar sua página!
## Problemas Comuns e Soluções
### "Meu slug já está em uso"
**Solução:** Escolha outro slug. Tente adicionar seu nome, cidade ou especialidade.
### "Link não funciona"
**Solução:** Verifique se a URL começa com `https://` e está digitada corretamente.
### "Página foi rejeitada na moderação"
**Solução:** Leia o email com o motivo da rejeição, ajuste conforme orientações e resubmeta.
### "Não recebi email de aprovação"
**Solução:** Verifique spam/lixo eletrônico ou entre em contato com suporte.
## Suporte
Precisa de ajuda?
- **Email**: suporte@bcards.site
- **Horário**: Segunda a sexta, 9h às 18h
- **FAQ**: bcards.site/ajuda
- **WhatsApp**: (Link no site)
## Conclusão
Parabéns! 🎉 Agora você sabe criar sua página profissional no BCards do zero.
**Recapitulando:**
1. ✅ Criar conta
2. ✅ Preencher informações
3. ✅ Escolher tema
4. ✅ Adicionar links
5. ✅ Salvar e submeter
6. ✅ Aguardar aprovação
7. ✅ Compartilhar!
Sua presença online profissional está a apenas alguns cliques de distância.
**Pronto para começar?** [Criar meu BCard agora →](https://bcards.site/)
---
**Tempo de leitura:** 8 minutos
**Dificuldade:** Iniciante
**Última atualização:** Janeiro 2025

View File

@ -1,160 +0,0 @@
---
title: "Como a Tecnologia Está Transformando o Ministério Moderno"
description: "Reflexão sobre como pastores, padres e líderes religiosos podem usar ferramentas digitais para alcançar mais pessoas com a mensagem de fé, sem perder a essência do ministério."
keywords: "ministério digital, igreja online, pastor digital, tecnologia e fé, evangelismo digital"
author: "Equipe LuzLinks"
date: 2026-01-28
lastMod: 2026-01-28
image: "/images/artigos/ministerio-digital.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como a Tecnologia Está Transformando o Ministério Moderno
Há dez anos, a maioria das igrejas considerava ter um site "suficiente" para a presença digital. Hoje, a pergunta não é mais *se* um ministério precisa estar online, mas *como* estar de forma consistente, autêntica e eficaz.
A pandemia acelerou uma transformação que já estava em curso. Congregações que resistiam ao digital foram obrigadas a se adaptar — e muitas descobriram que o alcance das live transmissões superava o número de cadeiras da sede física.
---
## O Que Mudou: Da Presença Física à Presença Contínua
A Igreja sempre soube alcançar pessoas além dos muros do templo: missionários, rádio evangélico, televisão. A diferença agora é a **bidirecionalidade** e o **custo praticamente zero** de produção e distribuição.
Um pastor em uma cidade do interior do Piauí pode, hoje:
- Transmitir um culto para membros no exterior
- Publicar um estudo bíblico que é compartilhado por completos desconhecidos
- Responder perguntas de pessoas que nunca pisaram em sua igreja
- Construir uma comunidade de fé com pessoas que jamais se encontrarão presencialmente
Isso não substitui a comunhão presencial — que permanece insubstituível. Mas expande o alcance do ministério de formas inimagináveis há uma geração.
---
## Ferramentas Digitais e suas Vocações no Ministério
### YouTube: O Púlpito que Nunca Fecha
O YouTube se tornou o maior arquivo de pregações da história da humanidade. Uma mensagem publicada hoje pode ser assistida daqui a dez anos por alguém que ainda não chegou à fé.
**Usos estratégicos:**
- Pregações completas dos cultos
- Séries de estudos bíblicos organizados em playlists
- Devocional diário (formato curto, 5-10 minutos)
- Transmissão ao vivo de cultos especiais
> Uma série bem-estruturada no YouTube funciona como um "cartão de visita" permanente do ministério. Um novo convertido pode acompanhar meses de ensino antes de aparecer pela primeira vez presencialmente.
### Instagram: O Espaço da Comunidade Cotidiana
O Instagram não é para pregações longas. É para presença diária, humanização do líder e construção de comunidade.
**O que funciona no Instagram para ministérios:**
- Versículos do dia com design cuidado
- Bastidores da preparação do culto
- Histórias de transformação (com autorização)
- Reels curtos com reflexões de 60 segundos
- Stories de interação com a congregação
O Instagram permite que membros que não puderam comparecer ao culto se sintam parte da comunidade mesmo à distância.
### WhatsApp e Telegram: A Pastoral Digital
Esses aplicativos estão se tornando, para muitos pastores, a principal ferramenta de acompanhamento pastoral. Um grupo bem gerenciado pode:
- Enviar a agenda semanal automaticamente
- Compartilhar o link da live antes do culto
- Receber pedidos de oração em tempo real
- Coordenar equipes de ministério
- Manter membros em células conectados entre os encontros
**Atenção:** Grupos grandes demais perdem a qualidade de interação. A estratégia de múltiplos grupos menores (por célula, por faixa etária) é mais eficaz pastoralmente.
### Podcast: Ministério para o Trânsito e a Academia
O brasileiro passa em média 1h30 no trânsito por dia. O podcast transforma esse tempo em tempo de edificação.
Muitas pessoas que nunca ouviriam uma pregação completa no YouTube consomem regularmente episódios de podcast no caminho para o trabalho.
---
## Os Desafios do Ministério Digital
### A Armadilha da Performance
A lógica das redes sociais recompensa engajamento — curtidas, comentários, compartilhamentos. Há um risco real de que líderes comecem a moldar sua mensagem para maximizar métricas, não para edificar a congregação.
A mensagem do evangelho não é otimizável para o algoritmo. E não deveria ser.
**A pergunta certa não é:** "Como esse conteúdo vai performar?"
**A pergunta certa é:** "Esse conteúdo serve à minha congregação e ao chamado do ministério?"
### A Dispersão de Atenção
Estar em todas as plataformas ao mesmo tempo, com qualidade, é impossível para a maioria dos ministérios. A tentação de estar no YouTube, Instagram, TikTok, Twitter, Telegram e Podcast simultaneamente leva à superficialidade em todas.
**Estratégia recomendada:** Domine um canal antes de expandir. Para a maioria dos ministérios, YouTube + Instagram + WhatsApp é suficiente e sustentável.
### A Ilusão da Comunidade Online
Seguidores não são membros. Assistentes de live não são congregação. A profundidade de formação e de compromisso que acontece na comunidade presencial não pode ser replicada digitalmente.
A presença digital deve ser entendida como **portal de entrada**, não como substituto da vida comunitária presencial.
---
## O Papel da Bio Link no Ministério Digital
Um problema prático que surge com a multiplicação de plataformas: como uma pessoa que descobre seu ministério no Instagram encontra seu canal no YouTube? Como alguém que assistiu uma pregação no YouTube entra em contato?
Sem uma centralização clara, o esforço de presença digital se fragmenta.
É por isso que a bio link — uma página única com todos os contatos e plataformas do ministério — se tornou uma necessidade básica para líderes que levam a sério a presença digital.
Com uma boa página no LuzLinks, qualquer pessoa que encontra o ministério em qualquer plataforma chega ao mesmo lugar: uma porta de entrada organizada, com links para as lives, o grupo da comunidade, a agenda, e como contribuir com o ministério.
---
## Princípios para um Ministério Digital Saudável
### 1. Autenticidade Acima de Produção
Uma câmera de celular com iluminação natural e mensagem genuína supera uma produção cara e vazia. As pessoas percebem autenticidade.
### 2. Consistência é mais valiosa que perfeição
Publicar toda semana com qualidade razoável é mais eficaz do que publicar raramente com qualidade impecável. O algoritmo e a audiência valorizam consistência.
### 3. O digital serve o presencial
A presença digital deve convidar pessoas para a comunidade real, não substituí-la. Toda estratégia digital deve ter, em algum ponto, um caminho de volta para o encontro presencial.
### 4. Proteja a privacidade da congregação
Histórias de transformação são poderosas — mas exigem autorização explícita de quem as vive. Rosto de crianças, situações de crise, momentos íntimos de culto: antes de publicar, pergunte-se se a pessoa envolvida autorizaria.
### 5. Descanse do digital
O líder que nunca se desconecta esgota sua capacidade de ouvir a voz de Deus e de sua congregação. Dias de silêncio, de oração sem câmera, de comunhão sem story — são tão importantes quanto a presença online.
---
## Conclusão
A tecnologia não mudou a mensagem do evangelho. Mas mudou profundamente o alcance e os meios pelos quais essa mensagem pode ser levada.
Líderes que abraçam o digital com discernimento — entendendo seus limites, evitando suas armadilhas, e usando-o como ferramenta a serviço do Reino — têm hoje uma capacidade missionária sem precedentes na história da Igreja.
O desafio não é técnico. É espiritual: usar bem o que temos disponível, sem nos perdermos no processo.
---
**Quer centralizar a presença digital do seu ministério?**
Crie sua página no LuzLinks e ofereça à sua congregação uma porta de entrada organizada para tudo que você produz.
[Criar minha página no LuzLinks →](https://luzlinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -1,190 +0,0 @@
---
title: "Como Criar Sua Bio de Fé do Zero"
description: "Um guia prático para pastores, líderes e ministérios que querem criar uma página de links organizada, bonita e que realmente represente o trabalho do ministério online."
keywords: "bio link pastor, como criar pagina ministerio, presenca digital igreja, links para pastores, ministerio digital"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/bio-de-fe.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Criar Sua Bio de Fé do Zero
Você já precisou enviar vários links diferentes para alguém que queria saber mais sobre o seu ministério? Link do YouTube, link do grupo do WhatsApp, link da agenda, Pix para dízimos… cada coisa num lugar diferente.
Existe uma forma muito mais simples de resolver isso: uma bio de links. Uma página única que reúne tudo que seu ministério oferece online, pronta para ser compartilhada com um só endereço.
Este guia mostra como criar a sua do zero, mesmo que você não tenha experiência com tecnologia.
---
## Este artigo é para você se...
- Você é pastor, padre, líder ou membro ativo de um ministério
- Você manda links diferentes para a congregação e quer centralizar tudo num único endereço
- Você ainda não tem nenhuma bio de links — ou tem uma desatualizada
- Você não precisa de experiência técnica: tudo aqui é feito pelo celular ou computador, sem programação
**O que você encontra aqui:** como escolher e organizar os links do ministério, o que escrever na descrição, erros comuns para evitar e o passo a passo completo para publicar.
---
## O que é uma bio de links?
Uma bio de links é uma página simples na internet com o seu nome, uma foto, uma descrição breve e uma lista de links para tudo que você quer que as pessoas encontrem.
Funciona assim: você cria uma página no LuzLinks, configura seus links, e recebe um endereço único — como `luzlinks.site/pastor/seu-nome`. Esse endereço vai na bio do Instagram, no perfil do WhatsApp, no final de cada vídeo do YouTube, em qualquer lugar que você queira.
Quem clicar chega numa página organizada com acesso a tudo.
---
## Antes de criar: o que você precisa ter em mãos
Antes de começar, separe estas informações:
**Foto de perfil**
Use uma foto de rosto clara, com boa iluminação. Não precisa ser profissional, mas precisa ser nítida. Uma foto em frente a uma janela iluminada já resolve bem.
**Nome do ministério ou do pastor**
Decida se vai usar seu nome pessoal, o nome da igreja ou o nome do ministério. Se você tem ambos, pode colocar "Pastor João Silva — Igreja Esperança".
**Descrição breve (até 2 linhas)**
O que você faz? Para quem? Onde?
Exemplo: *"Pastor evangélico em São Paulo. Pregações, estudos bíblicos e agenda de cultos."*
**Lista de links que você quer incluir**
Anote todos os links que você usa. Depois você vai organizar por prioridade.
---
## Quais links colocar (e em que ordem)
A ordem importa. As pessoas clicam mais nos primeiros links, então coloque o mais importante no topo.
### Sugestão de ordem para ministérios:
**1. Canal do YouTube**
Se você posta pregações ou estudos bíblicos, este é o link mais valioso. Uma pessoa que acabou de descobrir seu ministério vai querer assistir antes de qualquer outra coisa.
**2. Grupo do WhatsApp ou Telegram**
A comunidade da sua congregação fica aqui. Facilita muito para novos interessados entrarem em contato.
**3. Agenda de cultos ou eventos**
Um link direto para onde você divulga os próximos eventos — pode ser uma página do Instagram, um Google Agenda público ou um link simples.
**4. Dízimos e doações**
Se o seu ministério recebe dízimos ou ofertas pelo Pix, inclua um link aqui. Pode ser uma página de doação, um link do Vakinha, ou um link que abre o Pix no celular.
**5. Instagram**
Para quem quer acompanhar o dia a dia do ministério.
**6. Site da igreja (se tiver)**
Informações mais completas, histórico, endereço físico, horários fixos.
**7. Material para download (se tiver)**
Apostilas, hinários, materiais de célula — tudo que você distribui pode ter um link direto.
---
## Como escrever a descrição da sua página
Muitos líderes escrevem uma descrição vaga como "Ministério da Graça". Isso diz muito pouco para quem ainda não te conhece.
Uma boa descrição responde três perguntas rápidas:
- Quem é você?
- O que você oferece?
- Onde/como a pessoa pode participar?
**Exemplos práticos:**
*Versão genérica (evitar):*
> "Ministério da Graça — Servindo ao Senhor"
*Versão mais eficaz:*
> "Pastor João Silva — Pregações toda semana no YouTube. Cultos às quartas e domingos em São Paulo. Comunidade aberta no WhatsApp."
A segunda versão faz a pessoa entender em segundos se quer continuar explorando ou não.
---
## Dicas para a foto e aparência da página
**Foto de perfil:** Olhos nos olhos com a câmera, expressão acolhedora, fundo neutro ou de preferência relacionado ao ministério (púlpito, biblia, bandeira da igreja).
**Tema visual:** Escolha um tema que combine com a identidade do ministério. Tons de azul e branco transmitem paz e serenidade. Tons de dourado e vinho remetem a tradição e reverência. Evite cores muito agitadas — simplicidade passa mais confiança.
**Nome na página:** Use o nome pelo qual você é conhecido, não necessariamente o nome legal. "Pastor João" pode ser mais reconhecível do que "João Ferreira da Silva".
---
## Erros comuns ao criar uma bio de fé
**Colocar links quebrados**
Antes de publicar, clique em cada link para confirmar que está funcionando. Links quebrados afastam as pessoas.
**Usar descrição muito longa**
A bio não é um sermão. Duas ou três linhas são o suficiente. Quem quiser saber mais vai clicar nos links.
**Não atualizar quando algo muda**
Se você cria um novo grupo do WhatsApp, muda o canal do YouTube ou abre um novo site, lembre de atualizar a bio também.
**Ignorar a foto de perfil**
Uma página sem foto parece abandonada. Mesmo que seja uma imagem simples, coloque uma foto.
---
## Passo a passo para criar no LuzLinks
1. Acesse [luzlinks.site](https://luzlinks.site/) e crie sua conta com email ou Google
2. Clique em "Criar nova página"
3. Escolha a categoria que melhor representa seu ministério (pastor, padre, líder, etc.)
4. Defina sua URL personalizada — ex: `luzlinks.site/pastor/joao-silva`
5. Adicione foto de perfil e descrição
6. Adicione seus links na ordem de prioridade
7. Escolha um tema visual
8. Envie para moderação e aguarde a aprovação
Depois de aprovada, compartilhe o link em todas as suas redes e coloque na bio do Instagram e no status do WhatsApp.
---
## Por onde começar hoje?
Se você está começando do zero, não espere ter tudo pronto. Comece com o básico:
- Foto de perfil
- Descrição de duas linhas
- Três ou quatro links principais
Depois você vai ajustando conforme usa. Uma página simples e funcionando é muito melhor do que uma página perfeita que nunca sai do papel.
---
## O que pode não funcionar
- **Sem foto de perfil:** páginas sem foto têm taxa de saída muito mais alta — as pessoas não confiam num perfil anônimo
- **Links quebrados:** um link que não abre faz a pessoa desistir e não voltar; cheque tudo antes de publicar
- **Descrição genérica:** "Ministério da Graça" sem contexto não converte — seja específico sobre o que você oferece
- **Página desatualizada:** um horário de culto errado ou um grupo lotado (sem acesso) frustra quem quer participar
---
## Leia também
- [Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link](/artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp)
- [Por Que Pastores Precisam de Presença Digital](/artigos/por-que-pastores-precisam-de-presenca-digital)
- [Como Receber Dízimos Online no Seu Ministério](/artigos/como-receber-dizimos-online-no-seu-ministerio)
---
**Pronto para centralizar a presença digital do seu ministério?**
Crie sua página no LuzLinks e ofereça à sua congregação um único link para tudo.
[Criar minha bio de fé →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,189 +0,0 @@
---
title: "Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link"
description: "Chega de mandar vários links diferentes no grupo da igreja. Veja como centralizar a agenda, o link da live e as informações do culto em uma única página que você compartilha com facilidade."
keywords: "divulgar culto whatsapp, agenda igreja online, link ministerio whatsapp, como divulgar eventos religiosos, bio link pastor whatsapp"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/cultos-whatsapp.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link
Todo líder religioso que usa o WhatsApp para se comunicar com a congregação conhece bem essa situação: é véspera de culto, você precisa mandar o lembrete para o grupo, mas junto precisa enviar o link da live, o endereço no Google Maps, o link para o pedido de oração e talvez o Pix para a oferta especial.
Resultado: uma mensagem enorme, cheia de links, que a maioria das pessoas simplesmente para de ler no meio.
Existe uma solução bem mais simples para isso.
---
## Este artigo é para você se...
- Você já usa grupos de WhatsApp para se comunicar com a congregação
- Você manda vários links separados nos grupos e sente que as pessoas se perdem
- Você quer que um novo contato encontre tudo em um só lugar, sem precisar perguntar
- Você quer economizar tempo na comunicação semanal do ministério
**O que você encontra aqui:** como estruturar a bio de links para eventos, quando atualizar o link da live, modelos de mensagem prontos para usar e como aplicar o mesmo link em outros canais.
---
## O problema com múltiplos links no WhatsApp
Quando você manda vários links numa mensagem, acontece algo previsível: as pessoas abrem um ou dois, ignoram o resto, e ficam sem as informações que precisariam ter visto.
Isso não é falta de atenção da congregação. É assim que as pessoas funcionam com mensagens longas — especialmente em grupos com muitas notificações.
Além disso, toda vez que algo muda (nova live, novo link do Zoom, endereço diferente), você precisa mandar tudo de novo. O grupo começa a ficar poluído com mensagens repetidas.
---
## A solução: um único link que tem tudo
Com uma página no LuzLinks, você cria um endereço único — como `luzlinks.site/pastor/seu-nome` — que concentra tudo que a congregação precisa saber.
A mensagem do grupo vira algo assim:
> "Culto de quarta à noite às 19h. Acesse aqui todos os links: luzlinks.site/pastor/joao-silva"
Uma linha. Um link. Tudo que a pessoa precisa está lá quando ela clicar.
---
## Como estruturar a página para divulgação de eventos
O segredo está em como você organiza os links. Para uma congregação que acompanha eventos regularmente, esta ordem funciona bem:
**1. Link da live (transmissão ao vivo)**
Esse é o mais urgente e deve ficar no topo. Quando tem culto ao vivo, a pessoa que chega tarde ou não pôde ir precisa encontrar esse link rápido.
Dica: se o link da live muda toda semana (como acontece com algumas plataformas), use um link que aponte para a playlist do canal — assim você não precisa atualizar a bio a cada culto.
**2. Agenda de cultos**
Um link para onde está a programação completa. Pode ser uma nota no Instagram, um post fixado, ou uma página do Google Sites com os horários.
**3. Endereço da igreja (Google Maps)**
Para quem vai presencialmente. O link do Maps abre direto no aplicativo de navegação do celular.
**4. Grupo do WhatsApp principal**
Para novos membros que chegaram pelo link e ainda não estão no grupo.
**5. Pedidos de oração**
Um formulário simples (Google Forms funciona muito bem) ou um link direto para mensagem no WhatsApp.
**6. Dízimos e ofertas**
Pix, link de doação ou a plataforma que seu ministério usa.
**7. Canal do YouTube**
Para quem quer ver pregações anteriores.
---
## Quando atualizar o link da live
A maior dúvida de quem usa essa estratégia é: "E quando o link da live muda?"
**Se você transmite pelo YouTube:** O link do canal não muda nunca. Ao invés de linkar uma live específica, link o canal. Quem acessa vai ver a live no topo automaticamente quando ela estiver acontecendo.
**Se você transmite pelo Zoom:** O ID da reunião costuma ser sempre o mesmo quando você agenda recorrente. Verifique nas configurações do Zoom se pode usar "ID de reunião pessoal" — assim o link nunca muda.
**Se você transmite pelo Instagram Live:** Infelizmente não existe link direto para lives ao vivo no Instagram. Nesse caso, link seu perfil do Instagram e oriente a congregação a abrir o app e ir direto ao seu perfil.
---
## Modelo de mensagem para o grupo
Com a bio montada, você pode usar sempre a mesma estrutura de mensagem:
---
*Modelo para culto semanal:*
> 📅 Culto de quarta — hoje às 19h
>
> ▶️ Live: [link direto caso seja específico]
> 📍 Presencial: [endereço]
>
> Tudo em um só lugar: luzlinks.site/pastor/seu-nome
---
*Modelo para evento especial:*
> 🙏 Retiro de jovens — sábado, 10 de maio
>
> Informações completas, como chegar e inscrições:
> luzlinks.site/pastor/seu-nome
---
Perceba que a mensagem é curta. O link faz o trabalho pesado.
---
## Coloque o link no status do WhatsApp
Além do grupo, coloque o link da sua bio no status do WhatsApp. O status fica visível por 24 horas para todos os seus contatos, não apenas para quem está no grupo da igreja.
Isso alcança pessoas que você conhece pessoalmente mas que ainda não fazem parte da congregação — um contato de trabalho, um familiar, um vizinho que você encontra raramente.
Uma mensagem simples no status:
> "Culto hoje às 19h — link de tudo aqui 👆 luzlinks.site/pastor/seu-nome"
---
## Usando o link em outros lugares
O mesmo link que você usa no WhatsApp funciona em qualquer lugar:
**Bio do Instagram:** cole na bio e sempre que fizer stories peça para a pessoa "clicar no link da bio"
**Cartões de visita:** se você distribui cartões físicos, o link da bio pode ser o único endereço que você precisa colocar
**Assinatura de email:** coloque no final dos seus emails como "Acesse minha agenda: luzlinks.site/pastor/seu-nome"
**Final de pregações gravadas:** ao editar os vídeos, inclua o link nas telas finais e na descrição do YouTube
---
## Resumo prático
A bio de links não resolve apenas o problema do WhatsApp. Ela resolve o problema de ter presença digital fragmentada — cada plataforma com uma informação diferente, a congregação sem saber onde olhar.
Com um único link atualizado, você:
- Manda mensagens mais curtas e eficazes
- Facilita para pessoas novas encontrarem tudo de uma vez
- Reduz as mensagens repetidas nos grupos
- Tem um único lugar para atualizar quando algo muda
---
## O que pode não funcionar
- **Link da live que muda toda semana:** se você precisa atualizar o link da bio a cada culto, considere linkar o canal do YouTube ao invés da live específica — o canal sempre mostra a transmissão ativa no topo
- **Grupos lotados no WhatsApp:** se seu grupo atingiu o limite de participantes, o link de convite para e funcionar; mantenha a bio atualizada com o link do grupo atual
- **Bio usada apenas no grupo principal:** o maior potencial do link único é alcançar pessoas *fora* do grupo já existente — divulgue também no status do WhatsApp, no Instagram e em outras redes
- **Página sem agenda atualizada:** uma agenda desatualizada (com cultos que já passaram) confunde quem está tentando participar pela primeira vez
---
## Leia também
- [Como Criar Sua Bio de Fé do Zero](/artigos/como-criar-sua-bio-de-fe-do-zero)
- [Como Receber Dízimos Online no Seu Ministério](/artigos/como-receber-dizimos-online-no-seu-ministerio)
- [Por Que Pastores Precisam de Presença Digital](/artigos/por-que-pastores-precisam-de-presenca-digital)
---
**Quer configurar sua página e simplificar a comunicação com a congregação?**
Crie sua bio de fé no LuzLinks e comece a usar um link para tudo.
[Criar minha página →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,177 +0,0 @@
---
title: "Como Receber Dízimos Online no Seu Ministério"
description: "Um guia prático sobre as opções disponíveis para receber dízimos, ofertas e doações online — com foco em segurança, transparência e facilidade para a congregação."
keywords: "dizimo online, receber oferta online, pix igreja, doacao ministerio, contribuicao igreja digital"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/dizimos-online.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Receber Dízimos Online no Seu Ministério
A oferta no culto presencial tem um significado especial — é um gesto de fé, um ato de adoração feito em comunidade. Isso não muda com o digital.
Mas a realidade é que cada vez menos pessoas carregam dinheiro em espécie. E membros que assistem remotamente, que estão viajando, ou que simplesmente esqueceram o envelope em casa, podem querer contribuir de outras formas.
Disponibilizar opções de contribuição online não é substituir a oferta presencial — é incluir mais pessoas na prática.
---
## Este artigo é para você se...
- Você lidera um ministério e quer facilitar a contribuição de membros que assistem remotamente
- Você percebe que parte da congregação não contribui simplesmente porque não carrega dinheiro em espécie
- Você quer saber quais ferramentas existem no Brasil para receber contribuições online, com custo baixo ou zero
- Você quer fazer isso com transparência e sem complicar a gestão financeira do ministério
**O que você encontra aqui:** as principais opções de recebimento digital disponíveis no Brasil (Pix, plataformas de doação, internacionais), como comunicar isso à congregação e boas práticas de transparência financeira.
---
## Por que oferecer opções digitais de dízimo
**Membros remotos também querem contribuir**
Quem acompanha o ministério pelo YouTube ou vive em outra cidade frequentemente não tem como participar da oferta presencial. Uma forma digital de contribuir mantém esse vínculo.
**A geração mais jovem não usa dinheiro em espécie**
Pessoas entre 20 e 35 anos raramente têm notas no bolso. Se a única forma de contribuir é com dinheiro físico, essa parte da congregação simplesmente não contribui — não por falta de vontade, mas por falta de meio.
**Maior regularidade nas contribuições**
Uma pessoa que configura um Pix recorrente ou uma assinatura de contribuição tende a ser mais consistente do que uma que depende de ter dinheiro em mãos todo domingo.
**Facilidade de prestação de contas**
Contribuições digitais geram registro automático, o que facilita a gestão financeira e a transparência com a congregação.
---
## As principais opções disponíveis no Brasil
### Pix
O Pix é a opção mais simples e com menos burocracia. Qualquer banco ou fintech oferece gratuitamente, e a transferência é imediata.
**Como disponibilizar:**
- Crie uma chave Pix com um email ou número dedicado ao ministério (diferente do pessoal)
- Gere um QR Code estático de cobrança — sem valor fixo, a pessoa decide o valor
- Coloque o QR Code nos cultos, nos slides de apresentação, e o link na sua bio do LuzLinks
**Vantagem:** sem taxa, imediato, qualquer pessoa com app de banco consegue usar
**Desvantagem:** não tem gestão automática de recorrência — é uma transferência avulsa cada vez
---
### Conta bancária para crédito (TED/DOC)
Para dízimos maiores ou pessoas mais acostumadas com transferência bancária tradicional, ter os dados bancários disponíveis é importante.
Mantenha esses dados em lugar acessível — na bio do LuzLinks, no site da igreja, nos grupos do WhatsApp.
---
### Plataformas de doação recorrente
Para ministérios que querem gestão mais profissional de contribuições:
**Vakinha**
Focado em campanhas pontuais (reforma da sede, compra de equipamento, projeto especial). Ótimo para arrecadações com objetivo definido.
**PagSeguro / Mercado Pago**
Permitem criar links de doação com ou sem valor fixo, e configurar doações recorrentes. Cobram uma taxa por transação (em torno de 3-5%).
**Gerencianet / Efí**
Plataformas financeiras brasileiras que oferecem cobranças recorrentes via Pix ou boleto. Mais adequadas para ministérios que querem estrutura financeira mais completa.
**Assinaturas pelo WhatsApp/Telegram**
Algumas igrejas criam grupos "de membros contribuintes" onde a contribuição é o critério de participação — combinando comunicação especial com a prática do dízimo.
---
### Para contribuições internacionais
Se você tem membros ou doadores no exterior, o PayPal e o Wise são opções viáveis. Ambos permitem receber transferências de outros países com taxas razoáveis.
---
## Como comunicar as opções de forma eficaz
Disponibilizar as formas de contribuição não é suficiente — você precisa comunicar com clareza e naturalidade.
**No culto presencial:**
Reserve um momento breve para mostrar o QR Code do Pix no telão. Uma frase simples funciona bem: *"Para quem prefere contribuir pelo celular, o Pix está disponível aqui."*
**Nas transmissões ao vivo:**
Mencione as formas de contribuição no início e no final da transmissão. Não precisa ser uma campanha — uma referência breve e direta é suficiente.
**Na sua bio do LuzLinks:**
Inclua um link de dízimos na sua página. Algo como "Dízimos e Ofertas" com um link para o Pix ou para a página de doação. Quando alguém descobre seu ministério e quer contribuir, esse link é o caminho mais direto.
**No grupo do WhatsApp:**
Fixe uma mensagem com todas as formas de contribuição para que novos membros consigam encontrar facilmente.
---
## Transparência: a base da confiança financeira
Independente do método usado, a transparência é o que constrói e mantém a confiança da congregação nas finanças do ministério.
**Boas práticas:**
- Apresente um relatório financeiro periódico para a congregação (mensalmente ou trimestralmente)
- Explique para onde vai o dinheiro das ofertas e dízimos
- Tenha mais de uma pessoa responsável pela gestão financeira — nunca uma pessoa só
- Separe contas: conta pessoal do pastor e conta do ministério devem ser sempre diferentes
Ministérios que tratam as finanças com transparência raramente enfrentam crises de confiança. E transparência é mais fácil quando as contribuições são digitais, porque o registro é automático e consultável.
---
## Um sistema simples que funciona
Para a maioria dos ministérios, este conjunto básico já atende bem:
1. **Pix com QR Code** para contribuições espontâneas no culto e online
2. **Link do Pix ou página de doação** na bio do LuzLinks
3. **Menção breve nos cultos** sobre as formas disponíveis
4. **Relatório financeiro mensal** compartilhado com a liderança e, em versão resumida, com a congregação
Você pode começar com isso hoje, sem custo e sem complicação.
---
## Sobre a parte espiritual
Este artigo focou nos aspectos práticos — ferramentas, plataformas, comunicação. Mas é importante lembrar que o dízimo e a oferta têm um significado espiritual que vai além da logística.
A facilidade de contribuição digital deve ser uma ferramenta a serviço da prática de fé, não uma substituição do seu significado. O que você ensina sobre mordomia e contribuição é tão importante quanto as ferramentas que você disponibiliza.
Use o digital para remover obstáculos práticos — não para transformar a oferta em algo mecânico.
---
## O que pode não funcionar
- **Usar apenas o Pix pessoal do pastor:** misturar finanças pessoais e do ministério é a raiz de muitos problemas de confiança — crie um email e chave Pix separados para o ministério
- **Não comunicar as formas disponíveis:** disponibilizar sem avisar é como ter uma porta aberta num quarto escuro; mencione regularmente nos cultos e nas transmissões
- **Depender de uma única plataforma:** plataformas mudam políticas e taxas; ter Pix como opção principal e uma segunda alternativa garante continuidade
- **Não prestar contas:** contribuições digitais sem prestação de contas periódica geram desconfiança; um relatório simples, mesmo que informal, faz diferença
---
## Leia também
- [Como Criar Sua Bio de Fé do Zero](/artigos/como-criar-sua-bio-de-fe-do-zero)
- [Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link](/artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp)
- [Por Que Pastores Precisam de Presença Digital](/artigos/por-que-pastores-precisam-de-presenca-digital)
---
**Coloque o link dos dízimos na sua bio de fé.**
Com uma página no LuzLinks, sua congregação encontra todas as formas de contribuição em um só lugar.
[Criar minha página no LuzLinks →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,168 +0,0 @@
---
title: "Por Que Pastores Precisam de Presença Digital"
description: "Uma análise honesta de por que a presença digital deixou de ser opcional para líderes religiosos — e como começar sem abrir mão da autenticidade e dos valores do ministério."
keywords: "pastor presenca digital, igreja digital, lider religioso internet, ministerio online, como comecar presenca digital pastor"
author: "Equipe LuzLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/presenca-digital-pastor.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Por Que Pastores Precisam de Presença Digital
Há um argumento que muitos líderes religiosos usam para resistir ao mundo digital: "Meu trabalho é com pessoas, não com telas."
É um argumento honesto. E parcialmente correto — o coração do ministério é mesmo o encontro real, a comunhão presencial, o olho no olho.
Mas esse argumento ignora uma realidade importante: as pessoas que você quer alcançar estão online. E se você não está lá, alguém com uma mensagem completamente diferente está.
---
## Este artigo é para você se...
- Você ainda não tem presença digital — ou tem algo desatualizado que não representa bem o seu ministério
- Você já ouviu falar em "estar online" mas não está convencido de que faz diferença para um ministério
- Você quer entender os riscos reais de não estar no digital, com exemplos práticos
- Você quer começar de forma simples, sem transformar a vida em produção de conteúdo
**O que você encontra aqui:** por que a presença digital mudou de opcional para necessária, os custos reais de não estar online, e o mínimo que já faz diferença — com passos concretos para começar.
---
## Como as pessoas encontram novas igrejas hoje
Vinte anos atrás, uma família que se mudava para uma cidade nova perguntava para vizinhos ou parentes onde havia uma boa igreja. Hoje, ela abre o Google e digita "igreja evangélica perto de mim" ou "pastor presbitariano em [cidade]".
Se a sua congregação não aparece nessa busca, simplesmente não existe para essa família.
Pesquisas no Brasil mostram que mais de 70% das pessoas que começaram a frequentar uma nova comunidade religiosa nos últimos anos a descobriram primeiro online — seja pelo YouTube, pelo Instagram ou por uma pesquisa no Google.
A janela de entrada da sua congregação não é mais apenas a porta física da sede. É também o que aparece quando alguém pesquisa sobre seu ministério na internet.
---
## A presença digital não substitui o presencial
Antes de continuar, é importante deixar isso claro: presença digital não é concorrente do ministério presencial. É complemento.
Um culto ao vivo transmitido pelo YouTube não substitui a comunhão de estar presente no templo. Uma pregação no Instagram não tem a profundidade de um retiro espiritual.
Mas a live pode alcançar um membro que está doente e não pode sair. A pregação no Instagram pode ser o primeiro contato de alguém que nunca pisou numa igreja mas está buscando algo diferente.
O ministério digital abre portas. O ministério presencial é o que acontece quando as pessoas entram por essas portas.
---
## O custo de não estar online
Quando um ministério não tem presença digital, o custo não é apenas invisibilidade. São oportunidades concretas que se perdem:
**Pessoas que estão buscando mas não encontram**
Alguém com dúvidas espirituais pesquisa no YouTube por respostas. Encontra vários canais de líderes religiosos. O seu não está lá.
**Membros que se afastam por falta de conexão**
Um membro que se muda para outra cidade ou que passa por um período de dificuldade para comparecer presencialmente perde o vínculo com a comunidade se não há uma forma de continuar conectado.
**Jovens que não se identificam com o que veem**
Para a geração que cresceu com smartphone, uma comunidade que não tem presença digital parece desatualizada — mesmo que o conteúdo da mensagem seja profundo e relevante.
**Crises sem canais de comunicação**
Em situações de urgência — cancelamento de evento, mudança de horário, situação de emergência — um ministério sem canais digitais depende de ligações individuais ou grupos de WhatsApp desorganizados.
---
## O mínimo que faz diferença
Você não precisa ter uma equipe de produção de conteúdo ou postar todos os dias. O mínimo que já muda a situação de um ministério:
### 1. Um ponto de referência digital
Uma página simples com: nome do ministério, endereço, horários de culto, contato e links para redes sociais. Isso já garante que alguém que pesquisa seu nome encontre algo.
O LuzLinks foi criado exatamente para isso — criar esse ponto de referência de forma simples, sem precisar montar um site completo.
### 2. Presença consistente em uma plataforma
Escolha uma plataforma e mantenha-a atualizada regularmente. YouTube funciona bem para ministérios com pregações. Instagram funciona bem para comunidade e rotina. WhatsApp funciona bem para comunicação interna.
Você não precisa estar em todos os lugares. Mas precisa estar em algum lugar de forma consistente.
### 3. Comunicação clara sobre eventos
As pessoas precisam saber quando e onde os cultos acontecem. Isso parece óbvio, mas é surpreendente quantos ministérios não têm essa informação em lugar nenhum online.
---
## Objeções comuns — e respostas honestas
**"Não tenho tempo para ficar postando."**
Presença digital não precisa ser conteúdo novo todos os dias. Uma pregação postada por mês no YouTube, um post de agenda no Instagram por semana e um grupo de WhatsApp organizado já são suficientes para começar.
**"Tenho medo de expor minha vida pessoal."**
Você define o que compartilha. Muitos líderes mantêm uma presença digital completamente focada no ministério — pregações, estudos, agenda — sem expor nada da vida privada. Isso é possível e saudável.
**"Minha congregação é mais velha e não usa redes sociais."**
Parte da sua congregação atual pode não usar. Mas as pessoas que você ainda não alcançou — os filhos desses membros, a família de quem já frequenta — usam. E quando esses membros mais velhos indicam sua igreja para alguém, a primeira coisa que essa pessoa vai fazer é pesquisar online.
**"Não sei nada de tecnologia."**
Esse argumento ficou mais fraco com o tempo. As ferramentas de hoje são projetadas para serem usadas por qualquer pessoa. Criar uma página no LuzLinks, por exemplo, leva menos de 30 minutos e não exige nenhum conhecimento técnico.
---
## Por onde começar
Se você ainda não tem nenhuma presença digital, comece pelos fundamentos:
**Passo 1:** Crie uma conta no LuzLinks e monte uma página simples com os links do seu ministério. Isso leva menos de meia hora.
**Passo 2:** Coloque esse link na bio do Instagram e no status do WhatsApp.
**Passo 3:** Se você já grava os cultos em vídeo, comece a postar no YouTube. Mesmo sem edição — uma gravação direta da câmera do celular já serve para começar.
**Passo 4:** Mantenha as informações atualizadas. Horários, endereço, links — nada de informação desatualizada.
Depois que esses fundamentos estiverem funcionando, você avança com mais tranquilidade para outras plataformas, se quiser.
---
## A pergunta certa
A pergunta não é "por que devo estar online?" — porque as razões são claras.
A pergunta é: "Como posso estar online de uma forma que seja autêntica ao meu ministério e sustentável para a minha rotina?"
E essa é uma pergunta que você pode responder com calma, no seu ritmo, começando pelo mais simples.
---
## O que pode não funcionar
- **Tentar estar em todas as plataformas ao mesmo tempo:** leva à superficialidade em todas. Comece com uma e domine antes de expandir
- **Postar esporadicamente:** o algoritmo e a audiência valorizam consistência. Uma postagem por semana, sempre, supera dez postagens num mês e sumindo depois
- **Separar completamente digital do presencial:** a presença digital deve convidar para o encontro real, não substituí-lo. Ministérios que tratam os dois como mundos separados perdem o sentido de comunidade
- **Não monitorar o que está funcionando:** criar conteúdo sem nunca verificar se as pessoas estão chegando é trabalho sem aprendizado
---
## Leia também
- [Como Criar Sua Bio de Fé do Zero](/artigos/como-criar-sua-bio-de-fe-do-zero)
- [Como Divulgar Cultos e Eventos pelo WhatsApp com um Único Link](/artigos/como-divulgar-cultos-e-eventos-pelo-whatsapp)
- [Como Receber Dízimos Online no Seu Ministério](/artigos/como-receber-dizimos-online-no-seu-ministerio)
---
**Pronto para dar o primeiro passo?**
Crie a página do seu ministério no LuzLinks e garanta que as pessoas que estão buscando uma comunidade de fé possam te encontrar.
[Criar minha página no LuzLinks →](https://luzlinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,287 +0,0 @@
---
title: "Como Criar sua Página no LuzLinks: Guia Completo para Líderes Religiosos"
description: "Passo a passo para pastores, padres, líderes e ministérios criarem sua página profissional no LuzLinks e alcançarem mais pessoas com sua mensagem de fé."
keywords: "luzlinks tutorial, página pastor, bio links ministerio, presença digital iglesia, como criar página pastor"
author: "Equipe LuzLinks"
date: 2026-01-25
lastMod: 2026-01-25
image: "/images/tutoriais/pastor-luzlinks.jpg"
culture: "pt-BR"
category: "ministerio"
---
# Como Criar sua Página no LuzLinks: Guia Completo para Líderes Religiosos
Sua congregação está em vários lugares ao mesmo tempo: no Instagram, no YouTube, no WhatsApp, no Telegram, na agenda de eventos — e você fica repetindo os mesmos links toda semana. O LuzLinks resolve isso com uma página única que reúne tudo.
Este guia mostra o passo a passo completo para líderes religiosos criarem sua bio de fé.
---
## Por Que Líderes Religiosos Precisam de uma Página de Links
### A Realidade da Igreja Digital
Depois da pandemia, toda congregação se tornou também digital. Sua comunidade espera encontrar:
- O link da live do culto
- A série de estudos bíblicos no YouTube
- O grupo do WhatsApp dos membros
- A agenda de eventos do mês
- Como fazer dízimos e ofertas online
- O endereço da sede para quem ainda não foi
Quando alguém perguntar "onde acompanho o ministério?", você precisa ter uma resposta simples. Uma URL só.
---
## Passo 1: Criar sua Conta no LuzLinks
### 1.1. Acesse o Site
Vá para [luzlinks.site](https://luzlinks.site) e clique em **"Entrar"** no menu.
### 1.2. Login Social (Recomendado)
Escolha login com **Google** ou **Microsoft**. É mais rápido e seguro — sem necessidade de criar nova senha.
> **Dica:** Use o email principal do ministério para criar a conta. Assim, qualquer pessoa autorizada da equipe pode acessar.
---
## Passo 2: Criar Sua Primeira Página
Após o login, clique em **"Criar Minha Página"**.
### Escolhendo o Nome da Página
O nome deve ser claro e reconhecível pela sua congregação:
| Tipo de Líder | Exemplo de Nome |
|---|---|
| Pastor titular | Pr. João Silva — Igreja Graça Viva |
| Padre | Pe. Carlos Mendes — Paróquia São José |
| Missionário | Missionário Paulo — Missão África |
| Ministério de louvor | Ministério Ágape |
| Igreja geral | Igreja Batista Central — Belo Horizonte |
| Líder de jovens | Pr. André — Juventude Transformada |
### Escolhendo o Slug (URL)
Sua URL no LuzLinks seguirá o padrão:
```
luzlinks.site/ministerio/seu-slug
```
Boas opções:
```
pr-joao-silva → luzlinks.site/ministerio/pr-joao-silva
igreja-graca-viva → luzlinks.site/ministerio/igreja-graca-viva
ministerio-agape → luzlinks.site/ministerio/ministerio-agape
pe-carlos-mendes → luzlinks.site/ministerio/pe-carlos-mendes
```
> **Dica:** Escolha algo simples que qualquer membro consiga digitar de memória.
### Escrevendo a Descrição
Seja claro sobre quem você é e o que sua comunidade encontra aqui:
**Exemplo — Pastor:**
```
Pr. João Silva | Igreja Graça Viva — São Paulo/SP
Pregação, estudos bíblicos e agenda de cultos.
Domingo às 9h e 19h | Quarta às 20h (online e presencial)
```
**Exemplo — Ministério de Louvor:**
```
Ministério Ágape | Louvor e adoração
Novas músicas toda semana no YouTube
Shows, CDs e partituras disponíveis abaixo
```
**Exemplo — Padre:**
```
Pe. Carlos Mendes | Paróquia São José — Rio de Janeiro
Missas, catequese e eventos paroquiais
Confissões: terça e quinta, 16h às 18h
```
---
## Passo 3: Montando Seus Links
### Os Links Mais Importantes para Ministérios
**1. 📺 Canal do YouTube (Lives e Pregações)**
```
Título: Assistir Pregações e Lives
URL: https://youtube.com/@seuministerio
```
> Coloque no topo. É o que mais pessoas buscam.
**2. 📱 Grupo do WhatsApp**
```
Título: Entrar no Grupo da Igreja
URL: https://chat.whatsapp.com/seu-link-de-convite
```
> Limite de 1024 membros por grupo. Se sua congregação for maior, use o Telegram.
**3. 📅 Agenda de Cultos e Eventos**
```
Título: Ver Próximos Eventos
URL: https://linkdoseusite.com/agenda
```
> Pode ser uma página do Google Sites, Notion, ou o site da igreja.
**4. 🙏 Dízimos e Ofertas Online**
```
Título: Contribuir com o Ministério
URL: https://pix.bcb.gov.br/qr/seu-qr-code
```
> Ou: link para a página de doação do site da igreja, PagSeguro, Stripe etc.
**5. 📖 Série Atual de Estudos**
```
Título: Série: [Nome da Série] — Episódio Atual
URL: Link direto para o episódio mais recente
```
> Atualize semanalmente. Mostra que a página está viva.
**6. 📸 Instagram da Igreja**
```
Título: Seguir no Instagram
URL: https://instagram.com/seuministerio
```
**7. 📍 Endereço da Sede**
```
Título: Como Chegar — Sede São Paulo
URL: https://maps.google.com/?q=Rua+da+Igreja+123+São+Paulo
```
**8. ✉️ Email de Contato**
```
Título: Falar com Secretaria
URL: mailto:secretaria@suaigreja.com.br
```
---
## Passo 4: Escolhendo o Tema Visual
O LuzLinks oferece temas adequados para conteúdo espiritual. Recomendamos:
- **Clássico ou Minimalista** para igrejas tradicionais (católicas, luteranas, presbiterianas)
- **Gradiente suave ou Azul** para igrejas evangélicas pentecostais
- **Dourado ou Roxo** para ministérios de louvor e adoração
- **Verde ou Natural** para missões e trabalho social
> O tema pode ser alterado a qualquer momento sem precisar refazer a página.
---
## Passo 5: Submeter para Aprovação
Após configurar tudo, clique em **"Submeter para Moderação"**. Nossa equipe revisa em até 48 horas.
### O Que a Equipe Verifica
- Links funcionando e direcionando para conteúdo apropriado
- Informações claras e sem conteúdo enganoso
- Conteúdo compatível com a proposta da plataforma
---
## Passo 6: Divulgando Sua Página na Congregação
Com a página aprovada, divulgue por todos os canais:
### No Culto Presencial
Projete no telão durante os avisos:
```
"Encontre todos os nossos links e contatos em:
luzlinks.site/ministerio/sua-church"
```
### No Instagram
Atualize a bio:
```
Igreja Graça Viva | São Paulo
Cultos: Dom 9h e 19h | Qua 20h
🔗 Todos os links: luzlinks.site/ministerio/igreja-graca-viva
```
### No WhatsApp dos Membros
Envie uma mensagem no grupo:
```
Irmãos, agora temos uma página única com todos os nossos links:
cultos, agenda, YouTube, dízimos e mais.
👇 Salve e compartilhe:
luzlinks.site/ministerio/igreja-graca-viva
```
### No YouTube
Fixe o link como comentário pinado nos vídeos e inclua na descrição:
```
Todos os nossos contatos e próximos eventos:
luzlinks.site/ministerio/sua-church
```
---
## Manutenção da Página
Uma página atualizada é mais eficaz do que uma página perfeita e desatualizada.
**Atualizações semanais recomendadas:**
- Atualizar o link da série atual de estudos
- Adicionar links de eventos do mês
- Verificar se o grupo de WhatsApp não está cheio (link de convite expirado)
**Atualizações mensais:**
- Revisar se todos os links ainda funcionam
- Checar métricas no Dashboard: quais links têm mais cliques?
- Atualizar a descrição se houver mudança de horários de culto
---
## Dúvidas Frequentes
**Posso ter uma página para a igreja e outra para mim como pastor?**
Sim! Com o plano Premium você pode ter múltiplas páginas. Muitos líderes têm uma página pessoal e outra institucional da igreja.
**O link do grupo de WhatsApp expira. O que fazer?**
Sempre que renovar o link de convite, acesse o Dashboard e atualize o link na sua página. Leva menos de 1 minuto.
**Posso colocar link de canal privado (assinatura)?**
Sim, desde que o conteúdo seja compatível com a plataforma. O LuzLinks é focado em conteúdo espiritual e educativo.
**Posso ter a página em português e espanhol?**
Para congregações bilíngues, recomendamos duas páginas: uma em pt-BR e outra em es. O plano Premium permite isso.
---
## Conclusão
Criar sua página no LuzLinks é o primeiro passo para organizar a presença digital do seu ministério. Em vez de repetir links toda semana, você compartilha uma URL simples que sua congregação encontra tudo.
**Recapitulando:**
1. ✅ Criar conta com email do ministério
2. ✅ Definir nome e URL da página
3. ✅ Adicionar links essenciais (YouTube, WhatsApp, agenda, dízimos)
4. ✅ Escolher tema adequado ao ministério
5. ✅ Submeter para moderação
6. ✅ Divulgar para a congregação
[Criar minha página de fé →](https://luzlinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -1,197 +0,0 @@
---
title: "Como Configurar Sua Bio para Ganhar Mais Seguidores"
description: "Sua bio é a porta de entrada do seu perfil. Veja como configurar uma página de links que converte visitantes em seguidores — e seguidores em assinantes."
keywords: "bio link seguidores, como ganhar seguidores instagram, bio link criadora, aumentar seguidores bio, configurar bio profissional"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/bio-seguidores.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Configurar Sua Bio para Ganhar Mais Seguidores
A bio do Instagram tem um limite de 150 caracteres e permite um único link. Com esse espaço pequeno, você precisa convencer uma pessoa que acabou de ver seu conteúdo pela primeira vez a clicar, explorar e — idealmente — te seguir ou assinar.
A maioria das criadoras desperdiça essa oportunidade. Este artigo mostra como não desperdiçar.
---
## Este artigo é para você se...
- Você já cria conteúdo no Instagram mas sente que poderia estar convertendo mais visitantes em seguidores
- Você não sabe exatamente o que colocar na bio ou na página de links para chamar atenção
- Você quer uma estratégia clara — não dicas genéricas, mas um passo a passo que dá para aplicar hoje
- Você está crescendo e quer que a estrutura da sua bio acompanhe esse crescimento
**O que você encontra aqui:** como a jornada do visitante funciona, o que uma boa foto e bio fazem, como organizar os links por prioridade, e como usar métricas para melhorar ao longo do tempo.
---
## A jornada do visitante
Antes de falar sobre configuração, entenda o caminho que uma pessoa percorre antes de clicar em "seguir":
1. **Descobre você** — pelo feed, por um Reels, por um compartilhamento, por indicação
2. **Visita seu perfil** — analisa a foto, lê a bio, olha os posts recentes
3. **Clica no link da bio** — se a bio despertou interesse suficiente
4. **Explora a página de links** — vê o que você oferece além do que já viu
5. **Toma uma decisão** — seguir, assinar, ou sair
Cada etapa é uma chance de perder ou converter o visitante. Sua bio e sua página de links juntas determinam o que acontece nas etapas 2 a 4.
---
## A foto de perfil: primeiro impacto visual
Antes de ler uma palavra da sua bio, a pessoa vê sua foto. Ela precisa passar algumas mensagens em frações de segundo:
**Identificação clara:** É fácil reconhecer quem é você? O rosto deve estar visível e nítido, mesmo na versão pequena (a miniatura do Instagram é pequena).
**Coerência com o conteúdo:** Se você produz conteúdo com uma estética específica, a foto de perfil deve refletir isso. Quem clica no seu perfil depois de ver um Reels quer reconhecer a mesma pessoa.
**Qualidade:** Não precisa ser fotógrafo profissional, mas precisa ter boa iluminação. Uma janela com luz natural já faz grande diferença.
**Dica prática:** Evite fotos de grupo, fotos muito escuras, selfies com câmera frontal em ambientes com pouca luz, ou fotos com filtro tão forte que desfiguram o rosto.
---
## Os 150 caracteres da bio: cada palavra conta
A bio do Instagram tem espaço para aproximadamente 150 caracteres — umas 2 ou 3 linhas curtas. Esse é o seu espaço para responder a pergunta que o visitante está fazendo mentalmente: *"O que vou encontrar aqui?"*
**O que funciona na bio:**
**Uma descrição direta do conteúdo**
> "Conteúdo exclusivo toda semana 🔥 | Fitness e estilo de vida | Link abaixo 👇"
**Uma proposta de valor com emoji**
> "Sua dose diária de entretenimento ✨ | Novidades toda semana | Acesse meu link 👇"
**Uma chamada para ação com curiosidade**
> "Tem conteúdo exclusivo que não posto aqui 👀 | Veja no meu link ↓"
**O que evitar:**
❌ Bio vazia ou só com o nome
❌ Lista de redes sociais que já estão visíveis no perfil
❌ Frases muito genéricas que poderiam ser de qualquer pessoa
❌ Texto longo demais que a pessoa não lê
---
## A página de links: onde a conversão acontece
Quando alguém clica no link da sua bio, chega na sua página do SpicyLinks. É aqui que você tem mais espaço para convencer.
**Capriche no nome e na descrição da página**
O nome é o que aparece em destaque. Use como você quer ser reconhecida — seu nome artístico, apelido, ou o nome que você usa nas redes.
A descrição da página é mais longa que a bio do Instagram. Use para dar contexto sobre quem você é e o que oferece. Seja específica:
*Evite:* "Criadora de conteúdo"
*Prefira:* "Criadora de conteúdo de lifestyle e fitness. Conteúdo exclusivo toda semana, dicas de treino e meus looks favoritos."
**A ordem dos links define o que converte**
As primeiras posições recebem a maioria dos cliques. Coloque no topo o que você mais quer que as pessoas acessem:
```
1. Conteúdo exclusivo / plataforma principal
2. Instagram (para quem ainda não te segue lá)
3. Twitter/X (se você usa ativamente)
4. TikTok (se você usa)
5. Lista de desejos / outras plataformas
```
Se o objetivo é ganhar mais seguidores nas redes gratuitas, coloque os links delas antes das plataformas pagas. Se o objetivo é converter em assinantes, coloque a plataforma paga primeiro.
**O título de cada link importa**
O título do link é o que a pessoa lê antes de clicar. "Instagram" como título é genérico. Mas "Me segue lá também 📸" ou "Meus stories do dia a dia" já cria uma razão para clicar.
Teste títulos diferentes e veja o que gera mais cliques nas suas métricas.
---
## Consistência visual entre perfil e página de links
Um detalhe que faz diferença: a aparência da sua página de links deve combinar com a estética do seu perfil.
Se você tem um feed com paleta de cores clara e minimalista, escolha um tema do SpicyLinks que reflita isso. Se seu perfil é vibrante e colorido, vá em direção a algo mais ousado.
Quando a pessoa passa do Instagram para a sua página de links e vê uma identidade visual coerente, a sensação é de profissionalismo. Quando parece uma coisa diferente, causa estranheza — e estranheza não converte.
---
## Usando o SpicyLinks para crescer no Instagram especificamente
Se crescer no Instagram é o objetivo prioritário, configure sua página assim:
**Posição 1:** Um link para o seu Instagram com título que incentive o follow
**Posição 2:** Seu conteúdo principal (plataforma exclusiva, YouTube, etc.)
**Posição 3+:** Outras redes e links relevantes
**Na bio do Instagram**, teste este tipo de texto:
> "Sempre tem coisa nova aqui 📲 | Conteúdo exclusivo no link 👇"
Ao invés de só falar em "conteúdo exclusivo pago", mencione o que é gratuito também. A pessoa que ainda não está pronta para pagar pode te seguir agora e assinar mais tarde.
---
## Análise: o que está funcionando?
Configure sua página e depois use as métricas disponíveis para entender o comportamento das pessoas:
- Qual link recebe mais cliques?
- Quantas pessoas visitam a página mas não clicam em nada?
- De onde as pessoas chegam na sua página (Instagram, Twitter, etc.)?
Se muita gente visita mas poucos clicam, o problema pode estar nos títulos dos links ou na descrição da página.
Se muita gente clica no Instagram mas poucos clicam no conteúdo exclusivo, talvez valha a pena testar uma ordem diferente dos links ou um título mais atrativo para a plataforma principal.
---
## Checklist para uma bio que converte
Antes de publicar, confira:
- [ ] Foto de perfil nítida e com rosto visível
- [ ] Bio do Instagram clara, com no máximo 3 linhas
- [ ] Link da bio atualizado (apontando para o SpicyLinks)
- [ ] Página do SpicyLinks com nome e descrição preenchidos
- [ ] Links organizados por prioridade
- [ ] Títulos dos links descritivos (não só o nome da plataforma)
- [ ] Tema visual coerente com a estética do perfil
- [ ] Todos os links testados (clicando para confirmar que funcionam)
---
## O que pode não funcionar
- **Foto de perfil genérica ou de baixa qualidade:** é a primeira coisa que as pessoas veem — uma foto ruim faz a pessoa sair antes de ler a bio
- **Bio do Instagram e página de links com visual completamente diferente:** a falta de coerência visual causa estranheza e diminui a confiança
- **Atualizar a bio raramente:** o que convertia bem 6 meses atrás pode não converter mais — revise os títulos dos links e a descrição a cada 2 ou 3 meses
- **Muitos links sem hierarquia:** 10 links todos com a mesma importância visual fazem a pessoa não saber o que clicar; prioridade clara é fundamental
---
## Leia também
- [Os Melhores Links para Colocar na Sua Bio do Instagram](/artigos/os-melhores-links-para-sua-bio-do-instagram)
- [Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados](/artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados)
- [SpicyLinks vs Linktree: Qual é Melhor para Criadores?](/artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores)
---
**Sua bio pronta para converter visitantes em seguidores?**
Configure sua página no SpicyLinks e transforme cada visita em uma oportunidade.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,204 +0,0 @@
---
title: "Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados"
description: "Marketing de afiliados é uma das formas mais acessíveis de renda extra para criadores. Veja como funciona, como começar e como usar sua bio de links para maximizar as conversões."
keywords: "links afiliados criadora, marketing afiliados instagram, ganhar dinheiro afiliados, renda extra criadora conteudo, afiliados bio link"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/afiliados-criadores.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados
Você recomenda roupas, produtos de beleza, acessórios, equipamentos de fotografia — coisas que realmente usa e aprecia. Seus seguidores confiam no que você indica. Mas quando alguém vai comprar, você não vê nada dessa venda.
O marketing de afiliados muda isso: você ganha uma comissão cada vez que alguém compra um produto pela sua indicação. E a parte boa é que não precisa estocar nada, não precisa vender diretamente, e o produto nem precisa ser seu.
Este artigo explica como funciona e como você pode começar.
---
## Este artigo é para você se...
- Você recomenda produtos para suas seguidoras mas ainda não monetiza essas indicações
- Você quer uma fonte de renda que funcione mesmo quando não está postando
- Você quer entender o básico de afiliados sem enrolação — como funciona, por onde começa e quanto dá para ganhar de forma realista
- Você quer saber como organizar os links de afiliado na sua bio de forma profissional
**O que você encontra aqui:** como funciona o marketing de afiliados, os principais programas disponíveis no Brasil, como divulgar sem parecer forçada e como organizar tudo na sua bio do SpicyLinks.
---
## O que é marketing de afiliados
Marketing de afiliados é um modelo onde uma empresa te paga uma comissão pela venda que você gerou. Você recebe um link único (seu link de afiliado), compartilha com sua audiência, e quando alguém compra através desse link, você recebe uma porcentagem do valor.
**Exemplo prático:**
Você usa um conjunto de maquiagem que adora. A marca tem um programa de afiliados e te oferece 15% de comissão. Você posta sobre o produto com seu link de afiliado. Uma seguidora compra — digamos R$ 200. Você recebe R$ 30 dessa venda, sem ter feito mais nada além de compartilhar.
Multiplique isso por vários produtos, várias seguidoras e um período de semanas ou meses, e fica claro por que afiliados podem ser uma fonte relevante de renda.
---
## Por que afiliados funcionam bem para criadores de conteúdo
**Sua audiência já confia em você**
A principal vantagem de um criador de conteúdo no marketing de afiliados é a relação de confiança já construída. Uma recomendação sua tem muito mais peso do que um anúncio tradicional. Suas seguidoras te conhecem, te acompanham, e tendem a confiar no que você indica.
**Não exige capital inicial**
Diferente de vender produtos próprios, afiliados não exigem nenhum investimento. Você não compra estoque, não precisa de depósito, não tem risco financeiro.
**Funciona enquanto você dorme**
Um link de afiliado colocado na sua bio continua gerando comissões 24 horas por dia, mesmo quando você não está postando. Um post antigo com link de afiliado pode gerar comissão meses depois que foi publicado.
**Complementa outras formas de receita**
Afiliados não concorrem com suas assinaturas ou com outras fontes de renda — eles somam.
---
## Os principais programas de afiliados no Brasil
### Amazon Associates (Parceiros Amazon)
O maior programa de afiliados do mundo. Qualquer produto vendido na Amazon pode ser divulgado com comissão — de 2% a 10% dependendo da categoria.
**Ideal para:** maquiagem, acessórios, eletrônicos, roupas, decoração, livros, qualquer coisa que você compra e usa.
Como entrar: [associados.amazon.com.br](https://associados.amazon.com.br) — cadastro gratuito, aprovação em poucos dias.
### Hotmart, Eduzz e Monetizze
Plataformas brasileiras de produtos digitais (cursos, ebooks, programas). Comissões costumam ser mais altas — 30% a 60% do valor do produto.
**Ideal para:** indicar cursos relacionados ao seu nicho (fotografia, edição de vídeo, fitness, etc.)
### Shein, Renner, Dafiti (programas próprios)
Lojas de moda que têm programas de afiliados diretos. Comissões menores (3-8%), mas produtos com alta taxa de compra entre seguidoras de moda e lifestyle.
### Programa de Afiliados do SpicyLinks
O Plano Premium+Afiliados do SpicyLinks permite incluir links de afiliado com tracking diretamente na sua página, de forma organizada e com métricas de cliques para cada link.
---
## O que você precisa para começar
**1. Escolha um ou dois programas**
Não tente entrar em todos de uma vez. Comece com o que faz mais sentido para o seu nicho e o que você já compra naturalmente.
**2. Crie sua conta nos programas escolhidos**
A maioria exige apenas email, CPF e dados bancários para pagamento. Aprovação costuma ser rápida para quem já tem alguma audiência.
**3. Gere seus links de afiliado**
Cada produto que você quiser divulgar tem um link único com seu código. Quando alguém compra por esse link, a venda é atribuída a você.
**4. Compartilhe de forma autêntica**
Divulgue produtos que você realmente usa ou que fazem sentido para o seu público. Seguidoras percebem quando a indicação é genuína — e quando não é.
---
## Como organizar os links de afiliado na sua bio
Uma das melhores formas de aproveitar links de afiliado é mantê-los sempre visíveis na sua página do SpicyLinks.
**Estrutura sugerida:**
```
🛒 Favoritos que uso (lista de afiliados)
→ Cuidados com a pele que eu amo
→ Meus itens de roupa favoritos do mês
→ Equipamentos que uso para produzir
```
Você pode criar uma seção específica de "produtos que recomendo" na sua página, com links de afiliado para cada categoria.
**Vantagem do SpicyLinks:** Com o plano Premium+Afiliados, você pode incluir links de produto com tracking, vendo exatamente quantas pessoas clicam em cada recomendação.
---
## Como divulgar sem parecer forçada
Este é o ponto que diferencia criadoras que conseguem renda com afiliados das que irritam a audiência tentando vender o tempo todo.
**Regras práticas:**
**Só indique o que você genuinamente usa ou aprovaria usar**
Se você nunca testou o produto, não é indicação — é anúncio. Sua audiência sente a diferença, e um produto ruim indicado por você vai custar credibilidade.
**Seja transparente sobre a parceria**
Quando você usa um link de afiliado, é boa prática mencionar isso. Algo simples como "link de afiliado — não custa nada extra pra você, mas me ajuda" é suficiente. Além de ser honesta, essa transparência costuma aumentar — não diminuir — a conversão.
**Contextualize a indicação**
"Esse protetor solar é do meu link de afiliados" converte menos do que "Uso esse protetor todo dia e nunca descamou no meu rosto — link pra comprar abaixo". O contexto cria o desejo.
**Não exagere na frequência**
Se todo post é uma indicação de produto, sua audiência passa a ignorar. Reserve as indicações de afiliados para momentos em que surgem naturalmente — quando você realmente está usando o produto ou quando alguém pergunta.
---
## Quanto dá para ganhar?
A resposta honesta é: depende muito da audiência e de como você usa.
Uma criadora com 10 mil seguidores engajados pode ganhar mais com afiliados do que outra com 100 mil seguidores que não engaja.
Para ter uma referência:
- Um link de afiliado para produto de R$ 150 com 5% de comissão = R$ 7,50 por venda
- Se 20 seguidoras compram num mês = R$ 150
- Com 5 produtos diferentes gerando 20 vendas cada = R$ 750 por mês em comissões
Isso é adicional à renda de assinaturas e publipost, sem trabalho extra além de compartilhar links que você já usaria para indicar os produtos mesmo sem ser paga.
Com o tempo e com o crescimento da audiência, esses números escalam.
---
## Erros para evitar
**Indicar produtos de má qualidade por comissão mais alta**
A comissão não compensa a perda de credibilidade com a audiência.
**Usar links de afiliado em stories sem salvar nos destaques**
Stories somem em 24 horas. Mantenha os links de afiliado sempre disponíveis na sua bio do SpicyLinks.
**Não acompanhar o que está convertendo**
Use as métricas disponíveis nos programas de afiliados e na sua página do SpicyLinks para saber quais produtos geram mais cliques e vendas.
**Esquecer de atualizar quando um produto muda ou sai de linha**
Links quebrados geram frustração e custam credibilidade.
---
## O que pode não funcionar
- **Indicar produtos sem ter testado:** uma indicação de produto ruim custa muito mais do que a comissão ganha — a credibilidade com a audiência é seu ativo mais valioso
- **Não deixar os links de afiliado sempre visíveis:** guardar para posts esporádicos reduz muito a conversão; links fixos na bio trabalham 24h por dia
- **Esperar resultados rápidos:** afiliados são renda que cresce com o tempo e com o crescimento da audiência — nos primeiros meses os valores são pequenos, mas escalam
- **Não declarar que é link de afiliado:** além de ser a prática honesta, a transparência aumenta a confiança e frequentemente melhora a conversão
---
## Leia também
- [Os Melhores Links para Colocar na Sua Bio do Instagram](/artigos/os-melhores-links-para-sua-bio-do-instagram)
- [Como Configurar Sua Bio para Ganhar Mais Seguidores](/artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores)
- [SpicyLinks vs Linktree: Qual é Melhor para Criadores?](/artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores)
---
**Pronta para adicionar afiliados à sua estratégia de renda?**
Com o plano Premium+Afiliados do SpicyLinks, você organiza e acompanha todos os seus links de produto em um só lugar.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,217 +0,0 @@
---
title: "Como Monetizar seu Conteúdo com uma Bio Link Profissional"
description: "Como criadoras de conteúdo adulto podem usar uma bio link bem estruturada para aumentar conversões, diversificar fontes de receita e construir uma marca pessoal sólida."
keywords: "monetizar conteudo criadora, bio link conversao, aumentar assinantes, diversificar renda criadora, marca pessoal criadora"
author: "Equipe SpicyLinks"
date: 2026-01-28
lastMod: 2026-01-28
image: "/images/artigos/monetizacao-criadora.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Monetizar seu Conteúdo com uma Bio Link Profissional
A maioria das criadoras de conteúdo perde entre 30% e 50% das conversões possíveis por um motivo simples: a jornada entre "interesse" e "assinatura" tem fricção demais.
Alguém descobre seu perfil no Instagram, quer saber mais, clica na bio — e encontra um link confuso, desatualizado ou que leva para apenas uma plataforma. Ela vai embora antes de virar assinante.
Uma bio link profissional elimina essa fricção. Este artigo mostra como usar isso para monetizar melhor o que você já produz.
---
## O Problema com "Link na Bio" Sem Estratégia
"Link na bio" virou uma instrução automática que a maioria das criadoras usa sem pensar. Mas o que está nesse link faz toda a diferença.
**Cenário comum:**
- Bio diz "link na bio"
- Link vai direto para uma única plataforma de assinatura
- Visitante que não quer assinar imediatamente não encontra alternativa
- Saída sem conversão
**O que uma bio link estratégica faz:**
- Oferece múltiplos pontos de conversão (assinatura, lista de desejos, redes sociais)
- Captura pessoas em diferentes estágios de interesse
- Permite que a visitante escolha como se relacionar com você antes de decidir assinar
Uma pessoa que ainda não está pronta para assinar pode:
- Te seguir no Instagram
- Adicionar itens da sua lista de desejos
- Começar a te acompanhar no Twitter/X
- Voltar e assinar depois
---
## As Fontes de Receita que uma Bio Link Deve Cobrir
Uma boa bio link não é apenas sobre assinatura. É sobre diversificação de receita.
### 1. Assinatura em Plataforma Exclusiva
A principal fonte de receita recorrente. É o link que deve aparecer primeiro e com título mais atrativo.
**Exemplos de títulos que convertem mais:**
❌ "OnlyFans"
✅ "Conteúdo Exclusivo que Não Posto em Lugar Nenhum 🔥"
❌ "Minha Plataforma"
✅ "Assinar — Novo Conteúdo Toda Semana ❤️"
A diferença é que o segundo título comunica **valor** antes de comunicar a ação.
### 2. Lista de Desejos
Fonte de receita passiva que muitas criadoras subestimam. Uma lista de desejos bem mantida (Amazon ou similar) gera presentes de fãs de forma contínua.
**Para maximizar:**
- Atualize quinzenalmente
- Varie itens de diferentes faixas de preço (de R$ 30 a R$ 300+)
- Quando receber presente, agradeça publicamente (stories, tweet) — incentiva outros fãs
### 3. Segunda Plataforma de Assinatura
Criadoras com audiência consolidada frequentemente usam duas plataformas com propostas diferentes:
- Plataforma A: conteúdo mais acessível (preço menor, público maior)
- Plataforma B: conteúdo premium exclusivo (preço maior, público menor)
Isso permite capturar clientes de diferentes poder aquisitivo e criar um funil de upgrade.
### 4. Conteúdo Avulso / Pay-Per-View
Algumas plataformas permitem vender conteúdo específico sem assinatura. Se você usa esse modelo, inclua um link direto.
### 5. Parcerias e Publipost
Um email profissional bem posicionado na bio link — especificando que é para parcerias — pode gerar propostas de marcas relevantes para o seu nicho.
---
## A Psicologia da Ordem dos Links
A posição dos links na sua página importa mais do que parece.
### O Princípio da Atenção Decrescente
Visitantes leem de cima para baixo e perdem atenção progressivamente. As primeiras 3 posições recebem a maior parte dos cliques.
**Estrutura recomendada:**
```
Posição 1: Assinatura principal (sua principal receita)
Posição 2: Segunda plataforma ou lista de desejos
Posição 3: Conteúdo free mais popular (Instagram ou Twitter/X)
Posição 4: Segundo perfil social
Posição 5+: Outros links relevantes
```
### O Papel dos Links Gratuitos
Links gratuitos (Instagram, Twitter/X) na sua bio link parecem contraditórios — você está mandando pessoas para onde não ganha dinheiro.
Mas eles cumprem um papel importante: **construção de confiança**.
Uma visitante que ainda não conhece seu trabalho pode preferir primeiro te seguir no Instagram antes de pagar. Se o Instagram convence, ela volta e assina. Se não estiver na bio link, ela não te segue e você a perde.
---
## Sua Bio Link como Cartão de Visita Profissional
### Para Parceiros e Marcas
Quando uma marca te encontra e quer fazer publipost, ela vai avaliar sua profissionalidade. Uma página organizada no SpicyLinks — com bio clara, links funcionando, aparência coesa — passa uma impressão completamente diferente de um perfil bagunçado.
Isso afeta diretamente o valor que você consegue cobrar por parcerias.
### Para Plataformas de Afiliados
O **Plano Premium+Afiliados** do SpicyLinks permite incluir links de produto com tracking. Se você recomenda produtos (roupas, acessórios, equipamentos para produção de conteúdo), pode monetizar essas recomendações com links de afiliados organizados na mesma página.
---
## Usando Métricas para Otimizar sua Receita
O Dashboard do SpicyLinks mostra exatamente quantas pessoas clicam em cada link. Use isso ativamente.
### Diagnósticos comuns
**Assinatura recebe poucos cliques, mas há muitas visitas à página:**
- O título do link não está comunicando valor
- A descrição da página não está aquecendo a audiência antes dos links
- Tente mudar o título e o copy da descrição
**Lista de desejos tem muitos cliques mas poucos presentes:**
- Itens podem estar fora do alcance do seu público
- Adicione itens de menor valor (R$ 20-50)
**Instagram recebe mais cliques do que assinatura:**
- Pode ser positivo (construção de audiência) ou negativo (sua audiência prefere te seguir gratuitamente)
- Considere criar mais urgência no título do link de assinatura
### Ciclo de otimização
```
Mês 1: Configure os links básicos
Mês 2: Analise o que está convertendo
Mês 3: Mude os títulos dos links mais fracos
Mês 4: Compare os resultados
Mês 5: Mantenha o que funciona, teste variações
```
---
## Construindo uma Marca Pessoal de Longo Prazo
Criadoras que pensam em carreira de longo prazo não dependem apenas de assinaturas mensais. Elas constroem uma marca pessoal que tem valor independente de qualquer plataforma.
Uma plataforma pode mudar suas políticas, banir contas ou fechar. Sua marca pessoal, construída ao longo de anos, vai com você para qualquer plataforma.
### O que constrói marca pessoal
- **Consistência visual:** mesmas cores, estilo fotográfico e identidade em todas as plataformas
- **Voz única:** um jeito de se comunicar que é reconhecidamente seu
- **Presença além do conteúdo pago:** conteúdo gratuito de qualidade que faz as pessoas te recomendar
- **Relacionamento genuíno com a audiência:** responder comentários, interagir nos stories, ouvir o que as pessoas querem ver
Uma bio link profissional é o hub de toda essa presença. É onde tudo converge.
---
## Privacidade e Segurança
Ao construir sua presença no SpicyLinks, proteja-se:
**Nunca inclua na bio link:**
- Seu endereço ou cidade específica
- Número de documento pessoal
- Informações bancárias diretamente
**Inclua apenas:**
- Plataformas profissionais já públicas
- Email de trabalho (diferente do pessoal)
- WhatsApp comercial (número diferente do pessoal, se optar por incluir)
A separação entre vida pessoal e profissional é uma das decisões mais importantes para criadoras com carreira de longo prazo.
---
## Conclusão
Uma bio link profissional não é um detalhe estético — é infraestrutura de negócio. Criadoras que tratam a presença digital com seriedade, organizam seus links estrategicamente e monitoram o que funciona convertem mais visitantes em receita com o mesmo esforço de criação de conteúdo.
**Próximos passos:**
1. Configure sua página no SpicyLinks
2. Organize seus links por prioridade de receita
3. Atualize todas as suas bios com o novo link
4. Analise as métricas depois de 30 dias
5. Otimize o que não está convertendo
O conteúdo que você cria merece uma vitrine profissional.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -1,212 +0,0 @@
---
title: "Os Melhores Links para Colocar na Sua Bio do Instagram"
description: "Quais links realmente valem a pena ter na sua bio? Uma análise prática sobre o que coloca, o que evita e como organizar para converter mais — seja em seguidores, assinantes ou vendas."
keywords: "links bio instagram, o que colocar na bio, melhores links bio criadora, bio link instagram criador, links que convertem instagram"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/links-bio-instagram.jpg"
culture: "pt-BR"
category: "criadores"
---
# Os Melhores Links para Colocar na Sua Bio do Instagram
O Instagram permite um único link na bio. Então a maioria das criadoras coloca uma página com vários links — e essa decisão, quando bem feita, multiplica as possibilidades de conversão.
Mas quais links colocar? Em que ordem? O que realmente faz diferença?
Este artigo responde essas perguntas com base no que funciona na prática.
---
## Este artigo é para você se...
- Você tem uma página de links mas não sabe se está aproveitando bem o espaço
- Você adiciona links sem critério e quer entender qual ordem gera mais resultados
- Você quer saber o que realmente converte — e o que está desperdiçando o espaço dos primeiros cliques
- Você acabou de criar sua conta e quer montar a bio já com uma estrutura inteligente
**O que você encontra aqui:** a regra dos três primeiros links, quais links valem a pena incluir por objetivo, o que evitar, modelos prontos para diferentes estratégias e como manter a página atualizada.
---
## A regra dos primeiros três links
Pesquisas de comportamento em páginas de links mostram um padrão consistente: os três primeiros links recebem entre 60% e 80% de todos os cliques. O quarto em diante recebe cada vez menos atenção.
Isso significa que a decisão mais importante na sua bio de links não é quantos links ter, mas *quais links ficam nas três primeiras posições*.
Defina seu objetivo antes de ordenar:
- Se o objetivo é assinantes: plataforma exclusiva no topo
- Se o objetivo é crescer no Instagram: Instagram no topo (para quem chega por outra rede)
- Se o objetivo é vendas: loja ou lista de desejos no topo
---
## Os links que mais valem a pena
### 1. Sua plataforma de conteúdo exclusivo
Se você tem uma plataforma de assinatura, este deve ser o primeiro link. É a sua principal fonte de receita e merece a posição de maior visibilidade.
**Como nomear:** Não coloque só o nome da plataforma. Crie um título que comunique valor:
✅ "Conteúdo exclusivo que não posto em lugar nenhum 🔥"
✅ "Assinar — novidade toda semana ❤️"
✅ "Acesso VIP ao meu conteúdo especial"
❌ "OnlyFans"
❌ "Minha plataforma"
❌ "Link aqui"
### 2. Instagram
Parece estranho colocar o link do Instagram na bio do próprio Instagram, mas faz sentido em dois casos:
**Caso 1:** Você compartilha sua bio em outros lugares (WhatsApp, Twitter/X, TikTok). Quem chega por essas plataformas pode não te seguir no Instagram ainda.
**Caso 2:** Você tem um perfil secundário ou um perfil profissional separado que quer divulgar.
Se você só tem um Instagram e compartilha a bio principalmente por lá, esse link pode ir para posições mais baixas ou nem aparecer.
### 3. Twitter/X ou TikTok
Se você tem presença ativa nessas plataformas e quer crescer lá também, inclua. Escolha uma ou duas — não tente colocar todas as redes.
**Dica:** Coloque a plataforma onde seu conteúdo mais converte ou onde você tem mais potencial de crescimento.
### 4. Lista de desejos
Uma lista de desejos (Amazon Wishlist ou similar) é receita passiva. Fãs que querem presentear encontram aqui o que você quer. Quanto mais visível, mais presente funciona.
**Como nomear:**
✅ "Minha lista de desejos 🎁"
✅ "Me presentear? Tem aqui 🥰"
### 5. WhatsApp ou Telegram VIP
Se você tem um grupo privado de fãs mais próximos, coloque o link de acesso. Funciona bem como um nível intermediário — entre seguir gratuitamente e assinar o conteúdo exclusivo.
### 6. YouTube
Se você posta conteúdo gratuito no YouTube (vlogs, tutoriais, bastidores), inclua o canal. Conteúdo gratuito de qualidade constrói confiança e eventualmente converte em assinante.
### 7. Email para parcerias
Se você quer fechar contratos com marcas, um email profissional dedicado a parcerias é fundamental. Coloque com um título claro:
✅ "Parcerias e publipost → email aqui"
✅ "Contato comercial"
---
## Links que você provavelmente não precisa incluir
**Linktree ou outras ferramentas de bio link além do SpicyLinks**
Não faz sentido ter um link para outra página de links dentro da sua página de links. Centralize tudo em um lugar só.
**Facebook**
A menos que você tenha uma página ativa e relevante no Facebook, não precisa incluir. A plataforma tem relevância muito menor para o público jovem que consome conteúdo de criadores.
**Snapchat**
Plataforma com uso muito específico. Inclua apenas se for uma parte real da sua estratégia.
**Link para post específico**
Links para posts individuais ficam desatualizados rápido. Prefira linkar para o seu perfil ou canal — assim o visitante sempre vê o conteúdo mais recente.
---
## A questão da quantidade: menos é mais?
Não necessariamente. A pergunta certa não é "quantos links", mas "cada link aqui tem um propósito claro?"
Uma página com 4 links bem escolhidos converte melhor do que uma com 12 links desorganizados onde a pessoa não sabe o que clicar.
**Regra prática:** Se você hesita se um link deve estar na página, provavelmente não precisa estar.
---
## Organização por objetivo: modelos prontos
**Para quem quer maximizar assinaturas:**
```
1. Conteúdo exclusivo (plataforma principal)
2. Segundo perfil (plataforma alternativa)
3. Lista de desejos
4. Instagram
5. Twitter/X
6. Email para parcerias
```
**Para quem quer crescer em seguidores:**
```
1. Instagram
2. TikTok
3. Twitter/X
4. Conteúdo exclusivo
5. Lista de desejos
6. Email para parcerias
```
**Para quem quer equilibrar crescimento e receita:**
```
1. Conteúdo exclusivo
2. Instagram
3. TikTok ou YouTube
4. Lista de desejos
5. Email para parcerias
```
---
## Mantendo a página atualizada
Sua página de links precisa de manutenção periódica. A cada 2-4 semanas, verifique:
- Todos os links ainda funcionam?
- Os títulos continuam relevantes?
- Surgiu alguma nova plataforma ou oferta que merece aparecer?
- Algum link está desatualizado e deveria ser removido?
Uma página com links quebrados ou informações antigas passa uma impressão de descuido — o contrário do que você quer para a sua imagem profissional.
---
## Testando o que funciona
Se você usa o SpicyLinks, as métricas mostram quantas pessoas clicam em cada link. Use esses dados para tomar decisões:
**Se um link tem muitas visitas mas poucos cliques:** O título pode não estar sendo atraente o suficiente. Teste uma versão diferente por 2 semanas e compare.
**Se os links de baixo nunca são clicados:** Eles estão ocupando espaço de links que poderiam converter mais. Considere remover ou reorganizar.
**Se você recebe muitas visitas mas conversão baixa:** O problema pode ser na descrição da página ou na coerência entre o que as pessoas esperam e o que encontram.
---
## O que pode não funcionar
- **Título genérico nos links:** "Instagram" ou "OnlyFans" não convence ninguém — o título precisa comunicar o que a pessoa vai encontrar ao clicar
- **Links que apontam para conteúdo específico desatualizado:** um post antigo ou uma live encerrada gera frustração; prefira links para perfis ou canais completos
- **Não testar variações:** a ordem e o título dos links que você configurou hoje não são definitivos — teste, analise e ajuste
- **Ignorar as métricas:** se um link nunca recebe cliques, ele está ocupando o espaço de algo que poderia converter
---
## Leia também
- [Como Configurar Sua Bio para Ganhar Mais Seguidores](/artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores)
- [SpicyLinks vs Linktree: Qual é Melhor para Criadores?](/artigos/spicylinks-vs-linktree-qual-e-melhor-para-criadores)
- [Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados](/artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados)
---
**Configure sua bio com os links certos e veja a diferença.**
Crie ou atualize sua página no SpicyLinks com uma estratégia clara.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,210 +0,0 @@
---
title: "SpicyLinks vs Linktree: Qual é Melhor para Criadores?"
description: "Uma comparação direta entre SpicyLinks e Linktree para criadores de conteúdo brasileiros — preço, recursos, suporte e o que cada plataforma realmente entrega."
keywords: "spicylinks vs linktree, linktree alternativa brasil, melhor bio link criadora, linktree concorrente, bio link para criadores"
author: "Equipe SpicyLinks"
date: 2026-04-30
lastMod: 2026-04-30
image: "/images/artigos/spicylinks-vs-linktree.jpg"
culture: "pt-BR"
category: "criadores"
---
# SpicyLinks vs Linktree: Qual é Melhor para Criadores?
O Linktree é a plataforma de bio link mais conhecida do mundo. Então por que existem alternativas — e por que algumas criadoras preferem usar o SpicyLinks?
Este artigo faz uma comparação direta, sem rodeios, para ajudar você a decidir o que faz mais sentido para a sua situação.
---
## Este artigo é para você se...
- Você está avaliando qual plataforma de bio link usar e não sabe a diferença entre as opções
- Você já usa o Linktree mas está questionando se vale a pena continuar — especialmente pelo custo em dólar
- Você quer uma comparação direta, sem papo de vendedor, sobre o que cada plataforma entrega
- Você quer saber se migrar faz sentido e se o processo é complicado
**O que você encontra aqui:** pontos fortes e fracos de cada plataforma, comparação de preços real (dólar vs real), diferenças nos recursos principais e quando cada uma faz sentido.
---
## O Linktree: o que é e para quem serve bem
O Linktree foi lançado em 2016 e hoje tem mais de 40 milhões de usuários. É a escolha padrão para quem quer algo rápido, reconhecível e sem muita configuração.
**Pontos fortes do Linktree:**
- Reconhecimento de marca — todo mundo sabe o que é
- Versão gratuita disponível
- Interface simples, fácil de aprender
- Integrações com várias plataformas
- Disponível em muitos idiomas
**Limitações do Linktree:**
- Preços em dólar (caro para o bolso brasileiro)
- Suporte em inglês
- Não tem moderação de conteúdo — qualquer conteúdo pode ser hospedado
- Termos de serviço genéricos, não adaptados ao criador brasileiro
- Analytics básico nos planos gratuito e mais barato
- URL padrão é `linktr.ee/seu-nome` — não tem URL personalizada com contexto
---
## O SpicyLinks: para quem foi feito
O SpicyLinks foi criado especificamente para criadores de conteúdo brasileiros que precisam de uma plataforma que entenda a realidade local — em termos de preços, moeda, suporte e tipos de conteúdo.
**Diferenciais principais:**
- Preços em reais, sem variação cambial
- URL com contexto: `spicylinks.site/categoria/seu-nome`
- Suporte em português
- Plano com links de afiliados nativos
- Analytics de cliques por link
- Moderação que protege a plataforma e os criadores
- Temas visuais adaptados para o nicho de criadores
---
## Comparação direta
### Preço
| Plano | Linktree | SpicyLinks |
|-------|----------|-----------|
| Gratuito/Trial | Sim (limitado) | 7 dias grátis |
| Plano básico | US$ 5/mês (~R$ 28) | R$ 12,90/mês |
| Plano intermediário | US$ 9/mês (~R$ 51) | R$ 25,90/mês |
| Plano completo | US$ 24/mês (~R$ 136) | R$ 29,90/mês |
| Com afiliados | Não incluso | R$ 34,90/mês |
*Cotação do dólar varia. Preços Linktree podem ter se alterado desde esta publicação.*
**Vantagem:** SpicyLinks em todos os planos — com preço significativamente menor em reais e sem risco de variação cambial.
---
### URL personalizada
**Linktree:** Sua URL fica `linktr.ee/seu-nome`. Não tem contexto — qualquer pessoa pode ter essa URL, de qualquer nicho.
**SpicyLinks:** Sua URL inclui sua categoria: `spicylinks.site/modelos/seu-nome` ou `spicylinks.site/influencers/seu-nome`. Isso cria contexto e credibilidade — a URL já comunica quem você é antes de a pessoa clicar.
**Vantagem:** SpicyLinks para quem quer uma presença mais profissional e contextualizada.
---
### Links de afiliados
**Linktree:** Você pode colocar links de afiliados manualmente como qualquer outro link, mas não tem funcionalidade específica para isso — sem tracking por link dentro da plataforma.
**SpicyLinks:** O Plano Premium+Afiliados inclui links de produto com tracking nativo. Você vê exatamente quantas pessoas clicam em cada link de afiliado, o que permite otimizar o que está sendo divulgado.
**Vantagem:** SpicyLinks para quem trabalha ativamente com marketing de afiliados.
---
### Analytics
**Linktree gratuito:** Apenas total de views e cliques.
**Linktree Pro:** Análise por link, dados demográficos.
**SpicyLinks:** Analytics de cliques por link disponível nos planos pagos. Dados de onde as pessoas chegam à sua página.
**Vantagem:** Similar nos planos pagos de ambas.
---
### Temas visuais
**Linktree:** Dezenas de temas, mas muitos são genéricos e o resultado final costuma parecer igual ao de milhares de outras pessoas.
**SpicyLinks:** Temas desenvolvidos com a estética do criador de conteúdo em mente. Nos planos superiores, temas premium exclusivos. Menos temas no total, mas mais adequados ao nicho.
**Vantagem:** Depende da preferência — Linktree tem mais quantidade, SpicyLinks tem mais adequação ao nicho.
---
### Suporte
**Linktree:** Suporte em inglês, por email, com tempo de resposta que pode demorar dias. Para usuários fora do plano Pro, suporte bastante limitado.
**SpicyLinks:** Suporte em português. Planos Premium têm suporte prioritário e acesso via Telegram.
**Vantagem:** SpicyLinks para quem precisa de suporte em português.
---
### Confiabilidade e moderação
**Linktree:** Plataforma estável e com alta disponibilidade. Porém, por ter conteúdo de qualquer tipo, há riscos de mudanças na política de uso que afetem criadores de conteúdo adulto.
**SpicyLinks:** Moderação de conteúdo garante que a plataforma se mantém organizada. Criadores aprovados na moderação têm mais estabilidade.
**Vantagem:** SpicyLinks para criadores que querem uma plataforma dedicada ao seu tipo de conteúdo.
---
## Quando o Linktree pode ser a escolha certa
Seria desonesto dizer que o Linktree não tem vantagens em nenhum cenário. Faz sentido usar o Linktree se:
- Você já tem conta antiga e não quer migrar o histórico
- Você produz conteúdo de vários nichos completamente diferentes e precisa de muitos temas
- Você tem audiência internacional e quer a plataforma mais reconhecida globalmente
- Você quer começar gratuitamente sem compromisso de nenhum período
Para todos esses casos, o Linktree resolve. Mas para a maioria das criadoras brasileiras, o custo maior (em dólar) e o suporte apenas em inglês são desvantagens reais.
---
## Quando o SpicyLinks é a escolha certa
O SpicyLinks faz mais sentido se:
- Você é criadora de conteúdo e quer uma plataforma feita para o seu nicho
- Você prefere pagar em reais sem variação cambial
- Você trabalha com links de afiliados e quer acompanhar o desempenho de cada um
- Você valoriza suporte em português
- Você quer uma URL com contexto profissional
- Você está começando e quer um plano básico acessível
---
## Migrar do Linktree é difícil?
Não. O processo leva menos de 30 minutos:
1. Crie sua conta no SpicyLinks
2. Adicione os mesmos links que você tem no Linktree
3. Configure o tema visual
4. Envie para moderação e aguarde aprovação
5. Quando aprovada, atualize o link da bio do Instagram e de qualquer outro lugar onde você divulga
Seus seguidores não percebem a mudança — só encontram uma página de links mais organizada.
---
## Conclusão
Para criadoras de conteúdo brasileiras que querem uma plataforma feita para o seu nicho, com preço em reais e suporte em português, o SpicyLinks entrega mais valor pelo dinheiro do que o Linktree.
O Linktree é a escolha padrão por reconhecimento de marca. O SpicyLinks é a escolha inteligente para quem pesquisa antes de decidir.
---
## Leia também
- [Como Configurar Sua Bio para Ganhar Mais Seguidores](/artigos/como-configurar-sua-bio-para-ganhar-mais-seguidores)
- [Os Melhores Links para Colocar na Sua Bio do Instagram](/artigos/os-melhores-links-para-sua-bio-do-instagram)
- [Como Criadores de Conteúdo Ganham Dinheiro com Links de Afiliados](/artigos/como-criadores-de-conteudo-ganham-dinheiro-com-afiliados)
---
**Teste você mesma e compare.**
Crie sua página no SpicyLinks — são 7 dias grátis para avaliar sem compromisso.
[Criar meu SpicyLinks →](https://spicylinks.site/)
---
**Última atualização:** Abril 2026

View File

@ -1,334 +0,0 @@
---
title: "Como Configurar seu SpicyLinks: Guia Completo para Criadoras de Conteúdo"
description: "Passo a passo para criadoras de conteúdo adulto criarem uma bio profissional no SpicyLinks, centralizando suas plataformas e aumentando conversões."
keywords: "spicylinks tutorial, bio links criadora, como criar bio conteudo adulto, configurar página criadora"
author: "Equipe SpicyLinks"
date: 2026-01-25
lastMod: 2026-01-25
image: "/images/tutoriais/criadora-spicylinks.jpg"
culture: "pt-BR"
category: "criadores"
---
# Como Configurar seu SpicyLinks: Guia Completo para Criadoras de Conteúdo
Você está no Instagram com 50 mil seguidores, tem conta no Twitter/X, lista de desejos na Amazon e assinatura em plataforma exclusiva — mas sua bio diz apenas "link na bio" e leva para um único lugar. Você está deixando dinheiro na mesa.
Este guia mostra como configurar seu SpicyLinks do zero para centralizar tudo e converter mais visitantes em assinantes e clientes.
---
## Por Que uma Bio Profissional Faz Diferença
### O Funil de Conversão da Criadora
Toda visitante que chega no seu perfil passa por um funil:
```
Perfil Instagram/Twitter
Bio (1 clique)
Página SpicyLinks
Decisão: qual plataforma acessar?
```
Se sua bio link for desorganizada, lenta ou confusa, a visitante desiste antes de chegar no passo final. Cada ponto de atrito custou uma assinante.
### O que uma boa bio link resolve
- ✅ Centraliza todas as suas plataformas em um lugar
- ✅ Você muda um link e atualiza em todos os canais de uma vez
- ✅ Analytics de quais plataformas convertem mais
- ✅ Aparência profissional que gera mais confiança
- ✅ Verificação de idade automática — segurança jurídica
---
## Passo 1: Criar Sua Conta no SpicyLinks
### 1.1. Acesse o Site
Vá para [spicylinks.site](https://spicylinks.site) e clique em **"Entrar"**.
A plataforma possui verificação de idade automática antes de qualquer acesso — isso protege você juridicamente.
### 1.2. Criando sua Conta
Use login com **Google** ou **Microsoft** para facilidade. Recomendamos usar um email dedicado ao trabalho de criação de conteúdo, separado do email pessoal.
---
## Passo 2: Configurando sua Página
### Escolhendo o Nome da Página
Use o nome que sua audiência já conhece — o mesmo username que você usa no Instagram ou Twitter/X:
```
✅ Luna Rodrigues
✅ Ana_V Oficial
✅ Mel Santos — Criadora
```
Evite nomes genéricos que não te identificam.
### Escolhendo o Slug (URL)
Sua URL será:
```
spicylinks.site/modelo/seu-slug
```
Opções:
```
luna-rodrigues → spicylinks.site/modelo/luna-rodrigues
anav-oficial → spicylinks.site/modelo/anav-oficial
mel-santos-cr → spicylinks.site/modelo/mel-santos-cr
```
> **Dica:** Escolha algo fácil de ditar e memorizar. Você vai falar esse link em vídeos e stories.
### Escrevendo a Descrição
Seja direta, confiante e deixe claro o que sua audiência encontra aqui:
**Exemplo eficaz:**
```
Luna Rodrigues 🔥
Conteúdo exclusivo, bastidores e lista de desejos.
Links de todas as minhas plataformas abaixo 👇
Assinantes especiais: acesso prioritário ao conteúdo inédito.
```
**Outro exemplo:**
```
Criadora de conteúdo desde 2021 ✨
Fotos, vídeos e muito mais nas plataformas abaixo.
Lista de desejos atualizada semanalmente 🎁
```
---
## Passo 3: Montando Seus Links
A ordem dos links importa. Coloque primeiro o que mais converte.
### Estrutura Recomendada de Links
**1. ❤️ Assinatura Principal**
```
Título: Assinar Conteúdo Exclusivo ❤️
URL: https://suaplataforma.com/seuusuario
```
> Este deve ser o primeiro link. É sua principal fonte de receita.
**2. ❤️ Segunda Plataforma de Assinatura (se tiver)**
```
Título: Conteúdo Exclusivo — Plataforma 2
URL: https://outraplataforma.com/seuusuario
```
> Algumas criadoras usam plataformas diferentes para faixas de preço distintas.
**3. 🛒 Lista de Desejos**
```
Título: Minha Lista de Desejos 🎁
URL: https://amazon.com.br/hz/wishlist/ls/seu-id
```
> Atualize regularmente. Listas desatualizadas perdem conversão.
**4. 📸 Instagram Principal**
```
Título: Me Seguir no Instagram 📸
URL: https://instagram.com/seuusuario
```
**5. 🐦 Twitter/X**
```
Título: Twitter/X 🔥
URL: https://x.com/seuusuario
```
**6. 🎵 TikTok (se usar)**
```
Título: TikTok 🎵
URL: https://tiktok.com/@seuusuario
```
**7. 📞 WhatsApp (Opcional — para comunicação direta)**
```
Título: Falar Diretamente 💬
URL: https://wa.me/5511999999999
```
> Use com critério. Muitas criadoras preferem não disponibilizar WhatsApp público.
**8. ✉️ Email para Parcerias**
```
Título: Parcerias e Contato Profissional ✉️
URL: mailto:contato@seudominio.com
```
> Separe o contato de fãs do contato profissional.
---
## Passo 4: Escolhendo o Tema Visual
Seu tema deve ser extensão da sua identidade visual. Pense nas cores que você já usa:
- **Rosa/Magenta** → energia, sensualidade, feminilidade
- **Roxo/Dark** → mistério, premium, exclusividade
- **Vermelho/Preto** → ousadia, confiança
- **Dourado/Preto** → luxo, sofisticação
- **Rosa claro/Branco** → elegância, suavidade
O tema pode ser trocado a qualquer momento. Experimente alguns antes de decidir.
---
## Passo 5: Moderação e Aprovação
Após configurar, clique em **"Submeter para Moderação"**. Nossa equipe revisa:
- Verificação de identidade (para conteúdo adulto)
- Confirmação de que os links levam a plataformas legais
- Verificação de que o conteúdo respeita as diretrizes da plataforma
**Plano Premium+Afiliados** tem fila de moderação prioritária — aprovação em até 24 horas.
---
## Passo 6: Divulgando Sua Página
### Instagram e TikTok
Bio atualizada:
```
Luna Rodrigues 🔥
Criadora de conteúdo exclusivo ✨
👇 Todas as minhas plataformas
spicylinks.site/modelo/luna-rodrigues
```
### Twitter/X
Pinned tweet:
```
Links de tudo aqui 👇
spicylinks.site/modelo/luna-rodrigues
✅ Conteúdo exclusivo
✅ Lista de desejos
✅ Bastidores
```
### Stories Frequentes
Toda semana:
- "Link na bio" com Sticker de Link apontando para seu SpicyLinks
- Stories mostrando o que está disponível em cada plataforma
---
## Usando as Métricas para Crescer
O Dashboard do SpicyLinks mostra quais links estão recebendo mais cliques.
**Como interpretar:**
| Situação | O que fazer |
|---|---|
| Assinatura com poucos cliques | Mude o título do link para algo mais atrativo |
| Lista de desejos com muitos cliques | Mantenha sempre atualizada — está convertendo |
| Muitas visitas, poucos cliques | A descrição não está convencendo — reescreva |
| Pico de visitas em um dia | Algum post viralizou — descubra qual e replique |
### Teste A/B Simples
Mude o título de um link, espere 2 semanas e compare:
```
Versão A: "Assinar Conteúdo Exclusivo"
Versão B: "Conteúdo Que Não Posto em Lugar Nenhum 🔥"
```
Títulos com emoção e especificidade geralmente convertem mais.
---
## Dicas para Maximizar Conversão
### 1. Atualize Regularmente
Uma página com links atualizados performa melhor. A audiência percebe quando algo está desatualizado.
**Sugestão de rotina:**
- Toda segunda: verificar se todos os links funcionam
- Toda semana: atualizar link da série ou conteúdo em destaque
- Todo mês: revisar a descrição e o tema
### 2. Mencione seu SpicyLinks em Vídeos
Em cada vídeo, no final:
```
"Todos os meus links estão no meu SpicyLinks —
link na bio, é só clicar!"
```
### 3. Crie Links Temporários para Promoções
Se você está fazendo uma promoção de assinatura por tempo limitado, crie um link específico e destaque no topo da página durante o período.
### 4. Foto de Perfil Profissional
Use a mesma foto que você usa no Instagram principal. A consistência entre plataformas gera mais confiança.
---
## Segurança e Privacidade
O SpicyLinks possui verificação de idade automática em todas as páginas. Isso significa:
- ✅ Menores de idade são bloqueados antes de acessar seu conteúdo
- ✅ Você tem proteção jurídica adicional
- ✅ Maior segurança para sua audiência e para você
**Nunca coloque:**
- Seu endereço residencial
- Número de CPF ou documentos pessoais
- Informações que identifiquem sua localização exata
---
## Checklist Final
**Antes de submeter para moderação:**
- [ ] Nome é o mesmo que uso nas redes sociais?
- [ ] URL (slug) é simples e fácil de lembrar?
- [ ] Descrição é atrativa e deixa claro o que ofereço?
- [ ] Links de assinatura estão no topo?
- [ ] Todos os links foram testados e funcionam?
- [ ] Lista de desejos está atualizada?
- [ ] Foto de perfil é a mesma das redes principais?
- [ ] Tema visual combina com minha identidade?
**Após aprovação:**
- [ ] Bio do Instagram atualizada com o link?
- [ ] Bio do Twitter/X atualizada?
- [ ] TikTok bio atualizada (se usar)?
- [ ] Pinned tweet com o link (Twitter/X)?
- [ ] Primeiro story anunciando a nova bio link?
---
## Conclusão
Sua página no SpicyLinks é o centro da sua presença digital como criadora. Em vez de perder seguidores que não sabem como te encontrar em cada plataforma, você oferece uma experiência limpa e profissional com um link só.
Criadoras com bio link profissional convertem mais porque reduzem a fricção no caminho entre "interesse" e "assinatura".
[Criar meu SpicyLinks agora →](https://spicylinks.site/)
---
**Última atualização:** Janeiro 2026

View File

@ -5,13 +5,9 @@ using BCards.Web.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Configuration;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Linq;
using MongoDB.Bson;
using System.Collections.Generic;
namespace BCards.Web.Controllers;
@ -27,11 +23,9 @@ public class AdminController : Controller
private readonly IEmailService _emailService;
private readonly ILivePageService _livePageService;
private readonly IImageStorageService _imageStorage;
private readonly IDocumentStorageService _documentStorage;
private readonly IPaymentService _paymentService;
private readonly IDowngradeService _downgradeService;
private readonly ILogger<AdminController> _logger;
private readonly IConfiguration _configuration;
public AdminController(
IAuthService authService,
@ -42,11 +36,9 @@ public class AdminController : Controller
IEmailService emailService,
ILivePageService livePageService,
IImageStorageService imageStorage,
IDocumentStorageService documentStorage,
IPaymentService paymentService,
IDowngradeService downgradeService,
ILogger<AdminController> logger,
IConfiguration configuration)
ILogger<AdminController> logger)
{
_authService = authService;
_userPageService = userPageService;
@ -56,11 +48,9 @@ public class AdminController : Controller
_emailService = emailService;
_livePageService = livePageService;
_imageStorage = imageStorage;
_documentStorage = documentStorage;
_paymentService = paymentService;
_downgradeService = downgradeService;
_logger = logger;
_configuration = configuration;
}
@ -185,11 +175,7 @@ public class AdminController : Controller
AvailableCategories = categories,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
AllowProductLinks = planLimitations.AllowProductLinks,
AllowDocumentUpload = planLimitations.AllowDocumentUpload,
MaxDocumentsAllowed = planLimitations.MaxDocuments,
Documents = new List<ManageDocumentViewModel>(),
DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay()
AllowProductLinks = planLimitations.AllowProductLinks
};
return View(model);
}
@ -228,12 +214,6 @@ public class AdminController : Controller
return RedirectToAction("Login", "Auth");
userId = user.Id;
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var parsedPlan) ? parsedPlan : PlanType.Trial;
var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString());
model.AllowProductLinks = planLimitations.AllowProductLinks;
model.AllowDocumentUpload = planLimitations.AllowDocumentUpload;
model.MaxDocumentsAllowed = planLimitations.MaxDocuments;
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
// Limpar campos de redes sociais que são apenas espaços (tratados como vazios)
CleanSocialMediaFields(model);
@ -275,13 +255,13 @@ public class AdminController : Controller
TempData["ImageError"] = errorMessage;
// Preservar dados do form e repopular dropdowns
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial;
var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString());
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.MaxLinksAllowed = userPlanType.GetMaxLinksPerPage();
model.AllowProductLinks = planLimitations.AllowProductLinks;
model.AllowDocumentUpload = planLimitations.AllowDocumentUpload;
model.MaxDocumentsAllowed = planLimitations.MaxDocuments;
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
// Preservar ProfileImageId existente se estava editando
if (!model.IsNewPage && !string.IsNullOrEmpty(model.Id))
@ -297,8 +277,6 @@ public class AdminController : Controller
}
}
var (processedDocuments, removedFileIds, newFileIds) = await BuildDocumentsAsync(model, planLimitations);
if (!ModelState.IsValid)
{
var sbError = new StringBuilder();
@ -319,10 +297,6 @@ public class AdminController : Controller
model.Slug = slug;
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.AllowProductLinks = planLimitations.AllowProductLinks;
model.AllowDocumentUpload = planLimitations.AllowDocumentUpload;
model.MaxDocumentsAllowed = planLimitations.MaxDocuments;
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
return View(model);
}
@ -330,6 +304,7 @@ public class AdminController : Controller
{
// CRITICAL: Check if user can create new page (validate MaxPages limit)
var existingPages = await _userPageService.GetUserPagesAsync(user.Id);
var userPlanType = Enum.TryParse<PlanType>(user.CurrentPlan, true, out var planType) ? planType : PlanType.Trial;
var maxPages = userPlanType.GetMaxPages();
if (existingPages.Count >= maxPages)
@ -337,7 +312,6 @@ public class AdminController : Controller
TempData["Error"] = $"Você já atingiu o limite de {maxPages} página(s) do seu plano atual. Faça upgrade para criar mais páginas.";
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
return View(model);
}
@ -353,7 +327,6 @@ public class AdminController : Controller
ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra.");
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
return View(model);
}
@ -364,7 +337,6 @@ public class AdminController : Controller
ModelState.AddModelError("", $"Você excedeu o limite de {model.MaxLinksAllowed} links do seu plano atual.");
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
return View(model);
}
@ -372,7 +344,6 @@ public class AdminController : Controller
{
// Create new page
var userPage = await MapToUserPage(model, user.Id);
userPage.Documents = processedDocuments;
_logger.LogInformation($"Mapped to UserPage: {userPage.DisplayName}, Category: {userPage.Category}, Slug: {userPage.Slug}");
// Set status to Creating for new pages
@ -391,7 +362,6 @@ public class AdminController : Controller
ModelState.AddModelError("", "Erro ao criar página. Tente novamente.");
model.AvailableCategories = await _categoryService.GetAllCategoriesAsync();
model.AvailableThemes = await _themeService.GetAvailableThemesAsync();
model.DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay();
TempData["Error"] = $"Erro ao criar página. Tente novamente. TechMsg: {ex.Message}";
return View(model);
}
@ -401,15 +371,7 @@ public class AdminController : Controller
// Update existing page
var existingPage = await _userPageService.GetPageByIdAsync(model.Id);
if (existingPage == null || existingPage.UserId != user.Id)
{
await CleanupNewDocumentsAsync(newFileIds);
return NotFound();
}
if (!planLimitations.AllowDocumentUpload && existingPage.Documents?.Any() == true)
{
removedFileIds.AddRange(existingPage.Documents.Select(d => d.FileId));
}
// Check if user can create pages (for users with rejected pages)
var canCreatePage = await _moderationService.CanUserCreatePageAsync(user.Id);
@ -435,7 +397,7 @@ public class AdminController : Controller
}
}
await UpdateUserPageFromModel(existingPage, model, processedDocuments);
await UpdateUserPageFromModel(existingPage, model);
// Set status to PendingModeration for updates
existingPage.Status = ViewModels.PageStatus.Creating;
@ -443,21 +405,6 @@ public class AdminController : Controller
await _userPageService.UpdatePageAsync(existingPage);
if (removedFileIds.Count > 0)
{
foreach (var fileId in removedFileIds)
{
try
{
await _documentStorage.DeleteDocumentAsync(fileId);
}
catch (Exception cleanupEx)
{
_logger.LogWarning(cleanupEx, "Erro ao remover documento antigo {FileId}", fileId);
}
}
}
// Token será gerado apenas quando usuário clicar "Testar Página"
// Send email to user
@ -482,10 +429,182 @@ public class AdminController : Controller
}
}
[HttpPost]
[Route("CreatePage")]
public IActionResult CreatePage()
public async Task<IActionResult> CreatePage(CreatePageViewModel model)
{
return RedirectToAction("ManagePage", new { id = "new" });
var user = await _authService.GetCurrentUserAsync(User);
if (user == null)
return RedirectToAction("Login", "Auth");
// Check if user already has a page
var existingPage = await _userPageService.GetUserPageAsync(user.Id);
if (existingPage != null)
return RedirectToAction("EditPage");
if (!ModelState.IsValid)
{
var categories = await _categoryService.GetAllCategoriesAsync();
var themes = await _themeService.GetAvailableThemesAsync();
ViewBag.Categories = categories;
ViewBag.Themes = themes;
return View(model);
}
// Generate slug if not provided
if (string.IsNullOrEmpty(model.Slug))
{
model.Slug = await _userPageService.GenerateSlugAsync(model.Category, model.DisplayName);
}
// Check if slug is available
if (!await _userPageService.ValidateSlugAsync(model.Category, model.Slug))
{
ModelState.AddModelError("Slug", "Esta URL já está em uso. Tente outra.");
var categories = await _categoryService.GetAllCategoriesAsync();
var themes = await _themeService.GetAvailableThemesAsync();
ViewBag.Categories = categories;
ViewBag.Themes = themes;
return View(model);
}
// Check if user can create the requested number of links
var activeLinksCount = model.Links?.Count ?? 0;
if (!await _userPageService.CanCreateLinksAsync(user.Id, activeLinksCount))
{
ModelState.AddModelError("", "Você excedeu o limite de links do seu plano atual.");
var categories = await _categoryService.GetAllCategoriesAsync();
var themes = await _themeService.GetAvailableThemesAsync();
ViewBag.Categories = categories;
ViewBag.Themes = themes;
return View(model);
}
// Convert ViewModel to UserPage
var userPage = new UserPage
{
UserId = user.Id,
DisplayName = model.DisplayName,
Category = model.Category,
BusinessType = model.BusinessType,
Bio = model.Bio,
Slug = model.Slug,
Theme = await _themeService.GetThemeByNameAsync(model.SelectedTheme) ?? _themeService.GetDefaultTheme(),
Links = model.Links?.Select(l => new LinkItem
{
Title = l.Title,
Url = l.Url,
Description = l.Description,
Icon = l.Icon,
IsActive = true,
Order = model.Links.IndexOf(l)
}).ToList() ?? new List<LinkItem>()
};
// Add social media links
var socialLinks = new List<LinkItem>();
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.FacebookUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Facebook",
Url = model.FacebookUrl,
Icon = "fab fa-facebook",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.TwitterUrl))
{
socialLinks.Add(new LinkItem
{
Title = "X / Twitter",
Url = model.TwitterUrl,
Icon = "fab fa-x-twitter",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.InstagramUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Instagram",
Url = model.InstagramUrl,
Icon = "fab fa-instagram",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.TiktokUrl))
{
socialLinks.Add(new LinkItem
{
Title = "TikTok",
Url = model.TiktokUrl,
Icon = "fab fa-tiktok",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.PinterestUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Pinterest",
Url = model.PinterestUrl,
Icon = "fab fa-pinterest",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.DiscordUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Discord",
Url = model.DiscordUrl,
Icon = "fab fa-discord",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
if (!string.IsNullOrEmpty(model.KawaiUrl))
{
socialLinks.Add(new LinkItem
{
Title = "Kawai",
Url = model.KawaiUrl,
Icon = "fas fa-heart",
IsActive = true,
Order = userPage.Links.Count + socialLinks.Count
});
}
userPage.Links.AddRange(socialLinks);
await _userPageService.CreatePageAsync(userPage);
TempData["Success"] = "Página criada com sucesso!";
return RedirectToAction("Dashboard");
}
[HttpGet]
@ -626,8 +745,6 @@ public class AdminController : Controller
private async Task<ManagePageViewModel> MapToManageViewModel(UserPage page, List<Category> categories, List<PageTheme> themes, PlanType userPlanType)
{
var planLimitations = await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString());
return new ManagePageViewModel
{
Id = page.Id,
@ -655,33 +772,10 @@ public class AdminController : Controller
ProductDescription = l.ProductDescription,
ProductDataCachedAt = l.ProductDataCachedAt
}).ToList() ?? new List<ManageLinkViewModel>(),
Documents = page.Documents?.Select((d, index) => new ManageDocumentViewModel
{
Id = string.IsNullOrEmpty(d.Id) ? $"doc_{index}" : d.Id,
DocumentId = d.FileId,
Title = d.Title,
Description = d.Description,
FileName = d.FileName,
FileSize = d.FileSize,
UploadedAt = d.UploadedAt
}).ToList() ?? new List<ManageDocumentViewModel>(),
// Social media fields — extracted from Links so the edit form pre-fills correctly
WhatsAppNumber = page.Links?.FirstOrDefault(l => l.Icon?.Contains("whatsapp") == true)?.Url
?.Replace("https://wa.me/", "").Replace("whatsapp://", "") ?? string.Empty,
FacebookUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("facebook") == true)?.Url ?? string.Empty,
InstagramUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("instagram") == true)?.Url ?? string.Empty,
TwitterUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("twitter") == true)?.Url ?? string.Empty,
TiktokUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("tiktok") == true)?.Url ?? string.Empty,
PinterestUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("pinterest") == true)?.Url ?? string.Empty,
DiscordUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("discord") == true)?.Url ?? string.Empty,
KawaiUrl = page.Links?.FirstOrDefault(l => l.Icon?.Contains("kawai") == true)?.Url ?? string.Empty,
AvailableCategories = categories,
AvailableThemes = themes.Where(t => !t.IsPremium || userPlanType.AllowsCustomThemes()).ToList(),
MaxLinksAllowed = userPlanType.GetMaxLinksPerPage(),
AllowProductLinks = planLimitations.AllowProductLinks,
AllowDocumentUpload = planLimitations.AllowDocumentUpload,
MaxDocumentsAllowed = planLimitations.MaxDocuments,
DocumentUploadPlansDisplay = GetDocumentUploadPlansDisplay()
AllowProductLinks = (await _paymentService.GetPlanLimitationsAsync(userPlanType.ToString())).AllowProductLinks
};
}
@ -730,13 +824,10 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{whatsappDigits}",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = currentOrder++
@ -831,198 +922,7 @@ public class AdminController : Controller
return userPage;
}
private string GetDocumentUploadPlansDisplay()
{
var sections = _configuration.GetSection("Plans").GetChildren();
var friendlyNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var section in sections)
{
if (!bool.TryParse(section["AllowDocumentUpload"], out var allow) || !allow)
continue;
var key = section.Key ?? string.Empty;
var normalizedKey = key.EndsWith("Yearly", StringComparison.OrdinalIgnoreCase)
? key[..^6]
: key;
if (Enum.TryParse<PlanType>(normalizedKey, true, out var planType))
{
friendlyNames.Add(planType.GetDisplayName());
}
else
{
var display = section["Name"] ?? key;
if (!string.IsNullOrWhiteSpace(display))
{
friendlyNames.Add(display);
}
}
}
if (friendlyNames.Count == 0)
return "planos com suporte a documentos";
var orderedNames = friendlyNames.OrderBy(name => name).ToList();
return orderedNames.Count switch
{
1 => orderedNames[0],
2 => string.Join(" e ", orderedNames),
_ => $"{string.Join(", ", orderedNames.Take(orderedNames.Count - 1))} e {orderedNames.Last()}"
};
}
private async Task<(List<PageDocument> Documents, List<string> RemovedFileIds, List<string> NewlyUploadedFileIds)> BuildDocumentsAsync(ManagePageViewModel model, PlanLimitations planLimitations)
{
var documents = new List<PageDocument>();
var removedFileIds = new List<string>();
var newFileIds = new List<string>();
if (!planLimitations.AllowDocumentUpload)
{
return (documents, removedFileIds, newFileIds);
}
if (model.Documents == null || model.Documents.Count == 0)
return (documents, removedFileIds, newFileIds);
for (int index = 0; index < model.Documents.Count; index++)
{
var docVm = model.Documents[index];
if (docVm == null)
continue;
var hasExisting = !string.IsNullOrEmpty(docVm.DocumentId);
if (docVm.MarkForRemoval)
{
if (hasExisting && docVm.DocumentId != null)
removedFileIds.Add(docVm.DocumentId);
continue;
}
if (string.IsNullOrWhiteSpace(docVm.Title))
{
ModelState.AddModelError($"Documents[{index}].Title", "Título é obrigatório");
continue;
}
string fileId = docVm.DocumentId?.Trim() ?? string.Empty;
string fileName = docVm.FileName?.Trim() ?? string.Empty;
long fileSize = docVm.FileSize;
var uploadedAt = docVm.UploadedAt ?? DateTime.UtcNow;
if (hasExisting && string.IsNullOrEmpty(docVm.Id))
{
docVm.Id = ObjectId.GenerateNewId().ToString();
}
if (docVm.DocumentFile != null && docVm.DocumentFile.Length > 0)
{
if (!docVm.DocumentFile.ContentType.Equals("application/pdf", StringComparison.OrdinalIgnoreCase))
{
ModelState.AddModelError($"Documents[{index}].DocumentFile", "Envie um arquivo em PDF.");
continue;
}
if (docVm.DocumentFile.Length > 10 * 1024 * 1024)
{
ModelState.AddModelError($"Documents[{index}].DocumentFile", "Arquivo muito grande. Tamanho máximo: 10MB.");
continue;
}
using var memoryStream = new MemoryStream();
await docVm.DocumentFile.CopyToAsync(memoryStream);
var documentBytes = memoryStream.ToArray();
try
{
fileId = await _documentStorage.SaveDocumentAsync(documentBytes, docVm.DocumentFile.FileName, docVm.DocumentFile.ContentType);
fileName = docVm.DocumentFile.FileName;
fileSize = docVm.DocumentFile.Length;
uploadedAt = DateTime.UtcNow;
newFileIds.Add(fileId);
if (hasExisting && docVm.DocumentId != null)
removedFileIds.Add(docVm.DocumentId);
}
catch (ArgumentException ex)
{
ModelState.AddModelError($"Documents[{index}].DocumentFile", ex.Message);
continue;
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao salvar PDF do usuário");
ModelState.AddModelError($"Documents[{index}].DocumentFile", "Erro ao salvar o PDF. Tente novamente.");
continue;
}
}
else if (!hasExisting)
{
// Nenhum arquivo associado: ignorar
continue;
}
if (string.IsNullOrEmpty(fileId))
{
ModelState.AddModelError($"Documents[{index}].DocumentFile", "Falha ao processar o documento.");
continue;
}
documents.Add(new PageDocument
{
Id = string.IsNullOrEmpty(docVm.Id) || !ObjectId.TryParse(docVm.Id, out _) ? ObjectId.GenerateNewId().ToString() : docVm.Id,
FileId = fileId,
Title = docVm.Title,
Description = docVm.Description,
FileName = fileName,
FileSize = fileSize,
UploadedAt = uploadedAt
});
docVm.DocumentId = fileId;
docVm.FileName = fileName;
docVm.FileSize = fileSize;
docVm.UploadedAt = uploadedAt;
if (string.IsNullOrEmpty(docVm.Id) || !ObjectId.TryParse(docVm.Id, out _))
{
docVm.Id = documents.Last().Id;
}
}
if (planLimitations.MaxDocuments > 0 && documents.Count > planLimitations.MaxDocuments)
{
ModelState.AddModelError("Documents", $"Você pode enviar no máximo {planLimitations.MaxDocuments} documento(s) com seu plano.");
}
return (documents, removedFileIds, newFileIds);
}
private async Task CleanupNewDocumentsAsync(IEnumerable<string> documentIds)
{
if (documentIds == null)
return;
foreach (var docId in documentIds)
{
if (string.IsNullOrEmpty(docId))
continue;
try
{
await _documentStorage.DeleteDocumentAsync(docId);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Erro ao limpar documento temporário {DocumentId}", docId);
}
}
}
private async Task UpdateUserPageFromModel(UserPage page, ManagePageViewModel model, List<PageDocument> documents)
private async Task UpdateUserPageFromModel(UserPage page, ManagePageViewModel model)
{
page.DisplayName = model.DisplayName;
page.Category = model.Category;
@ -1082,13 +982,10 @@ public class AdminController : Controller
if (!string.IsNullOrEmpty(model.WhatsAppNumber))
{
var whatsappDigits = model.WhatsAppNumber
.Replace("https://wa.me/", "").Replace("whatsapp://", "")
.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "");
socialLinks.Add(new LinkItem
{
Title = "WhatsApp",
Url = $"https://wa.me/{whatsappDigits}",
Url = $"https://wa.me/{model.WhatsAppNumber.Replace("+", "").Replace(" ", "").Replace("-", "").Replace("(", "").Replace(")", "")}",
Icon = "fab fa-whatsapp",
IsActive = true,
Order = currentOrder++
@ -1180,8 +1077,6 @@ public class AdminController : Controller
}
page.Links.AddRange(socialLinks);
page.Documents = documents ?? new List<PageDocument>();
}
[HttpPost]

View File

@ -1,50 +0,0 @@
using BCards.Web.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace BCards.Web.Controllers;
public class AgeGateController : Controller
{
private readonly TenantSettings _tenant;
public AgeGateController(IOptions<TenantSettings> tenantSettings)
{
_tenant = tenantSettings.Value;
}
[HttpGet("/age-gate")]
public IActionResult Index(string? returnUrl)
{
if (Request.Cookies.ContainsKey("age_verified"))
return Redirect(LocalUrl(returnUrl));
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost("/age-gate/confirm")]
[ValidateAntiForgeryToken]
public IActionResult Confirm(string? returnUrl)
{
Response.Cookies.Append("age_verified", "1", new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1),
HttpOnly = true,
SameSite = SameSiteMode.Lax,
Secure = true
});
return Redirect(LocalUrl(returnUrl));
}
[HttpPost("/age-gate/deny")]
[ValidateAntiForgeryToken]
public IActionResult Deny()
{
return View("Denied");
}
private string LocalUrl(string? returnUrl) =>
!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl) ? returnUrl : "/";
}

View File

@ -1,64 +0,0 @@
using BCards.Web.Services;
using Microsoft.AspNetCore.Mvc;
namespace BCards.Web.Controllers;
[Route("api/[controller]")]
[ApiController]
public class DocumentController : ControllerBase
{
private readonly IDocumentStorageService _documentStorage;
private readonly ILogger<DocumentController> _logger;
public DocumentController(IDocumentStorageService documentStorage, ILogger<DocumentController> logger)
{
_documentStorage = documentStorage;
_logger = logger;
}
[HttpGet("{documentId}")]
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any)]
public async Task<IActionResult> GetDocument(string documentId)
{
try
{
if (string.IsNullOrEmpty(documentId))
return BadRequest("Documento inválido.");
var documentBytes = await _documentStorage.GetDocumentAsync(documentId);
if (documentBytes == null)
return NotFound();
Response.Headers["Cache-Control"] = "public, max-age=31536000";
Response.Headers["Expires"] = DateTime.UtcNow.AddYears(1).ToString("R");
Response.Headers["ETag"] = $"\"{documentId}\"";
return File(documentBytes, "application/pdf");
}
catch (Exception ex)
{
_logger.LogError(ex, "Erro ao obter documento {DocumentId}", documentId);
return NotFound();
}
}
[HttpDelete("{documentId}")]
public async Task<IActionResult> DeleteDocument(string documentId)
{
if (string.IsNullOrEmpty(documentId))
return BadRequest();
var deleted = await _documentStorage.DeleteDocumentAsync(documentId);
return deleted ? Ok(new { success = true }) : NotFound();
}
[HttpHead("{documentId}")]
public async Task<IActionResult> DocumentExists(string documentId)
{
if (string.IsNullOrEmpty(documentId))
return BadRequest();
var exists = await _documentStorage.DocumentExistsAsync(documentId);
return exists ? Ok() : NotFound();
}
}

View File

@ -2,8 +2,6 @@ using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Xml.Linq;
using BCards.Web.Services;
using BCards.Web.Areas.Tutoriais.Services;
using BCards.Web.Repositories;
namespace BCards.Web.Controllers;
@ -11,21 +9,15 @@ public class SitemapController : Controller
{
private readonly IUserPageService _userPageService;
private readonly ILivePageService _livePageService;
private readonly IMarkdownService _markdownService;
private readonly ICategoryRepository _categoryRepository;
private readonly ILogger<SitemapController> _logger;
public SitemapController(
IUserPageService userPageService,
ILivePageService livePageService,
IMarkdownService markdownService,
ICategoryRepository categoryRepository,
ILogger<SitemapController> logger)
{
_userPageService = userPageService;
_livePageService = livePageService;
_markdownService = markdownService;
_categoryRepository = categoryRepository;
_logger = logger;
}
@ -35,30 +27,13 @@ public class SitemapController : Controller
{
try
{
XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
// 🔥 NOVA FUNCIONALIDADE: Usar LivePages em vez de UserPages
var livePages = await _livePageService.GetAllActiveAsync();
// Artigos
var artigos = await _markdownService.GetAllArticlesAsync("Artigos", "pt-BR");
// Tutoriais por categoria
var categories = await _categoryRepository.GetAllActiveAsync();
var tutorialUrls = new List<XElement>();
foreach (var cat in categories)
{
var tutorials = await _markdownService.GetArticlesByCategoryAsync(cat.Slug, "pt-BR");
foreach (var t in tutorials)
{
tutorialUrls.Add(new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/tutoriais/{cat.Slug}/{t.Slug}"),
new XElement(ns + "lastmod", t.LastMod.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "monthly"),
new XElement(ns + "priority", "0.7")
));
}
}
// Define namespace corretamente para evitar conflitos
XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
// Construir URLs das páginas dinâmicas separadamente para evitar problemas
var dynamicUrls = livePages.Select(page =>
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/page/{page.Category?.Replace(" ", "-")?.ToLower()}/{page.Slug}"),
@ -68,18 +43,10 @@ public class SitemapController : Controller
)
).ToList();
var artigoUrls = artigos.Select(a =>
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/artigos/{a.Slug}"),
new XElement(ns + "lastmod", a.LastMod.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "monthly"),
new XElement(ns + "priority", "0.7")
)
).ToList();
var sitemap = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement(ns + "urlset",
// Add static pages
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/"),
new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")),
@ -92,26 +59,13 @@ public class SitemapController : Controller
new XElement(ns + "changefreq", "weekly"),
new XElement(ns + "priority", "0.9")
),
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/artigos"),
new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "weekly"),
new XElement(ns + "priority", "0.8")
),
new XElement(ns + "url",
new XElement(ns + "loc", $"{Request.Scheme}://{Request.Host}/tutoriais"),
new XElement(ns + "lastmod", DateTime.UtcNow.ToString("yyyy-MM-dd")),
new XElement(ns + "changefreq", "weekly"),
new XElement(ns + "priority", "0.8")
),
artigoUrls,
tutorialUrls,
// Add live pages (SEO-optimized URLs only)
dynamicUrls
)
);
_logger.LogInformation("Generated sitemap with {LivePages} live pages, {Artigos} artigos, {Tutoriais} tutoriais",
livePages.Count, artigos.Count, tutorialUrls.Count);
_logger.LogInformation($"Generated sitemap with {livePages.Count} live pages");
return Content(sitemap.ToString(SaveOptions.DisableFormatting), "application/xml", Encoding.UTF8);
}

View File

@ -73,7 +73,7 @@ public class SubscriptionController : Controller
{
case "immediate_with_refund":
success = await _paymentService.CancelSubscriptionImmediatelyAsync(request.SubscriptionId, true);
message = success ? "Assinatura cancelada com reembolso total. O valor será retornado em até 10 dias úteis." : "Erro ao processar cancelamento.";
message = success ? "Assinatura cancelada. Reembolso será processado manualmente em até 10 dias úteis." : "Erro ao processar cancelamento.";
break;
case "immediate_no_refund":
@ -86,7 +86,7 @@ public class SubscriptionController : Controller
var (_, canRefundPartial, refundAmount) = await _paymentService.CalculateRefundAsync(request.SubscriptionId);
if (success && canRefundPartial && refundAmount > 0)
{
message = $"Assinatura cancelada com reembolso parcial de R$ {refundAmount:F2}. O valor será retornado em até 10 dias úteis.";
message = $"Assinatura cancelada. Reembolso parcial de R$ {refundAmount:F2} será processado manualmente em até 10 dias úteis.";
}
else
{

View File

@ -149,9 +149,7 @@ public class TestToolsController : ControllerBase
AllowProductLinks = source.AllowProductLinks,
SpecialModeration = source.SpecialModeration,
OGExtractionsUsedToday = 0,
LastExtractionDate = null,
AllowDocumentUpload = source.AllowDocumentUpload,
MaxDocuments = source.MaxDocuments
LastExtractionDate = null
};
}

View File

@ -1,58 +0,0 @@
using BCards.Web.Configuration;
using Microsoft.Extensions.Options;
namespace BCards.Web.Middleware;
public class AgeGateMiddleware
{
private readonly RequestDelegate _next;
private readonly bool _isAgeGated;
private static readonly HashSet<string> _excludedPrefixes = new(StringComparer.OrdinalIgnoreCase)
{
"/age-gate",
"/auth",
"/admin",
"/webhook",
"/health",
"/ready",
"/live",
"/lib",
"/css",
"/js",
"/images",
"/favicon"
};
public AgeGateMiddleware(RequestDelegate next, IOptions<TenantSettings> tenantSettings)
{
_next = next;
_isAgeGated = tenantSettings.Value.AgeGated;
}
public async Task InvokeAsync(HttpContext context)
{
if (_isAgeGated && !IsVerified(context) && !IsExcluded(context.Request.Path))
{
var returnUrl = Uri.EscapeDataString(context.Request.Path + context.Request.QueryString);
context.Response.Redirect($"/age-gate?returnUrl={returnUrl}");
return;
}
await _next(context);
}
private static bool IsVerified(HttpContext context) =>
context.Request.Cookies.ContainsKey("age_verified");
private static bool IsExcluded(PathString path)
{
var value = path.Value ?? "";
foreach (var prefix in _excludedPrefixes)
{
if (value.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
}

View File

@ -90,9 +90,7 @@ public class PlanLimitationMiddleware
AllowCustomDomain = false,
AllowMultipleDomains = false,
PrioritySupport = false,
PlanType = "basic",
AllowDocumentUpload = false,
MaxDocuments = 0
PlanType = "basic"
},
"professional" => new Models.PlanLimitations
{
@ -102,9 +100,7 @@ public class PlanLimitationMiddleware
AllowCustomDomain = true,
AllowMultipleDomains = false,
PrioritySupport = false,
PlanType = "professional",
AllowDocumentUpload = false,
MaxDocuments = 0
PlanType = "professional"
},
"premium" => new Models.PlanLimitations
{
@ -114,21 +110,7 @@ public class PlanLimitationMiddleware
AllowCustomDomain = true,
AllowMultipleDomains = true,
PrioritySupport = true,
PlanType = "premium",
AllowDocumentUpload = true,
MaxDocuments = 5
},
"premiumaffiliate" => new Models.PlanLimitations
{
MaxLinks = -1,
AllowCustomThemes = true,
AllowAnalytics = true,
AllowCustomDomain = true,
AllowMultipleDomains = true,
PrioritySupport = true,
PlanType = "premiumaffiliate",
AllowDocumentUpload = true,
MaxDocuments = 10
PlanType = "premium"
},
_ => new Models.PlanLimitations
{
@ -138,9 +120,7 @@ public class PlanLimitationMiddleware
AllowCustomDomain = false,
AllowMultipleDomains = false,
PrioritySupport = false,
PlanType = "free",
AllowDocumentUpload = false,
MaxDocuments = 0
PlanType = "free"
}
};
}

View File

@ -16,7 +16,6 @@
string BusinessType { get; }
PageTheme Theme { get; }
List<LinkItem> Links { get; }
List<PageDocument> Documents { get; }
SeoSettings SeoSettings { get; }
string Language { get; }
DateTime CreatedAt { get; }

View File

@ -47,9 +47,6 @@ public class LivePage : IPageDisplay
[BsonElement("links")]
public List<LinkItem> Links { get; set; } = new();
[BsonElement("documents")]
public List<PageDocument> Documents { get; set; } = new();
[BsonElement("seoSettings")]
public SeoSettings SeoSettings { get; set; } = new();

View File

@ -1,30 +0,0 @@
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace BCards.Web.Models;
public class PageDocument
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = string.Empty;
[BsonElement("fileId")]
[BsonRepresentation(BsonType.ObjectId)]
public string FileId { get; set; } = string.Empty;
[BsonElement("title")]
public string Title { get; set; } = string.Empty;
[BsonElement("description")]
public string Description { get; set; } = string.Empty;
[BsonElement("fileName")]
public string FileName { get; set; } = string.Empty;
[BsonElement("fileSize")]
public long FileSize { get; set; }
[BsonElement("uploadedAt")]
public DateTime UploadedAt { get; set; } = DateTime.UtcNow;
}

View File

@ -43,10 +43,4 @@ public class PlanLimitations
[BsonElement("lastExtractionDate")]
public DateTime? LastExtractionDate { get; set; }
[BsonElement("allowDocumentUpload")]
public bool AllowDocumentUpload { get; set; } = false;
[BsonElement("maxDocuments")]
public int MaxDocuments { get; set; } = 0;
}

View File

@ -31,10 +31,10 @@ public static class PlanTypeExtensions
return planType switch
{
PlanType.Trial => 0.00m,
PlanType.Basic => 12.90m,
PlanType.Professional => 25.90m,
PlanType.Premium => 29.90m,
PlanType.PremiumAffiliate => 34.90m,
PlanType.Basic => 5.90m,
PlanType.Professional => 12.90m,
PlanType.Premium => 19.90m,
PlanType.PremiumAffiliate => 29.90m,
_ => 0.00m
};
}

View File

@ -44,9 +44,6 @@ public class UserPage : IPageDisplay
[BsonElement("links")]
public List<LinkItem> Links { get; set; } = new();
[BsonElement("documents")]
public List<PageDocument> Documents { get; set; } = new();
[BsonElement("seoSettings")]
public SeoSettings SeoSettings { get; set; } = new();

View File

@ -35,16 +35,13 @@ Serilog.Debugging.SelfLog.Enable(msg =>
System.Diagnostics.Debug.WriteLine($"[SERILOG SELF] {msg}");
});
var tenantName = builder.Configuration["Tenant:SiteName"] ?? "BCards";
var loggerConfig = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.Enrich.WithEnvironmentName()
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.Enrich.WithProperty("ApplicationName", tenantName)
.Enrich.WithProperty("Tenant", tenantName)
.Enrich.WithProperty("ApplicationName", builder.Configuration["ApplicationName"] ?? "BCards")
.Enrich.WithProperty("Environment", builder.Environment.EnvironmentName)
.Enrich.WithProperty("Hostname", hostname);
@ -71,37 +68,40 @@ if (isDevelopment)
var openSearchUrl = builder.Configuration["Serilog:OpenSearchUrl"];
if (!string.IsNullOrEmpty(openSearchUrl))
{
var tenantSlug = tenantName.ToLower().Replace("+", "").Replace(" ", "-");
var indexFormat = $"{tenantSlug}-dev-{{0:yyyy-MM}}";
Console.WriteLine($"[OPENSEARCH DEV] Configurando sink → {openSearchUrl} (index: {indexFormat})");
var indexFormat = "b-cards-dev-{0:yyyy-MM}";
// TODO: após confirmar que conecta, restaurar try/catch silencioso (opcional)
try
{
// OpenSearch configurado para ser MUITO agressivo no envio
loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl))
{
IndexFormat = indexFormat,
AutoRegisterTemplate = false,
AutoRegisterTemplate = true,
BufferBaseFilename = "./logs/opensearch-buffer", // Buffer em disco
ModifyConnectionSettings = conn => conn
.RequestTimeout(TimeSpan.FromSeconds(5))
.PingTimeout(TimeSpan.FromSeconds(3)),
.RequestTimeout(TimeSpan.FromSeconds(8))
.PingTimeout(TimeSpan.FromSeconds(4)),
MinimumLogEventLevel = LogEventLevel.Debug,
EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
BatchPostingLimit = 10,
Period = TimeSpan.FromSeconds(2),
BatchPostingLimit = 10, // Lotes pequenos = envio mais frequente
Period = TimeSpan.FromSeconds(2), // Envia a cada 2 segundos
// Configurações para máxima persistência
BufferRetainedInvalidPayloadsLimitBytes = 100 * 1024 * 1024, // 100MB buffer
BufferLogShippingInterval = TimeSpan.FromSeconds(1), // Tenta reenviar rapidamente
TemplateCustomSettings = new Dictionary<string, string>
{
{"number_of_shards", "1"},
{"number_of_replicas", "0"}
}
}),
bufferSize: 10000,
blockWhenFull: false);
Console.WriteLine($"[OPENSEARCH DEV] Sink registrado. Erros de envio aparecem como [SERILOG SELF] no console.");
bufferSize: 10000, // Buffer grande na memória
blockWhenFull: false); // Nunca bloquear aplicação
}
else
catch (Exception)
{
Console.WriteLine("[OPENSEARCH DEV] OpenSearchUrl não configurado — sem sink OpenSearch.");
// Falha silenciosa - logs continuam no console e arquivo
}
}
}
else
@ -137,8 +137,7 @@ else
_ => environment
};
var tenantSlugProd = tenantName.ToLower().Replace("+", "").Replace(" ", "-");
var indexFormat = $"{tenantSlugProd}-{envMapping}-{{0:yyyy-MM}}";
var indexFormat = $"b-cards-{envMapping}-{{0:yyyy-MM}}";
try
{
@ -157,10 +156,9 @@ else
Period = TimeSpan.FromSeconds(5),
}), bufferSize: 10000, blockWhenFull: false);
}
catch (Exception ex)
catch (Exception)
{
// OpenSearch é opcional — app continua com console/arquivo
Console.WriteLine($"[OPENSEARCH PROD] Falha ao configurar sink → {openSearchUrl}: {ex.Message}");
// Falha silenciosa em produção - logs continuam no console/arquivo
}
}
}
@ -264,9 +262,6 @@ else
}
// Stripe Configuration with validation
builder.Services.Configure<TenantSettings>(
builder.Configuration.GetSection("Tenant"));
builder.Services.Configure<StripeSettings>(
builder.Configuration.GetSection("Stripe"));
@ -314,18 +309,8 @@ authBuilder.AddCookie(options =>
options.SlidingExpiration = true;
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
// SameSite: None para produção (Cloudflare), Lax para desenvolvimento (OAuth local)
if (builder.Environment.IsDevelopment())
{
options.Cookie.SameSite = SameSiteMode.Lax;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Permite HTTP em dev
}
else
{
options.Cookie.SameSite = SameSiteMode.None; // Para Cloudflare
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
}
});
// Always register Google and Microsoft authentication schemes
@ -498,7 +483,6 @@ builder.Services.AddScoped<IEmailService, EmailService>();
builder.Services.AddScoped<IDowngradeService, DowngradeService>();
builder.Services.AddScoped<IImageStorageService, GridFSImageStorage>();
builder.Services.AddScoped<IDocumentStorageService, GridFSDocumentStorage>();
// Support Area - Rating and Contact System
builder.Services.Configure<BCards.Web.Configuration.SupportSettings>(
@ -508,27 +492,23 @@ builder.Services.AddScoped<BCards.Web.Areas.Support.Repositories.IRatingReposito
builder.Services.AddScoped<BCards.Web.Areas.Support.Services.IRatingService, BCards.Web.Areas.Support.Services.RatingService>();
builder.Services.AddScoped<BCards.Web.Areas.Support.Services.ISupportService, BCards.Web.Areas.Support.Services.SupportService>();
// Markdown/Articles System - Tutoriais e Artigos Areas
builder.Services.AddScoped<BCards.Web.Areas.Tutoriais.Services.IMarkdownService, BCards.Web.Areas.Tutoriais.Services.MarkdownService>();
// Configure upload limits for file handling (images up to 5MB)
builder.Services.Configure<FormOptions>(options =>
{
var maxUploadSize = 12 * 1024 * 1024; // 12MB
options.MultipartBodyLengthLimit = maxUploadSize;
options.MultipartBodyLengthLimit = 5 * 1024 * 1024; // 5MB
options.ValueLengthLimit = int.MaxValue;
options.ValueCountLimit = int.MaxValue;
options.KeyLengthLimit = int.MaxValue;
options.BufferBody = true;
options.BufferBodyLengthLimit = maxUploadSize;
options.MultipartBodyLengthLimit = maxUploadSize;
options.BufferBodyLengthLimit = 5 * 1024 * 1024; // 5MB
options.MultipartBodyLengthLimit = 5 * 1024 * 1024; // 5MB
options.MultipartHeadersLengthLimit = 16384;
});
// Configure Kestrel server limits for larger requests
builder.Services.Configure<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = 12 * 1024 * 1024; // 12MB
options.Limits.MaxRequestBodySize = 5 * 1024 * 1024; // 5MB
options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(2);
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
});
@ -587,19 +567,9 @@ builder.Services.AddHttpClient<CriticalServicesHealthCheck>(client =>
client.DefaultRequestHeaders.Add("User-Agent", "BCards-CriticalCheck/1.0");
});
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true;
});
var app = builder.Build();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseForwardedHeaders();
if (!app.Environment.IsDevelopment())
{
@ -607,9 +577,11 @@ if (!app.Environment.IsDevelopment())
{
context.Request.Scheme = "https";
// Fix para Cloudflare - remover porta 443 explícita em qualquer domínio
if (context.Request.Host.Port == 443)
context.Request.Host = new HostString(context.Request.Host.Host);
if (context.Request.Host.Host == "bcards.site")
{
// Fix para Cloudflare - não especificar porta explícita
context.Request.Host = new HostString("bcards.site");
}
await next();
});
@ -623,19 +595,6 @@ app.Use(async (context, next) =>
context.Response.Headers.Append("Referrer-Policy", "no-referrer");
context.Response.Headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
// Content Security Policy - Protects against XSS attacks
var csp = "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://code.jquery.com https://cdn.jsdelivr.net/npm/bootstrap@5.3.2 https://accounts.google.com https://apis.google.com https://www.clarity.ms https://static.cloudflareinsights.com; " +
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/bootstrap@5.3.2 https://cdnjs.cloudflare.com; " +
"img-src 'self' data: https: blob:; " +
"font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; " +
"connect-src 'self' https://accounts.google.com https://apis.google.com https://login.microsoftonline.com https://www.clarity.ms; " +
"frame-src 'self' https://accounts.google.com https://login.microsoftonline.com; " +
"object-src 'none'; " +
"base-uri 'self'; " +
"form-action 'self' https://accounts.google.com https://login.microsoftonline.com";
context.Response.Headers.Append("Content-Security-Policy", csp);
// Load balancer e debugging headers
context.Response.Headers.Append("X-Server-ID", Environment.MachineName);
context.Response.Headers.Append("X-Instance-ID", $"{Environment.MachineName}-{Environment.ProcessId}");
@ -743,7 +702,6 @@ app.Use(async (context, next) =>
app.UseMiddleware<SmartCacheMiddleware>();
app.UseMiddleware<AuthCacheMiddleware>();
app.UseMiddleware<AgeGateMiddleware>();
app.UseMiddleware<PlanLimitationMiddleware>();
app.UseMiddleware<PageStatusMiddleware>();
app.UseMiddleware<ModerationAuthMiddleware>();
@ -828,56 +786,6 @@ app.MapControllerRoute(
pattern: "terminos",
defaults: new { controller = "Legal", action = "TermsES" });
// ========================================
// AREAS: Tutoriais e Artigos
// ========================================
// IMPORTANTE: Ordem importa! Rotas mais específicas primeiro.
// Artigos Area - Índice (ANTES para evitar conflito)
app.MapAreaControllerRoute(
name: "artigos-index",
areaName: "Artigos",
pattern: "artigos",
defaults: new { controller = "Artigos", action = "Index" });
// Artigos Area - Artigo específico
app.MapAreaControllerRoute(
name: "artigos-article",
areaName: "Artigos",
pattern: "artigos/{slug}",
defaults: new { controller = "Artigos", action = "Article" },
constraints: new { slug = @"^[a-z0-9\-]+$" });
// Tutoriais Area - Índice
app.MapAreaControllerRoute(
name: "tutoriais-index",
areaName: "Tutoriais",
pattern: "tutoriais",
defaults: new { controller = "Tutoriais", action = "Index" });
// Tutoriais Area - Lista de artigos por categoria
app.MapAreaControllerRoute(
name: "tutoriais-category",
areaName: "Tutoriais",
pattern: "tutoriais/{categoria}",
defaults: new { controller = "Tutoriais", action = "Category" },
constraints: new { categoria = @"^[a-z\-]+$" });
// Tutoriais Area - Artigo específico
app.MapAreaControllerRoute(
name: "tutoriais-article",
areaName: "Tutoriais",
pattern: "tutoriais/{categoria}/{slug}",
defaults: new { controller = "Tutoriais", action = "Article" },
constraints: new
{
categoria = @"^[a-z\-]+$", // slug de categoria
slug = @"^[a-z0-9\-]+$" // slug do artigo
});
// ========================================
// Rota default
// ========================================
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
@ -895,7 +803,11 @@ using (var scope = app.Services.CreateScope())
await themeService.InitializeDefaultThemesAsync();
}
var existingCategories = await categoryService.GetAllCategoriesAsync();
if (!existingCategories.Any())
{
await categoryService.InitializeDefaultCategoriesAsync();
}
Log.Information("Default themes and categories initialized successfully");
}

View File

@ -15,22 +15,6 @@
"ASPNETCORE_ENVIRONMENT": "Testing"
},
"applicationUrl": "https://localhost:49178;http://localhost:49179"
},
"SpicyLinks": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Spicylinks"
},
"applicationUrl": "https://localhost:49182;http://localhost:49183"
},
"LuzLinks": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Luzlinks"
},
"applicationUrl": "https://localhost:49184;http://localhost:49185"
}
}
}

View File

@ -1,7 +1,5 @@
using BCards.Web.Configuration;
using BCards.Web.Models;
using BCards.Web.Repositories;
using Microsoft.Extensions.Options;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Text;
@ -12,12 +10,10 @@ namespace BCards.Web.Services;
public class CategoryService : ICategoryService
{
private readonly ICategoryRepository _categoryRepository;
private readonly TenantSettings _tenant;
public CategoryService(ICategoryRepository categoryRepository, IOptions<TenantSettings> tenantOptions)
public CategoryService(ICategoryRepository categoryRepository)
{
_categoryRepository = categoryRepository;
_tenant = tenantOptions.Value;
}
public async Task<List<Category>> GetAllCategoriesAsync()
@ -58,39 +54,6 @@ public class CategoryService : ICategoryService
public async Task InitializeDefaultCategoriesAsync()
{
Console.WriteLine($"[CategoryService] DefaultCategories count: {_tenant.DefaultCategories.Count}, SiteName: {_tenant.SiteName}");
// Se tenant tem DefaultCategories configuradas, sempre sincroniza (tenant config é fonte da verdade)
if (_tenant.DefaultCategories.Any())
{
var existing = await _categoryRepository.GetAllActiveAsync();
var existingSlugs = existing.Select(c => c.Slug).ToHashSet();
var configSlugs = _tenant.DefaultCategories
.Select(i => string.IsNullOrWhiteSpace(i.Slug) ? GenerateSlug(i.Name) : i.Slug)
.ToHashSet();
// Remove categorias que não estão mais na config
foreach (var cat in existing.Where(c => !configSlugs.Contains(c.Slug)))
await _categoryRepository.DeleteAsync(cat.Id!);
// Adiciona categorias novas
foreach (var item in _tenant.DefaultCategories)
{
var slug = string.IsNullOrWhiteSpace(item.Slug) ? GenerateSlug(item.Name) : item.Slug;
if (!existingSlugs.Contains(slug))
{
await _categoryRepository.CreateAsync(new Category
{
Name = item.Name,
Slug = slug,
Icon = item.Icon,
Description = item.Description,
SeoKeywords = item.SeoKeywords
});
}
}
return;
}
var categories = await _categoryRepository.GetAllActiveAsync();
if (categories.Any()) return;

View File

@ -1,5 +1,3 @@
using BCards.Web.Configuration;
using Microsoft.Extensions.Options;
using SendGrid;
using SendGrid.Helpers.Mail;
@ -10,18 +8,15 @@ public class EmailService : IEmailService
private readonly ISendGridClient _sendGridClient;
private readonly IConfiguration _configuration;
private readonly ILogger<EmailService> _logger;
private readonly TenantSettings _tenant;
public EmailService(
ISendGridClient sendGridClient,
IConfiguration configuration,
ILogger<EmailService> logger,
IOptions<TenantSettings> tenantSettings)
ILogger<EmailService> logger)
{
_sendGridClient = sendGridClient;
_configuration = configuration;
_logger = logger;
_tenant = tenantSettings.Value;
}
public async Task SendModerationStatusAsync(string userEmail, string userName, string pageTitle, string status, string? reason = null, string? previewUrl = null)
@ -101,7 +96,7 @@ public class EmailService : IEmailService
private (string subject, string htmlContent) GetPendingModerationTemplate(string userName, string pageTitle, string? previewUrl)
{
var subject = $"📋 Sua página está sendo analisada - {_tenant.SiteName}";
var subject = "📋 Sua página está sendo analisada - bcards.site";
var previewButton = !string.IsNullOrEmpty(previewUrl)
? $"<p><a href='{previewUrl}' style='background: #28a745; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; display: inline-block;'>Ver Preview</a></p>"
: "";
@ -131,7 +126,7 @@ public class EmailService : IEmailService
private (string subject, string htmlContent) GetApprovedTemplate(string userName, string pageTitle)
{
var subject = $"✅ Sua página foi aprovada! - {_tenant.SiteName}";
var subject = "✅ Sua página foi aprovada! - bcards.site";
var htmlContent = $@"
<div style='font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;'>
<h2 style='color: #28a745;'>Parabéns {userName}! 🎉</h2>
@ -162,7 +157,7 @@ public class EmailService : IEmailService
private (string subject, string htmlContent) GetRejectedTemplate(string userName, string pageTitle, string? reason)
{
var subject = $"⚠️ Sua página precisa de ajustes - {_tenant.SiteName}";
var subject = "⚠️ Sua página precisa de ajustes - bcards.site";
var reasonText = !string.IsNullOrEmpty(reason) ? $"<p><strong>Motivo:</strong> {reason}</p>" : "";
var htmlContent = $@"

View File

@ -1,93 +0,0 @@
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;
namespace BCards.Web.Services;
public class GridFSDocumentStorage : IDocumentStorageService
{
private const int MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
private static readonly string[] ALLOWED_TYPES = { "application/pdf" };
private readonly GridFSBucket _gridFS;
private readonly ILogger<GridFSDocumentStorage> _logger;
public GridFSDocumentStorage(IMongoDatabase database, ILogger<GridFSDocumentStorage> logger)
{
_gridFS = new GridFSBucket(database, new GridFSBucketOptions
{
BucketName = "page_documents"
});
_logger = logger;
}
public async Task<string> SaveDocumentAsync(byte[] documentBytes, string fileName, string contentType)
{
if (documentBytes == null || documentBytes.Length == 0)
throw new ArgumentException("Documento inválido.");
if (documentBytes.Length > MAX_FILE_SIZE)
throw new ArgumentException($"Arquivo muito grande. Tamanho máximo permitido: {MAX_FILE_SIZE / (1024 * 1024)}MB.");
if (!ALLOWED_TYPES.Contains(contentType.ToLower()))
throw new ArgumentException("Tipo de arquivo não suportado. Envie um PDF.");
var uniqueFileName = $"document_{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Guid.NewGuid():N}.pdf";
var options = new GridFSUploadOptions
{
Metadata = new BsonDocument
{
{ "originalFileName", fileName },
{ "contentType", contentType },
{ "uploadDate", DateTime.UtcNow },
{ "size", documentBytes.Length }
}
};
var fileId = await _gridFS.UploadFromBytesAsync(uniqueFileName, documentBytes, options);
_logger.LogInformation("PDF salvo no GridFS: {FileId}", fileId);
return fileId.ToString();
}
public async Task<byte[]?> GetDocumentAsync(string documentId)
{
if (!ObjectId.TryParse(documentId, out var objectId))
return null;
try
{
return await _gridFS.DownloadAsBytesAsync(objectId);
}
catch (GridFSFileNotFoundException)
{
return null;
}
}
public async Task<bool> DeleteDocumentAsync(string documentId)
{
if (!ObjectId.TryParse(documentId, out var objectId))
return false;
try
{
await _gridFS.DeleteAsync(objectId);
return true;
}
catch (GridFSFileNotFoundException)
{
return false;
}
}
public async Task<bool> DocumentExistsAsync(string documentId)
{
if (!ObjectId.TryParse(documentId, out var objectId))
return false;
var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", objectId);
var fileInfo = await _gridFS.Find(filter).FirstOrDefaultAsync();
return fileInfo != null;
}
}

View File

@ -1,9 +0,0 @@
namespace BCards.Web.Services;
public interface IDocumentStorageService
{
Task<string> SaveDocumentAsync(byte[] documentBytes, string fileName, string contentType);
Task<byte[]?> GetDocumentAsync(string documentId);
Task<bool> DeleteDocumentAsync(string documentId);
Task<bool> DocumentExistsAsync(string documentId);
}

View File

@ -51,8 +51,6 @@ public class PlanConfiguration
public bool AllowProductLinks { get; set; }
public bool AllowAnalytics { get; set; }
public bool? SpecialModeration { get; set; }
public bool AllowDocumentUpload { get; set; }
public int MaxDocuments { get; set; }
public List<string> Features { get; set; } = new();
public string Interval { get; set; } = "month";
public PlanType BasePlanType { get; set; }

View File

@ -59,7 +59,6 @@ public class LivePageService : ILivePageService
BusinessType = userPage.BusinessType,
Theme = userPage.Theme,
Links = userPage.Links,
Documents = userPage.Documents,
SeoSettings = userPage.SeoSettings,
Language = userPage.Language,
Analytics = new LivePageAnalytics

View File

@ -372,45 +372,16 @@ public class PaymentService : IPaymentService
try
{
var service = new SubscriptionService();
var subscription = await service.GetAsync(subscriptionId);
if (refund)
{
// Processar reembolso automático - obter última charge e reembolsá-la
try
{
var chargeService = new ChargeService();
var charges = await chargeService.ListAsync(new ChargeListOptions
{
Customer = subscription.CustomerId,
Limit = 1
});
if (charges.Data.Any())
{
var lastCharge = charges.Data.First();
if (lastCharge.Refunded == false && !string.IsNullOrEmpty(lastCharge.Id))
{
try
{
var refundService = new RefundService();
await refundService.CreateAsync(new RefundCreateOptions { Charge = lastCharge.Id });
}
catch (StripeException)
{
// Reembolso falhou mas continuaremos com o cancelamento
}
}
}
}
catch (StripeException)
{
// Se houver erro ao obter charge, ainda cancela a assinatura
}
}
// Cancelar a assinatura
// Para reembolso completo, apenas cancela - reembolso deve ser feito manualmente via Stripe Dashboard
await service.CancelAsync(subscriptionId);
}
else
{
await service.CancelAsync(subscriptionId);
}
// Atualizar subscription local
var localSubscription = await _subscriptionRepository.GetByStripeSubscriptionIdAsync(subscriptionId);

View File

@ -47,9 +47,7 @@ public class PlanConfigurationService : IPlanConfigurationService
PrioritySupport = false,
AllowProductLinks = false,
MaxProductLinks = 0,
PlanType = "trial",
AllowDocumentUpload = false,
MaxDocuments = 0
PlanType = "trial"
},
PlanType.Basic => new PlanLimitations
{
@ -61,9 +59,7 @@ public class PlanConfigurationService : IPlanConfigurationService
PrioritySupport = false,
AllowProductLinks = GetConfigValue(PlanType.Basic, "AllowProductLinks", false),
MaxProductLinks = 0,
PlanType = "basic",
AllowDocumentUpload = GetConfigValue(PlanType.Basic, "AllowDocumentUpload", false),
MaxDocuments = GetConfigValue(PlanType.Basic, "MaxDocuments", 0)
PlanType = "basic"
},
PlanType.Professional => new PlanLimitations
{
@ -75,9 +71,7 @@ public class PlanConfigurationService : IPlanConfigurationService
PrioritySupport = false,
AllowProductLinks = GetConfigValue(PlanType.Professional, "AllowProductLinks", false),
MaxProductLinks = 0,
PlanType = "professional",
AllowDocumentUpload = GetConfigValue(PlanType.Professional, "AllowDocumentUpload", false),
MaxDocuments = GetConfigValue(PlanType.Professional, "MaxDocuments", 0)
PlanType = "professional"
},
PlanType.Premium => new PlanLimitations
{
@ -89,9 +83,7 @@ public class PlanConfigurationService : IPlanConfigurationService
PrioritySupport = true,
AllowProductLinks = GetConfigValue(PlanType.Premium, "AllowProductLinks", false),
MaxProductLinks = 0,
PlanType = "premium",
AllowDocumentUpload = GetConfigValue(PlanType.Premium, "AllowDocumentUpload", true),
MaxDocuments = GetConfigValue(PlanType.Premium, "MaxDocuments", 5)
PlanType = "premium"
},
PlanType.PremiumAffiliate => new PlanLimitations
{
@ -103,11 +95,9 @@ public class PlanConfigurationService : IPlanConfigurationService
PrioritySupport = true,
AllowProductLinks = GetConfigValue(PlanType.PremiumAffiliate, "AllowProductLinks", true),
MaxProductLinks = 10,
PlanType = "premiumaffiliate",
AllowDocumentUpload = GetConfigValue(PlanType.PremiumAffiliate, "AllowDocumentUpload", true),
MaxDocuments = GetConfigValue(PlanType.PremiumAffiliate, "MaxDocuments", 10)
PlanType = "premiumaffiliate"
},
_ => new PlanLimitations { PlanType = "trial", AllowDocumentUpload = false, MaxDocuments = 0 }
_ => new PlanLimitations { PlanType = "trial" }
};
}

View File

@ -178,8 +178,8 @@ public class TrialExpirationService : BackgroundService
Para continuar usando sua página de links, escolha um de nossos planos:
Básico - R$ 12,90/mês
Profissional - R$ 25,90/mês
Básico - R$ 9,90/mês
Profissional - R$ 24,90/mês
Premium - R$ 29,90/mês
Acesse: {GetUpgradeUrl()}
@ -204,9 +204,9 @@ public class TrialExpirationService : BackgroundService
Para reativar sua página, escolha um de nossos planos:
Básico - R$ 12,90/mês - 5 links, analytics básicos
Profissional - R$ 25,90/mês - 15 links, todos os temas, analytics avançados
Premium - R$ 29,90/mês - Links ilimitados, temas premium, analytics completos, upload de PDFs
Básico - R$ 9,90/mês - 5 links, analytics básicos
Profissional - R$ 24,90/mês - 15 links, todos os temas, analytics avançados
Premium - R$ 29,90/mês - Links ilimitados, temas premium, analytics completos
Seus dados estão seguros e serão restaurados assim que você escolher um plano.

View File

@ -0,0 +1,58 @@
using System.ComponentModel.DataAnnotations;
namespace BCards.Web.ViewModels;
public class CreatePageViewModel
{
[Required(ErrorMessage = "Nome é obrigatório")]
[StringLength(50, ErrorMessage = "Nome deve ter no máximo 50 caracteres")]
public string DisplayName { get; set; } = string.Empty;
[Required(ErrorMessage = "Categoria é obrigatória")]
public string Category { get; set; } = string.Empty;
[Required(ErrorMessage = "Tipo de negócio é obrigatório")]
public string BusinessType { get; set; } = "individual";
[StringLength(200, ErrorMessage = "Bio deve ter no máximo 200 caracteres")]
public string Bio { get; set; } = string.Empty;
[Required(ErrorMessage = "Tema é obrigatório")]
public string SelectedTheme { get; set; } = "minimalist";
public string WhatsAppNumber { get; set; } = string.Empty;
public string FacebookUrl { get; set; } = string.Empty;
public string TwitterUrl { get; set; } = string.Empty;
public string InstagramUrl { get; set; } = string.Empty;
public string TiktokUrl { get; set; } = string.Empty;
public string PinterestUrl { get; set; } = string.Empty;
public string DiscordUrl { get; set; } = string.Empty;
public string KawaiUrl { get; set; } = string.Empty;
public List<CreateLinkViewModel> Links { get; set; } = new();
public string Slug { get; set; } = string.Empty;
}
public class CreateLinkViewModel
{
[Required(ErrorMessage = "Título é obrigatório")]
[StringLength(50, ErrorMessage = "Título deve ter no máximo 50 caracteres")]
public string Title { get; set; } = string.Empty;
[Required(ErrorMessage = "URL é obrigatória")]
[Url(ErrorMessage = "URL inválida")]
public string Url { get; set; } = string.Empty;
[StringLength(100, ErrorMessage = "Descrição deve ter no máximo 100 caracteres")]
public string Description { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
}

View File

@ -18,7 +18,7 @@ public class ManagePageViewModel
[Required(ErrorMessage = "Tipo de negócio é obrigatório")]
public string BusinessType { get; set; } = "individual";
[StringLength(3000, ErrorMessage = "Bio deve ter no máximo 3000 caracteres")]
[StringLength(200, ErrorMessage = "Bio deve ter no máximo 200 caracteres")]
public string Bio { get; set; } = string.Empty;
public string Slug { get; set; } = string.Empty;
@ -43,7 +43,6 @@ public class ManagePageViewModel
public string KawaiUrl { get; set; } = string.Empty;
public List<ManageLinkViewModel> Links { get; set; } = new();
public List<ManageDocumentViewModel> Documents { get; set; } = new();
// Profile image fields
public string? ProfileImageId { get; set; }
@ -56,9 +55,6 @@ public class ManagePageViewModel
// Plan limitations
public int MaxLinksAllowed { get; set; } = 3;
public bool AllowProductLinks { get; set; } = false;
public int MaxDocumentsAllowed { get; set; } = 0;
public bool AllowDocumentUpload { get; set; } = false;
public string DocumentUploadPlansDisplay { get; set; } = "planos com suporte a documentos";
public bool CanUseTheme(string themeName) => AvailableThemes.Any(t => t.Name.ToLower() == themeName.ToLower());
/// <summary>
@ -105,28 +101,6 @@ public class ManageLinkViewModel
public DateTime? ProductDataCachedAt { get; set; }
}
public class ManageDocumentViewModel
{
// Campos opcionais - preenchidos pelo model binding ou pelo controller
public string? Id { get; set; }
public string? DocumentId { get; set; }
public string? FileName { get; set; }
[Required(ErrorMessage = "Título é obrigatório")]
[StringLength(120, ErrorMessage = "Título deve ter no máximo 120 caracteres")]
public string Title { get; set; } = string.Empty;
[StringLength(300, ErrorMessage = "Descrição deve ter no máximo 300 caracteres")]
public string Description { get; set; } = string.Empty;
public long FileSize { get; set; }
public DateTime? UploadedAt { get; set; }
public IFormFile? DocumentFile { get; set; }
public bool MarkForRemoval { get; set; }
public bool ReplaceExisting => DocumentFile != null && !string.IsNullOrEmpty(DocumentId);
}
public class DashboardViewModel
{
public User CurrentUser { get; set; } = new();

View File

@ -0,0 +1,618 @@
@model BCards.Web.ViewModels.CreatePageViewModel
@{
ViewData["Title"] = "Criar Página";
Layout = "_Layout";
}
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-8 mx-auto">
<div class="card shadow-sm">
<div class="card-header bg-primary text-white">
<h4 class="mb-0">
<i class="fas fa-magic"></i>
Criar Sua Página de Links
</h4>
</div>
<div class="card-body">
<!-- Progress Bar -->
<div class="progress mb-4" style="height: 8px;">
<div class="progress-bar" role="progressbar" style="width: 20%" id="wizardProgress"></div>
</div>
<form asp-action="CreatePage" method="post" id="createPageForm">
<!-- Step 1: Informações Básicas -->
<div class="wizard-step" id="step1">
<h5 class="step-title">
<span class="step-number">1</span>
Informações Básicas
</h5>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="DisplayName" class="form-label">Nome da Página</label>
<input asp-for="DisplayName" class="form-control" placeholder="Ex: João Silva">
<span asp-validation-for="DisplayName" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label asp-for="Category" class="form-label">Categoria</label>
<select asp-for="Category" class="form-select">
<option value="">Selecione uma categoria</option>
@foreach (var category in ViewBag.Categories as List<BCards.Web.Models.Category> ?? new List<BCards.Web.Models.Category>())
{
<option value="@category.Name">@category.Name</option>
}
</select>
<span asp-validation-for="Category" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="BusinessType" class="form-label">Tipo</label>
<select asp-for="BusinessType" class="form-select">
<option value="individual">Pessoa Física</option>
<option value="company">Empresa</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="slugPreview" class="form-label">URL da Página</label>
<div class="input-group">
<span class="input-group-text">page/</span>
<span class="input-group-text" id="categorySlug">categoria</span>
<span class="input-group-text">/</span>
<input type="text" class="form-control" id="slugPreview" readonly>
<input asp-for="Slug" type="hidden">
</div>
<small class="form-text text-muted">URL gerada automaticamente</small>
</div>
</div>
</div>
<div class="mb-3">
<label asp-for="Bio" class="form-label">Bio/Descrição</label>
<textarea asp-for="Bio" class="form-control" rows="3" placeholder="Uma breve descrição sobre você ou sua empresa..."></textarea>
<span asp-validation-for="Bio" class="text-danger"></span>
</div>
</div>
<!-- Step 2: Seleção de Tema -->
<div class="wizard-step d-none" id="step2">
<h5 class="step-title">
<span class="step-number">2</span>
Escolha Seu Tema Visual
</h5>
<div class="row">
@foreach (var theme in ViewBag.Themes as List<BCards.Web.Models.PageTheme> ?? new List<BCards.Web.Models.PageTheme>())
{
<div class="col-md-4 mb-3">
<div class="theme-card" data-theme="@theme.Name.ToLower()">
<div class="theme-preview" style="background: @theme.BackgroundColor; color: @theme.TextColor;">
<div class="theme-header" style="background-color: @theme.PrimaryColor;">
<div class="theme-avatar"></div>
<h6>@theme.Name</h6>
</div>
<div class="theme-links">
<div class="theme-link" style="background-color: @theme.PrimaryColor;"></div>
<div class="theme-link" style="background-color: @theme.SecondaryColor;"></div>
</div>
</div>
<div class="theme-name">
@theme.Name
@if (theme.IsPremium)
{
<span class="badge bg-warning">Premium</span>
}
</div>
</div>
</div>
}
</div>
<input asp-for="SelectedTheme" type="hidden">
</div>
<!-- Step 3: Links Principais -->
<div class="wizard-step d-none" id="step3">
<h5 class="step-title">
<span class="step-number">3</span>
Links Principais
</h5>
<div id="linksContainer">
<!-- Links will be added dynamically -->
</div>
<button type="button" class="btn btn-outline-primary" id="addLinkBtn">
<i class="fas fa-plus"></i> Adicionar Link
</button>
</div>
<!-- Step 4: Redes Sociais -->
<div class="wizard-step d-none" id="step4">
<h5 class="step-title">
<span class="step-number">4</span>
Redes Sociais
</h5>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="WhatsAppNumber" class="form-label">
<i class="fab fa-whatsapp text-success"></i>
WhatsApp
</label>
<input asp-for="WhatsAppNumber" class="form-control" placeholder="+55 11 99999-9999">
<span asp-validation-for="WhatsAppNumber" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label asp-for="FacebookUrl" class="form-label">
<i class="fab fa-facebook text-primary"></i>
Facebook
</label>
<input asp-for="FacebookUrl" class="form-control" placeholder="https://facebook.com/seu-perfil">
<span asp-validation-for="FacebookUrl" class="text-danger"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label asp-for="TwitterUrl" class="form-label">
<i class="fab fa-x-twitter"></i>
X / Twitter
</label>
<input asp-for="TwitterUrl" class="form-control" placeholder="https://x.com/seu-perfil">
<span asp-validation-for="TwitterUrl" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label asp-for="InstagramUrl" class="form-label">
<i class="fab fa-instagram text-danger"></i>
Instagram
</label>
<input asp-for="InstagramUrl" class="form-control" placeholder="https://instagram.com/seu-perfil">
<span asp-validation-for="InstagramUrl" class="text-danger"></span>
</div>
</div>
</div>
</div>
<!-- Step 5: Preview e Finalização -->
<div class="wizard-step d-none" id="step5">
<h5 class="step-title">
<span class="step-number">5</span>
Preview e Finalização
</h5>
<div class="preview-container">
<div class="preview-phone">
<div class="preview-screen" id="previewScreen">
<!-- Preview will be generated here -->
</div>
</div>
</div>
<div class="text-center mt-4">
<p class="text-muted">Sua página estará disponível em:</p>
<strong id="finalUrl">page/categoria/seu-slug</strong>
</div>
</div>
<!-- Navigation Buttons -->
<div class="wizard-navigation mt-4">
<button type="button" class="btn btn-secondary" id="prevBtn" style="display: none;">
<i class="fas fa-arrow-left"></i> Anterior
</button>
<button type="button" class="btn btn-primary float-end" id="nextBtn">
Próximo <i class="fas fa-arrow-right"></i>
</button>
<button type="submit" class="btn btn-success float-end d-none" id="submitBtn">
<i class="fas fa-check"></i> Criar Página
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
.wizard-step {
min-height: 400px;
}
.step-title {
color: #495057;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e9ecef;
}
.step-number {
display: inline-block;
width: 30px;
height: 30px;
line-height: 30px;
background-color: #007bff;
color: white;
border-radius: 50%;
text-align: center;
margin-right: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
}
.theme-card {
cursor: pointer;
border: 2px solid transparent;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
.theme-card:hover,
.theme-card.selected {
border-color: #007bff;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.theme-preview {
height: 120px;
position: relative;
padding: 1rem;
}
.theme-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-radius: 4px;
margin-bottom: 1rem;
}
.theme-avatar {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
}
.theme-header h6 {
margin: 0;
font-size: 0.75rem;
color: white;
}
.theme-links {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.theme-link {
height: 8px;
border-radius: 4px;
opacity: 0.8;
}
.theme-name {
padding: 0.75rem;
text-align: center;
font-weight: 500;
background-color: #f8f9fa;
}
.link-input-group {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.preview-phone {
width: 300px;
height: 400px;
border: 8px solid #333;
border-radius: 20px;
background-color: #000;
padding: 20px 10px;
position: relative;
}
.preview-screen {
width: 100%;
height: 100%;
background-color: #fff;
border-radius: 12px;
overflow-y: auto;
padding: 1rem;
}
.wizard-navigation {
border-top: 1px solid #dee2e6;
padding-top: 1rem;
}
</style>
<script>
let currentStep = 1;
const totalSteps = 5;
let linkCount = 0;
$(document).ready(function() {
initializeWizard();
// Generate slug when name or category changes
$('#DisplayName, #Category').on('input change', function() {
generateSlug();
});
// Theme selection
$('.theme-card').on('click', function() {
$('.theme-card').removeClass('selected');
$(this).addClass('selected');
const themeName = $(this).data('theme');
$('#SelectedTheme').val(themeName);
});
// Navigation
$('#nextBtn').on('click', function() {
if (validateCurrentStep()) {
nextStep();
}
});
$('#prevBtn').on('click', function() {
prevStep();
});
// Add link functionality
$('#addLinkBtn').on('click', function() {
addLinkInput();
});
// Form submission
$('#createPageForm').on('submit', function(e) {
generateLinksData();
});
});
function initializeWizard() {
updateProgressBar();
updateNavigationButtons();
addLinkInput(); // Add first link input
}
function nextStep() {
if (currentStep < totalSteps) {
$(`#step${currentStep}`).addClass('d-none');
currentStep++;
$(`#step${currentStep}`).removeClass('d-none');
if (currentStep === 5) {
generatePreview();
}
updateProgressBar();
updateNavigationButtons();
}
}
function prevStep() {
if (currentStep > 1) {
$(`#step${currentStep}`).addClass('d-none');
currentStep--;
$(`#step${currentStep}`).removeClass('d-none');
updateProgressBar();
updateNavigationButtons();
}
}
function updateProgressBar() {
const progress = (currentStep / totalSteps) * 100;
$('#wizardProgress').css('width', progress + '%');
}
function updateNavigationButtons() {
$('#prevBtn').toggle(currentStep > 1);
if (currentStep === totalSteps) {
$('#nextBtn').addClass('d-none');
$('#submitBtn').removeClass('d-none');
} else {
$('#nextBtn').removeClass('d-none');
$('#submitBtn').addClass('d-none');
}
}
function validateCurrentStep() {
let isValid = true;
switch (currentStep) {
case 1:
if (!$('#DisplayName').val() || !$('#Category').val()) {
alert('Por favor, preencha o nome e a categoria.');
isValid = false;
}
break;
case 2:
if (!$('#SelectedTheme').val()) {
alert('Por favor, selecione um tema.');
isValid = false;
}
break;
}
return isValid;
}
function generateSlug() {
const name = $('#DisplayName').val();
const category = $('#Category').val();
if (name && category) {
$.post('/Admin/GenerateSlug', { category: category, name: name })
.done(function(data) {
$('#Slug').val(data.slug);
$('#slugPreview').val(data.slug);
$('#categorySlug').text(category);
$('#finalUrl').text(`page/${category}/${data.slug}`);
});
}
}
function addLinkInput() {
linkCount++;
const linkHtml = `
<div class="link-input-group" data-link="${linkCount}">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Link ${linkCount}</h6>
<button type="button" class="btn btn-sm btn-outline-danger remove-link-btn">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-2">
<label class="form-label">Título</label>
<input type="text" class="form-control link-title" placeholder="Ex: Meu Site">
</div>
</div>
<div class="col-md-6">
<div class="mb-2">
<label class="form-label">URL</label>
<input type="url" class="form-control link-url" placeholder="https://exemplo.com">
</div>
</div>
</div>
<div class="mb-2">
<label class="form-label">Descrição (opcional)</label>
<input type="text" class="form-control link-description" placeholder="Breve descrição do link">
</div>
</div>
`;
$('#linksContainer').append(linkHtml);
// Add remove functionality
$('.remove-link-btn').off('click').on('click', function() {
$(this).closest('.link-input-group').remove();
});
}
function generateLinksData() {
const links = [];
$('.link-input-group').each(function() {
const title = $(this).find('.link-title').val();
const url = $(this).find('.link-url').val();
const description = $(this).find('.link-description').val();
if (title && url) {
links.push({
Title: title,
Url: url,
Description: description,
Icon: ''
});
}
});
// Remove existing hidden link inputs
$('input[name^="Links["]').remove();
// Create hidden inputs for links directly in the form
links.forEach((link, index) => {
$('#createPageForm').append(`
<input type="hidden" name="Links[${index}].Title" value="${link.Title}" />
<input type="hidden" name="Links[${index}].Url" value="${link.Url}" />
<input type="hidden" name="Links[${index}].Description" value="${link.Description}" />
<input type="hidden" name="Links[${index}].Icon" value="${link.Icon}" />
`);
});
// Debug: Log what we're sending
console.log('=== DEBUG GENERATELINKSDATA ===');
console.log('Links found:', links.length);
links.forEach((link, index) => {
console.log(`Link ${index}:`, link);
});
console.log('=== FIM DEBUG ===');
}
function generatePreview() {
const name = $('#DisplayName').val();
const bio = $('#Bio').val();
const selectedTheme = $('#SelectedTheme').val();
let previewHtml = `
<div class="text-center">
<div class="mb-3">
<div style="width: 60px; height: 60px; background-color: #ddd; border-radius: 50%; margin: 0 auto;"></div>
</div>
<h5 class="mb-2">${name}</h5>
<p class="text-muted small mb-3">${bio}</p>
<div class="d-grid gap-2">
`;
// Add links preview
$('.link-input-group').each(function() {
const title = $(this).find('.link-title').val();
if (title) {
previewHtml += `<div class="btn btn-primary btn-sm">${title}</div>`;
}
});
// Add social media preview
if ($('#WhatsAppNumber').val()) {
previewHtml += `<div class="btn btn-success btn-sm"><i class="fab fa-whatsapp"></i> WhatsApp</div>`;
}
if ($('#FacebookUrl').val()) {
previewHtml += `<div class="btn btn-primary btn-sm"><i class="fab fa-facebook"></i> Facebook</div>`;
}
if ($('#TwitterUrl').val()) {
previewHtml += `<div class="btn btn-dark btn-sm"><i class="fab fa-x-twitter"></i> X / Twitter</div>`;
}
if ($('#InstagramUrl').val()) {
previewHtml += `<div class="btn btn-danger btn-sm"><i class="fab fa-instagram"></i> Instagram</div>`;
}
previewHtml += `</div></div>`;
$('#previewScreen').html(previewHtml);
}
</script>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@ -1,8 +1,6 @@
@model BCards.Web.ViewModels.DashboardViewModel
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = $"Dashboard - {tenant.SiteName}";
ViewData["Title"] = "Dashboard - BCards";
Layout = "_Layout";
var pageInCreation = Model.UserPages.FirstOrDefault(p => (p.LastModerationStatus ?? p.Status) == BCards.Web.ViewModels.PageStatus.Creating);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Acesso Negado";
Layout = null;
}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@tenant.SiteName — Acesso Negado</title>
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
<style>
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
</style>
</head>
<body>
<div class="text-center p-5">
<h1 class="fs-1 mb-3">🚫</h1>
<h2>Acesso não permitido</h2>
<p class="text-white-50">Este site é exclusivo para maiores de 18 anos.</p>
</div>
</body>
</html>

View File

@ -1,73 +0,0 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Verificação de Idade";
Layout = null;
}
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@tenant.SiteName — Verificação de Idade</title>
<link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css" />
<style>
body {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.age-gate-card {
max-width: 440px;
width: 100%;
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 16px;
backdrop-filter: blur(10px);
color: #fff;
}
.btn-confirm {
background: linear-gradient(135deg, #e94560, #c23152);
border: none;
padding: 12px 32px;
font-size: 1.1rem;
border-radius: 8px;
color: #fff;
width: 100%;
}
.btn-confirm:hover { opacity: 0.9; color: #fff; }
.btn-deny { color: rgba(255,255,255,0.5); background: none; border: none; width: 100%; }
.btn-deny:hover { color: rgba(255,255,255,0.8); }
</style>
</head>
<body>
<div class="age-gate-card p-5 text-center mx-3">
<h1 class="fs-1 mb-2">🔞</h1>
<h2 class="fw-bold mb-1">@tenant.SiteName</h2>
<p class="text-white-50 mb-4">Este site contém conteúdo exclusivo para adultos.</p>
<p class="mb-4">
Você confirma que tem <strong>18 anos ou mais</strong> e concorda com os
<a href="/Legal/Terms" class="text-white-50">Termos de Uso</a>?
</p>
<form asp-controller="AgeGate" asp-action="Confirm" method="post">
@Html.AntiForgeryToken()
<input type="hidden" name="returnUrl" value="@ViewBag.ReturnUrl" />
<button type="submit" class="btn btn-confirm mb-3">
✅ Sim, tenho 18 anos ou mais
</button>
</form>
<form asp-controller="AgeGate" asp-action="Deny" method="post">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-deny">
Não, tenho menos de 18 anos
</button>
</form>
</div>
<script src="~/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@ -1,7 +1,5 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = $"Login - {tenant.SiteName}";
ViewData["Title"] = "Login - BCards";
var returnUrl = ViewBag.ReturnUrl as string;
var isPreview = ViewBag.IsPreview as bool? ?? false;
Layout = isPreview ? "_Layout" : "_UserPageLayout";
@ -13,7 +11,7 @@
<div class="card shadow">
<div class="card-body p-4">
<div class="text-center mb-4">
<h2 class="text-primary fw-bold">@tenant.SiteName</h2>
<h2 class="text-primary fw-bold">BCards</h2>
<p class="text-muted">Entre na sua conta</p>
</div>

View File

@ -1,22 +1,23 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = $"{tenant.SiteName} - {tenant.Tagline}";
//var isPreview = ViewBag.IsPreview as bool? ?? false;
ViewData["Title"] = "BCards - Crie sua bio / links Profissional";
var categories = ViewBag.Categories as List<BCards.Web.Models.Category> ?? new List<BCards.Web.Models.Category>();
var recentPages = ViewBag.RecentPages as List<BCards.Web.Models.UserPage> ?? new List<BCards.Web.Models.UserPage>();
//Layout = isPreview ? "_Layout" : "_UserPageLayout";
Layout = "_Layout";
var featuresHeadline = tenant.FeaturesHeadline.Replace("{SiteName}", tenant.SiteName);
}
<div class="hero-section text-white py-5 mb-5">
<div class="hero-section bg-primary bg-gradient text-white py-5 mb-5">
<div class="container">
<div class="row align-items-center">
<div class="col-lg-6">
<h1 class="display-4 fw-bold mb-4">
@tenant.HeroHeadline
Crie sua página profissional em minutos
</h1>
<p class="lead mb-4">
@tenant.HeroDescription
A melhor alternativa ao para ter uma página de links simples.
Criada para profissionais e empresas no Brasil.
Organize todos os seus links em uma página única e profissional.
</p>
<div class="d-flex gap-3 flex-wrap mb-4 mb-lg-0">
@if (User.Identity?.IsAuthenticated == true)
@ -28,7 +29,7 @@
else
{
<a asp-controller="Auth" asp-action="Login" class="btn btn-light btn-lg px-4">
@tenant.HeroCtaText
Começar Grátis
</a>
}
<a asp-controller="Home" asp-action="Pricing" class="btn btn-outline-light btn-lg px-4">
@ -37,7 +38,7 @@
</div>
</div>
<div class="col-lg-6 text-center">
<img src="~/images/hero-mockup.svg" alt="Exemplo de página @tenant.SiteName" class="img-fluid rounded shadow-lg" style="max-height: 400px;">
<img src="~/images/hero-mockup.svg" alt="Exemplo de página BCards" class="img-fluid rounded shadow-lg" style="max-height: 400px;">
</div>
</div>
</div>
@ -69,32 +70,44 @@
}
<!-- Funcionalidades -->
@if (tenant.Features.Any())
{
<section class="mb-5">
<h2 class="text-center mb-4">@featuresHeadline</h2>
<h2 class="text-center mb-4">Por que escolher o BCards?</h2>
<div class="row g-4">
@foreach (var feature in tenant.Features)
{
<div class="col-md-4">
<div class="text-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="fs-2 text-primary">@feature.Icon</i>
<i class="fs-2 text-primary">🎨</i>
</div>
<h5>@feature.Title</h5>
<p class="text-muted">@feature.Description</p>
<h5>Temas Profissionais</h5>
<p class="text-muted">Escolha entre diversos temas profissionais ou personalize as cores da sua página.</p>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="fs-2 text-primary">📊</i>
</div>
<h5>Analytics Avançado</h5>
<p class="text-muted">Acompanhe quantas pessoas visitaram sua página e clicaram nos seus links.</p>
</div>
</div>
<div class="col-md-4">
<div class="text-center">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
<i class="fs-2 text-primary">🔗</i>
</div>
<h5>URLs Organizadas</h5>
<p class="text-muted">Suas URLs são organizadas por categoria: bcards.site/corretor/seu-nome</p>
</div>
</div>
}
</div>
</section>
}
<!-- Páginas Recentes -->
@if (recentPages.Any())
{
<section class="mb-5">
<h2 class="text-center mb-4">Quem já usa o @tenant.SiteName</h2>
<h2 class="text-center mb-4">Profissionais que confiam no BCards</h2>
<div class="row g-3">
@foreach (var page in recentPages)
{
@ -132,20 +145,20 @@
<!-- CTA Final -->
<section class="text-center py-5 bg-light rounded-3 mb-5">
<div class="container">
<h2 class="mb-3">@tenant.CtaHeadline</h2>
<h2 class="mb-3">Pronto para começar?</h2>
<p class="lead mb-4 text-muted">
@tenant.CtaDescription
Crie sua página profissional agora mesmo e comece a organizar seus links.
</p>
@if (User.Identity?.IsAuthenticated == true)
{
<a asp-controller="Admin" asp-action="Dashboard" class="btn btn-primary btn-lg">
@tenant.CtaButtonText
Criar Minha Página
</a>
}
else
{
<a asp-controller="Auth" asp-action="Login" class="btn btn-primary btn-lg">
@tenant.CtaButtonText
Começar Grátis
</a>
}
</div>

View File

@ -1,81 +1,33 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = $"Planos e Preços - {tenant.SiteName}";
ViewData["Title"] = "Planos e Preços - BCards";
//var isPreview = ViewBag.IsPreview as bool? ?? false;
//Layout = isPreview ? "_Layout" : "_UserPageLayout";
Layout = "_Layout";
}
<div class="container-fluid px-3 px-lg-5 py-5">
<div class="container py-5">
<div class="text-center mb-5">
<h1 class="display-5 fw-bold mb-3">Escolha o plano ideal para você</h1>
<p class="lead text-muted">Comece grátis e faça upgrade quando precisar de mais recursos</p>
<!-- Toggle Mensal/Anual -->
<div class="d-flex justify-content-center mb-4 mt-3">
<div class="pricing-toggle-wrapper">
<span class="pricing-savings-chip">🎁 2 meses grátis</span>
<div class="pricing-pill">
<button type="button" class="pill-btn pill-active" id="btnMonthly">Mensal</button>
<button type="button" class="pill-btn" id="btnYearly">Anual</button>
<div class="d-flex justify-content-center mb-4">
<div class="btn-group" role="group" aria-label="Período de cobrança">
<input type="radio" class="btn-check" name="billingPeriod" id="monthly" autocomplete="off" checked>
<label class="btn btn-outline-primary" for="monthly">Mensal</label>
<input type="radio" class="btn-check" name="billingPeriod" id="yearly" autocomplete="off">
<label class="btn btn-outline-primary" for="yearly">
Anual
<span class="badge bg-success ms-1">2 meses grátis</span>
</label>
</div>
</div>
</div>
<style>
.pricing-toggle-wrapper {
position: relative;
display: inline-block;
}
.pricing-savings-chip {
position: absolute;
top: -24px;
right: -4px;
background: #198754;
color: #fff;
font-size: 0.68rem;
font-weight: 700;
padding: 2px 9px;
border-radius: 20px;
white-space: nowrap;
letter-spacing: 0.02em;
}
.pricing-pill {
display: inline-flex;
background: #ededf3;
border-radius: 50px;
padding: 4px;
gap: 2px;
}
.pill-btn {
padding: 8px 32px;
border-radius: 50px;
border: none;
background: transparent;
color: #555;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: background 0.18s, color 0.18s, box-shadow 0.18s;
white-space: nowrap;
}
.pill-btn.pill-active {
background: var(--tenant-primary, #667eea);
color: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,.18);
}
.pricing-cards .card-body {
font-size: 0.875rem;
}
.pricing-cards .card-header h5 {
font-size: 0.95rem;
}
</style>
</div>
<div class="row g-4 row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-5 pricing-cards">
<div class="row g-3 justify-content-center pricing-cards">
<!-- Plano Trial -->
<div class="col">
<div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-success bg-opacity-10 text-center py-4">
<h5 class="mb-0">Trial Gratuito</h5>
@ -111,7 +63,7 @@
<div class="card-footer bg-transparent p-4">
@if (User.Identity?.IsAuthenticated == true)
{
<a asp-controller="Admin" asp-action="ManagePage" asp-route-id="new" class="btn btn-success w-100">Começar Grátis</a>
<a asp-controller="Admin" asp-action="CreatePage" class="btn btn-success w-100">Começar Grátis</a>
}
else
{
@ -122,20 +74,20 @@
</div>
<!-- Plano Básico -->
<div class="col">
<div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-light text-center py-4">
<h5 class="mb-0">Básico</h5>
<div class="mt-3">
<div class="pricing-monthly">
<span class="display-5 fw-bold text-primary">R$ 12,90</span>
<span class="display-5 fw-bold text-primary">R$ 5,90</span>
<span class="text-muted">/mês</span>
</div>
<div class="pricing-yearly d-none">
<span class="display-5 fw-bold text-primary">R$ 129,00</span>
<span class="display-5 fw-bold text-primary">R$ 59,00</span>
<span class="text-muted">/ano</span>
<br>
<small class="text-success">Economize R$ 25,80 (2 meses grátis)</small>
<small class="text-success">Economize R$ 11,80 (2 meses grátis)</small>
</div>
</div>
</div>
@ -196,20 +148,20 @@
</div>
<!-- Plano Profissional (Decoy) -->
<div class="col">
<div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-warning bg-opacity-10 text-center py-4">
<h5 class="mb-0">Profissional</h5>
<div class="mt-3">
<div class="pricing-monthly">
<span class="display-5 fw-bold text-warning">R$ 25,90</span>
<span class="display-5 fw-bold text-warning">R$ 12,90</span>
<span class="text-muted">/mês</span>
</div>
<div class="pricing-yearly d-none">
<span class="display-5 fw-bold text-warning">R$ 259,00</span>
<span class="display-5 fw-bold text-warning">R$ 129,00</span>
<span class="text-muted">/ano</span>
<br>
<small class="text-success">Economize R$ 51,80 (2 meses grátis)</small>
<small class="text-success">Economize R$ 25,80 (2 meses grátis)</small>
</div>
</div>
</div>
@ -270,21 +222,23 @@
</div>
<!-- Plano Premium (Mais Popular) -->
<div class="col">
<div class="card h-100 border-primary shadow">
<div class="card-header bg-primary text-white text-center pt-3 pb-4">
<span class="badge bg-white text-primary px-3 py-1 mb-2 d-inline-block" style="font-size:0.7rem;letter-spacing:.04em;">⭐ Mais Popular</span>
<div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-primary shadow position-relative">
<div class="position-absolute top-0 start-50 translate-middle">
<span class="badge bg-primary px-3 py-2">Mais Popular</span>
</div>
<div class="card-header bg-primary text-white text-center py-4">
<h5 class="mb-0">Premium</h5>
<div class="mt-3">
<div class="pricing-monthly">
<span class="display-5 fw-bold">R$ 29,90</span>
<span class="display-5 fw-bold">R$ 19,90</span>
<span class="opacity-75">/mês</span>
</div>
<div class="pricing-yearly d-none">
<span class="display-5 fw-bold">R$ 299,00</span>
<span class="display-5 fw-bold">R$ 199,00</span>
<span class="opacity-75">/ano</span>
<br>
<small class="text-light">Economize R$ 59,80 (2 meses grátis)</small>
<small class="text-light">Economize R$ 39,80 (2 meses grátis)</small>
</div>
</div>
<small class="opacity-75">Melhor custo-benefício!</small>
@ -315,10 +269,6 @@
<i class="text-success me-2">✓</i>
Suporte prioritário
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
Upload de PDFs (até 5 arquivos)
</li>
<li class="mb-3">
<i class="text-muted me-2">✗</i>
<span class="text-muted">Links de produto</span>
@ -353,21 +303,23 @@
</div>
<!-- Plano Premium + Afiliados -->
<div class="col">
<div class="col-xl-2 col-lg-4 col-md-6">
<div class="card h-100 border-success shadow">
<div class="card-header bg-success text-white text-center pt-3 pb-4">
<span class="badge bg-white text-success px-3 py-1 mb-2 d-inline-block" style="font-size:0.7rem;letter-spacing:.04em;">🆕 Novo!</span>
<div class="position-absolute top-0 start-50 translate-middle pricing-premium-badge">
<span class="badge bg-success px-3 py-2">Novo!</span>
</div>
<div class="card-header bg-success text-white text-center py-4">
<h5 class="mb-0">Premium + Afiliados</h5>
<div class="mt-3">
<div class="pricing-monthly">
<span class="display-5 fw-bold">R$ 34,90</span>
<span class="display-5 fw-bold">R$ 29,90</span>
<span class="opacity-75">/mês</span>
</div>
<div class="pricing-yearly d-none">
<span class="display-5 fw-bold">R$ 349,00</span>
<span class="display-5 fw-bold">R$ 299,00</span>
<span class="opacity-75">/ano</span>
<br>
<small class="text-light">Economize R$ 69,80 (2 meses grátis)</small>
<small class="text-light">Economize R$ 59,80 (2 meses grátis)</small>
</div>
</div>
<small class="opacity-75">Para monetização!</small>
@ -402,10 +354,6 @@
<i class="text-success me-2">✓</i>
10 links afiliados
</li>
<li class="mb-3">
<i class="text-success me-2">✓</i>
Upload de PDFs (até 10 arquivos)
</li>
</ul>
<div class="mt-3">
<small class="text-muted">* 20 temas básicos + 20 temas premium exclusivos</small>
@ -436,7 +384,6 @@
</div>
</div>
<div class="container">
<!-- Comparação de recursos -->
<div class="mt-5 pt-5">
<h2 class="text-center mb-4">Compare todos os recursos</h2>
@ -578,7 +525,6 @@
</div>
</div>
</div>
</div><!-- /container inner -->
</div>
@if (TempData["Success"] != null)
@ -631,20 +577,22 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
const btnMonthly = document.getElementById('btnMonthly');
const btnYearly = document.getElementById('btnYearly');
const monthlyRadio = document.getElementById('monthly');
const yearlyRadio = document.getElementById('yearly');
const monthlyElements = document.querySelectorAll('.pricing-monthly');
const yearlyElements = document.querySelectorAll('.pricing-yearly');
function setPricing(period) {
const isYearly = period === 'yearly';
btnMonthly.classList.toggle('pill-active', !isYearly);
btnYearly.classList.toggle('pill-active', isYearly);
monthlyElements.forEach(el => el.classList.toggle('d-none', isYearly));
yearlyElements.forEach(el => el.classList.toggle('d-none', !isYearly));
function togglePricing() {
if (yearlyRadio.checked) {
monthlyElements.forEach(el => el.classList.add('d-none'));
yearlyElements.forEach(el => el.classList.remove('d-none'));
} else {
monthlyElements.forEach(el => el.classList.remove('d-none'));
yearlyElements.forEach(el => el.classList.add('d-none'));
}
}
btnMonthly.addEventListener('click', () => setPricing('monthly'));
btnYearly.addEventListener('click', () => setPricing('yearly'));
monthlyRadio.addEventListener('change', togglePricing);
yearlyRadio.addEventListener('change', togglePricing);
});
</script>

View File

@ -1,6 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
ViewData["Title"] = "Diretrizes da Comunidade";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@ -10,13 +8,13 @@
<div class="col-lg-10">
<div class="card border-0 shadow-sm">
<div class="card-body p-4 p-md-5">
<h1 class="card-title text-primary fw-bold">Diretrizes da Comunidade @tenant.SiteName</h1>
<h1 class="card-title text-primary fw-bold">Diretrizes da Comunidade BCards</h1>
<p class="text-muted">Última atualização: 31 de agosto de 2025</p>
<hr class="my-4">
<p>O @tenant.SiteName se dedica a manter uma comunidade segura, profissional e respeitosa. Todas as páginas e conteúdos criados em nossa plataforma devem seguir estas regras. O descumprimento pode levar à remoção do conteúdo, suspensão da página ou banimento da conta.</p>
<p>Nosso objetivo é capacitar criadores e profissionais. Estas regras nos ajudam a garantir que o @tenant.SiteName continue sendo uma plataforma confiável e valiosa para todos.</p>
<p>O BCards se dedica a manter uma comunidade segura, profissional e respeitosa. Todas as páginas e conteúdos criados em nossa plataforma devem seguir estas regras. O descumprimento pode levar à remoção do conteúdo, suspensão da página ou banimento da conta.</p>
<p>Nosso objetivo é capacitar criadores e profissionais. Estas regras nos ajudam a garantir que o BCards continue sendo uma plataforma confiável e valiosa para todos.</p>
<h4 class="mt-5 fw-bold text-danger">1. Conteúdo Estritamente Proibido</h4>
<p>O seguinte conteúdo será removido imediatamente e pode resultar no banimento da conta sem aviso prévio:</p>
@ -50,7 +48,7 @@
<div class="alert alert-info mt-5" role="alert">
<h5 class="alert-heading">Como Denunciar</h5>
<p>Se você encontrar conteúdo que viola estas diretrizes, por favor, denuncie através do link "Denunciar" presente no rodapé de todas as páginas de usuário ou envie um e-mail para <a href="mailto:@tenant.SupportEmail">@tenant.SupportEmail</a>. Levamos todas as denúncias a sério.</p>
<p>Se você encontrar conteúdo que viola estas diretrizes, por favor, denuncie através do link "Denunciar" presente no rodapé de todas as páginas de usuário ou envie um e-mail para <a href="mailto:suporte@bcards.site">suporte@bcards.site</a>. Levamos todas as denúncias a sério.</p>
</div>
</div>
</div>

View File

@ -1,8 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
var supportParts = tenant.SupportEmail.Split('@');
var dpoParts = tenant.DpoEmail.Split('@');
ViewData["Title"] = "Política de Privacidade";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@ -17,22 +13,22 @@
<hr class="my-4">
<p>Bem-vindo à Política de Privacidade do <strong>@tenant.SiteName</strong>. Sua privacidade é de extrema importância para nós. Este documento explica como coletamos, usamos, compartilhamos e protegemos suas informações pessoais, em conformidade com a Lei Geral de Proteção de Dados (LGPD), Lei nº 13.709/2018, do Brasil, e outras legislações aplicáveis nos países em que atuamos.</p>
<p>Bem-vindo à Política de Privacidade do <strong>BCards</strong>. Sua privacidade é de extrema importância para nós. Este documento explica como coletamos, usamos, compartilhamos e protegemos suas informações pessoais, em conformidade com a Lei Geral de Proteção de Dados (LGPD), Lei nº 13.709/2018, do Brasil, e outras legislações aplicáveis nos países em que atuamos.</p>
<h4 class="mt-5 fw-bold">1. Dados Pessoais Coletados e a Finalidade</h4>
<p>Coletamos diferentes tipos de informações para fornecer e melhorar nossos serviços para você:</p>
<ul>
<li><strong>Dados de Cadastro e Autenticação:</strong> Ao se registrar usando Google ou Microsoft (OAuth), coletamos seu nome, endereço de e-mail e foto de perfil. Usamos esses dados para criar e gerenciar sua conta, permitir seu acesso à plataforma e nos comunicarmos com você.</li>
<li><strong>Conteúdo da Página Pública:</strong> Coletamos as informações que você insere em sua página @tenant.SiteName, como título, biografia, links, categoria de negócio e imagens. A finalidade é exibir publicamente sua página conforme sua configuração.</li>
<li><strong>Conteúdo da Página Pública:</strong> Coletamos as informações que você insere em sua página BCards, como título, biografia, links, categoria de negócio e imagens. A finalidade é exibir publicamente sua página conforme sua configuração.</li>
<li><strong>Dados de Pagamento:</strong> Para nossos planos pagos, utilizamos o Stripe como processador de pagamentos. Não armazenamos os dados do seu cartão de crédito. O Stripe coleta as informações necessárias para processar a transação de forma segura.</li>
<li><strong>Dados de Análise e Uso (Analytics):</strong> Coletamos dados sobre como os visitantes e você interagem com as páginas @tenant.SiteName. Isso inclui visualizações de página, cliques em links, endereço IP (anonimizado sempre que possível), tipo de dispositivo, sistema operacional e navegador. Usamos esses dados para fornecer estatísticas, entender o uso da plataforma e melhorar nossos serviços.</li>
<li><strong>Dados de Análise e Uso (Analytics):</strong> Coletamos dados sobre como os visitantes e você interagem com as páginas BCards. Isso inclui visualizações de página, cliques em links, endereço IP (anonimizado sempre que possível), tipo de dispositivo, sistema operacional e navegador. Usamos esses dados para fornecer estatísticas, entender o uso da plataforma e melhorar nossos serviços.</li>
<li><strong>Cookies e Tecnologias Semelhantes:</strong> Usamos cookies essenciais para o funcionamento da plataforma (ex: manter sua sessão ativa) e cookies de análise. Para mais detalhes, consulte nossa seção sobre Cookies.</li>
</ul>
<h4 class="mt-5 fw-bold">2. Base Legal para o Tratamento de Dados (LGPD)</h4>
<p>O tratamento de seus dados pessoais é realizado com base nas seguintes hipóteses legais previstas no Art. 7º da LGPD:</p>
<ul>
<li><strong>Execução de Contrato (Inciso V):</strong> A maior parte da nossa coleta e processamento de dados é necessária para executar o contrato de serviço que você aceita ao criar uma conta no @tenant.SiteName.</li>
<li><strong>Execução de Contrato (Inciso V):</strong> A maior parte da nossa coleta e processamento de dados é necessária para executar o contrato de serviço que você aceita ao criar uma conta no BCards.</li>
<li><strong>Consentimento (Inciso I):</strong> Para o uso de cookies não essenciais e para o envio de comunicações de marketing, solicitaremos seu consentimento explícito.</li>
<li><strong>Legítimo Interesse (Inciso IX):</strong> Para análises de uso da plataforma e prevenção a fraudes, tratamos os dados com base em nosso legítimo interesse, sempre balanceando com seus direitos e liberdades.</li>
<li><strong>Cumprimento de Obrigação Legal (Inciso II):</strong> Podemos tratar dados para cumprir obrigações legais, como a emissão de notas fiscais ou ordens judiciais.</li>
@ -59,7 +55,7 @@
<li><strong>Informação sobre Compartilhamento:</strong> O direito de saber com quais entidades públicas e privadas compartilhamos seus dados.</li>
<li><strong>Revogação do Consentimento:</strong> O direito de revogar seu consentimento a qualquer momento.</li>
</ul>
<p>Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO) através do e-mail <a class="email-obfuscated" data-user="@dpoParts[0]" data-domain="@dpoParts[1]">[carregando e-mail...]</a>. O prazo para resposta é de até 15 dias, conforme a legislação.</p>
<p>Para exercer seus direitos, entre em contato com nosso Encarregado de Proteção de Dados (DPO) através do e-mail <a class="email-obfuscated" data-user="dpo" data-domain="bcards.site">[carregando e-mail...]</a>. O prazo para resposta é de até 15 dias, conforme a legislação.</p>
<h4 class="mt-5 fw-bold">5. Cookies e Tecnologias de Rastreamento</h4>
<p>Utilizamos cookies para melhorar sua experiência. Cookies são pequenos arquivos de texto armazenados em seu dispositivo. Você pode gerenciar suas preferências de cookies através do nosso banner de consentimento ou nas configurações do seu navegador.</p>
@ -77,7 +73,7 @@
<h4 class="mt-5 fw-bold">8. Contato do Encarregado de Proteção de Dados (DPO)</h4>
<p>Para qualquer dúvida sobre esta Política de Privacidade ou para exercer seus direitos, entre em contato com nosso DPO:</p>
<p><strong>E-mail:</strong> <a href="mailto:@tenant.DpoEmail" class="fw-bold">@tenant.DpoEmail</a></p>
<p><strong>E-mail:</strong> <a href="mailto:dpo@bcards.site" class="fw-bold">dpo@bcards.site</a></p>
<h4 class="mt-5 fw-bold">9. Alterações a esta Política</h4>
<p>Podemos atualizar esta Política de Privacidade periodicamente. Notificaremos você sobre quaisquer alterações significativas através de um aviso em nosso site ou por e-mail.</p>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
var dpoParts = tenant.DpoEmail.Split('@');
ViewData["Title"] = "Política de Privacidad";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@ -16,15 +13,15 @@
<hr class="my-4">
<p>Bienvenido a la Política de Privacidad de <strong>@tenant.SiteName</strong>. Su privacidad es de suma importancia para nosotros. Este documento explica cómo recopilamos, usamos, compartimos y protegemos su información personal, en conformidad con las leyes de protección de datos de los países en los que operamos, incluyendo la Ley 1581 de 2012 de Colombia, la Ley 19.628 de Chile, y la Ley Federal de Protección de Datos Personales en Posesión de los Particulares de México.</p>
<p>Bienvenido a la Política de Privacidad de <strong>BCards</strong>. Su privacidad es de suma importancia para nosotros. Este documento explica cómo recopilamos, usamos, compartimos y protegemos su información personal, en conformidad con las leyes de protección de datos de los países en los que operamos, incluyendo la Ley 1581 de 2012 de Colombia, la Ley 19.628 de Chile, y la Ley Federal de Protección de Datos Personales en Posesión de los Particulares de México.</p>
<h4 class="mt-5 fw-bold">1. Datos Personales Recopilados y su Finalidad</h4>
<p>Recopilamos diferentes tipos de información para proporcionar y mejorar nuestros servicios para usted:</p>
<ul>
<li><strong>Datos de Registro y Autenticación:</strong> Al registrarse usando Google o Microsoft (OAuth), recopilamos su nombre, dirección de correo electrónico y foto de perfil. Usamos estos datos para crear y administrar su cuenta, permitir su acceso a la plataforma y comunicarnos con usted.</li>
<li><strong>Contenido de la Página Pública:</strong> Recopilamos la información que usted introduce en su página @tenant.SiteName, como título, biografía, enlaces, categoría de negocio e imágenes. La finalidad es mostrar públicamente su página según su configuración.</li>
<li><strong>Contenido de la Página Pública:</strong> Recopilamos la información que usted introduce en su página BCards, como título, biografía, enlaces, categoría de negocio e imágenes. La finalidad es mostrar públicamente su página según su configuración.</li>
<li><strong>Datos de Pago:</strong> Para nuestros planes de pago, utilizamos Stripe como procesador de pagos. No almacenamos los datos de su tarjeta de crédito. Stripe recopila la información necesaria para procesar la transacción de forma segura.</li>
<li><strong>Datos de Análisis y Uso (Analytics):</strong> Recopilamos datos sobre cómo los visitantes y usted interactúan con las páginas @tenant.SiteName. Esto incluye vistas de página, clics en enlaces, dirección IP (anonimizada siempre que sea posible), tipo de dispositivo, sistema operativo y navegador. Usamos estos datos para proporcionar estadísticas, entender el uso de la plataforma y mejorar nuestros servicios.</li>
<li><strong>Datos de Análisis y Uso (Analytics):</strong> Recopilamos datos sobre cómo los visitantes y usted interactúan con las páginas BCards. Esto incluye vistas de página, clics en enlaces, dirección IP (anonimizada siempre que sea posible), tipo de dispositivo, sistema operativo y navegador. Usamos estos datos para proporcionar estadísticas, entender el uso de la plataforma y mejorar nuestros servicios.</li>
<li><strong>Cookies y Tecnologías Similares:</strong> Usamos cookies esenciales para el funcionamiento de la plataforma (ej: mantener su sesión activa) y cookies de análisis.</li>
</ul>
@ -50,14 +47,14 @@
<li><strong>Oposición:</strong> El derecho a oponerse al tratamiento de sus datos para ciertos fines.</li>
<li><strong>Portabilidad:</strong> El derecho a recibir sus datos en un formato estructurado.</li>
</ul>
<p>Para ejercer sus derechos (conocidos como derechos ARCO en México y Chile), por favor contacte a nuestro Oficial de Protección de Datos (DPO) a través del correo electrónico <a class="email-obfuscated" data-user="@dpoParts[0]" data-domain="@dpoParts[1]">[cargando email...]</a>.</p>
<p>Para ejercer sus derechos (conocidos como derechos ARCO en México y Chile), por favor contacte a nuestro Oficial de Protección de Datos (DPO) a través del correo electrónico <a class="email-obfuscated" data-user="dpo" data-domain="bcards.site">[cargando email...]</a>.</p>
<h4 class="mt-5 fw-bold">5. Retención de Datos</h4>
<p>Mantendremos sus datos personales mientras su cuenta esté activa. Si su cuenta es desactivada o permanece inactiva por más de 12 meses, sus datos serán anonimizados o eliminados, excepto aquellos que necesitemos retener para cumplir con obligaciones legales.</p>
<h4 class="mt-5 fw-bold">6. Contacto del Oficial de Protección de Datos (DPO)</h4>
<p>Para cualquier pregunta sobre esta Política de Privacidad o para ejercer sus derechos, contacte a nuestro DPO:</p>
<p><strong>Correo Electrónico:</strong> <a href="mailto:@tenant.DpoEmail" class="fw-bold">@tenant.DpoEmail</a></p>
<p><strong>Correo Electrónico:</strong> <a href="mailto:dpo@bcards.site" class="fw-bold">dpo@bcards.site</a></p>
<h4 class="mt-5 fw-bold">7. Cambios a esta Política</h4>
<p>Podemos actualizar esta Política de Privacidad periódicamente. Le notificaremos sobre cualquier cambio significativo a través de un aviso en nuestro sitio web o por correo electrónico.</p>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
var dpoParts = tenant.DpoEmail.Split('@');
ViewData["Title"] = "Direitos do Titular de Dados";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@ -16,7 +13,7 @@
<hr class="my-4">
<p>Em conformidade com a Lei Geral de Proteção de Dados (LGPD) e outras legislações de privacidade, o <strong>@tenant.SiteName</strong> garante a você o controle sobre seus dados pessoais. Você pode solicitar acesso, correção, portabilidade ou exclusão de suas informações a qualquer momento.</p>
<p>Em conformidade com a Lei Geral de Proteção de Dados (LGPD) e outras legislações de privacidade, o <strong>BCards</strong> garante a você o controle sobre seus dados pessoais. Você pode solicitar acesso, correção, portabilidade ou exclusão de suas informações a qualquer momento.</p>
<h4 class="mt-5 fw-bold">Como Fazer uma Solicitação</h4>
<p>Para garantir a segurança do processo e a correta identificação do titular, todas as solicitações devem ser enviadas para nosso Encarregado de Proteção de Dados (DPO) através do seguinte canal:</p>
@ -24,13 +21,13 @@
<div class="alert alert-secondary text-center">
<h5 class="alert-heading">Canal de Atendimento ao Titular</h5>
<p class="mb-0">Envie um e-mail para:</p>
<a class="email-obfuscated fs-5 fw-bold" data-user="@dpoParts[0]" data-domain="@dpoParts[1]">[carregando e-mail...]</a>
<a class="email-obfuscated fs-5 fw-bold" data-user="dpo" data-domain="bcards.site">[carregando e-mail...]</a>
</div>
<p>No seu e-mail, por favor, inclua:</p>
<ul>
<li><strong>Nome Completo:</strong> O nome associado à sua conta.</li>
<li><strong>E-mail de Cadastro:</strong> O e-mail que você usou para se registrar no @tenant.SiteName.</li>
<li><strong>E-mail de Cadastro:</strong> O e-mail que você usou para se registrar no BCards.</li>
<li><strong>Tipo de Solicitação:</strong> Especifique o que você deseja (ex: "Solicitação de Acesso aos Dados", "Pedido de Exclusão de Conta e Dados", "Correção de Informações").</li>
<li><strong>Detalhes da Solicitação:</strong> Forneça qualquer detalhe adicional que possa nos ajudar a atender seu pedido.</li>
</ul>
@ -39,7 +36,7 @@
<p>Após o recebimento da sua solicitação, nossa equipe poderá entrar em contato para validar sua identidade. O prazo legal para resposta a solicitações de confirmação e acesso é de até <strong>15 dias</strong>. Para outras solicitações, responderemos o mais breve possível, respeitando os prazos definidos na legislação aplicável.</p>
<div class="alert alert-info mt-5">
<strong>Nota:</strong> A exclusão de dados é um processo irreversível e resultará na perda permanente de sua conta e de todas as suas páginas @tenant.SiteName.
<strong>Nota:</strong> A exclusão de dados é um processo irreversível e resultará na perda permanente de sua conta e de todas as suas páginas BCards.
</div>
</div>
</div>

View File

@ -1,7 +1,4 @@
@inject Microsoft.Extensions.Options.IOptions<BCards.Web.Configuration.TenantSettings> TenantConfig
@{
var tenant = TenantConfig.Value;
var supportParts = tenant.SupportEmail.Split('@');
ViewData["Title"] = "Termos de Uso";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@ -16,20 +13,20 @@
<hr class="my-4">
<p>Bem-vindo ao <strong>@tenant.SiteName</strong>. Ao acessar ou usar nossa plataforma, você concorda em cumprir e estar vinculado a estes Termos de Uso. Por favor, leia-os com atenção.</p>
<p>Bem-vindo ao <strong>BCards</strong>. Ao acessar ou usar nossa plataforma, você concorda em cumprir e estar vinculado a estes Termos de Uso. Por favor, leia-os com atenção.</p>
<h4 class="mt-5 fw-bold">1. Aceitação dos Termos</h4>
<p>Ao criar uma conta ou usar os serviços do @tenant.SiteName, você celebra um contrato legalmente vinculativo conosco e concorda com estes Termos, nossa <a asp-action="Privacy">Política de Privacidade</a> e nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>.</p>
<p>Ao criar uma conta ou usar os serviços do BCards, você celebra um contrato legalmente vinculativo conosco e concorda com estes Termos, nossa <a asp-action="Privacy">Política de Privacidade</a> e nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>.</p>
<h4 class="mt-5 fw-bold">2. Descrição do Serviço</h4>
<p>O @tenant.SiteName é uma plataforma que permite a indivíduos e empresas criar e gerenciar uma página pública personalizada contendo links para seus sites e redes sociais. Oferecemos planos gratuitos e pagos com diferentes níveis de funcionalidade.</p>
<p>O BCards é uma plataforma que permite a indivíduos e empresas criar e gerenciar uma página pública personalizada contendo links para seus sites e redes sociais. Oferecemos planos gratuitos e pagos com diferentes níveis de funcionalidade.</p>
<h4 class="mt-5 fw-bold">3. Responsabilidades do Usuário</h4>
<p>Você é o único responsável por todo o conteúdo que publica em sua página @tenant.SiteName e por garantir que ele cumpra todas as leis aplicáveis e nossas diretrizes. Você concorda em:</p>
<p>Você é o único responsável por todo o conteúdo que publica em sua página BCards e por garantir que ele cumpra todas as leis aplicáveis e nossas diretrizes. Você concorda em:</p>
<ul>
<li>Fornecer informações de registro precisas e mantê-las atualizadas.</li>
<li>Manter a segurança de sua senha e conta. Você é responsável por todas as atividades que ocorrem em sua conta.</li>
<li>Não usar o @tenant.SiteName para qualquer finalidade ilegal ou não autorizada.</li>
<li>Não usar o BCards para qualquer finalidade ilegal ou não autorizada.</li>
<li>Não violar nossas <a asp-action="CommunityGuidelines">Diretrizes da Comunidade</a>, que proíbem conteúdo de ódio, violência, spam, nudez, entre outros.</li>
<li>Possuir os direitos ou as permissões necessárias para todo o conteúdo que você publica.</li>
</ul>
@ -46,12 +43,12 @@
<h4 class="mt-5 fw-bold">6. Propriedade Intelectual</h4>
<ul>
<li><strong>Seu Conteúdo:</strong> Você retém todos os direitos de propriedade intelectual sobre o conteúdo que publica em sua página @tenant.SiteName. No entanto, você nos concede uma licença mundial, não exclusiva e isenta de royalties para hospedar, exibir, reproduzir e distribuir seu conteúdo publicamente como parte da prestação do serviço.</li>
<li><strong>Nossa Plataforma:</strong> O @tenant.SiteName e todo o seu conteúdo original, recursos e funcionalidades (incluindo, mas não se limitando a, software, texto, gráficos e logotipos) são de propriedade exclusiva do @tenant.SiteName e seus licenciadores e são protegidos por leis de direitos autorais e outras leis de propriedade intelectual.</li>
<li><strong>Seu Conteúdo:</strong> Você retém todos os direitos de propriedade intelectual sobre o conteúdo que publica em sua página BCards. No entanto, você nos concede uma licença mundial, não exclusiva e isenta de royalties para hospedar, exibir, reproduzir e distribuir seu conteúdo publicamente como parte da prestação do serviço.</li>
<li><strong>Nossa Plataforma:</strong> O BCards e todo o seu conteúdo original, recursos e funcionalidades (incluindo, mas não se limitando a, software, texto, gráficos e logotipos) são de propriedade exclusiva do BCards e seus licenciadores e são protegidos por leis de direitos autorais e outras leis de propriedade intelectual.</li>
</ul>
<h4 class="mt-5 fw-bold">7. Limitação de Responsabilidade</h4>
<p>NA MÁXIMA EXTENSÃO PERMITIDA PELA LEI, O @tenant.SiteName.ToUpper() E SEUS DIRETORES, FUNCIONÁRIOS E AFILIADOS NÃO SERÃO RESPONSÁVEIS POR QUAISQUER DANOS INDIRETOS, INCIDENTAIS, ESPECIAIS, CONSEQUENCIAIS OU PUNITIVOS, OU QUALQUER PERDA DE LUCROS OU RECEITAS, SEJA INCORRIDA DIRETA OU INDIRETAMENTE, OU QUALQUER PERDA DE DADOS, USO, BOA VONTADE OU OUTRAS PERDAS INTANGÍVEIS, RESULTANTES DE:</p>
<p>NA MÁXIMA EXTENSÃO PERMITIDA PELA LEI, O BCARDS E SEUS DIRETORES, FUNCIONÁRIOS E AFILIADOS NÃO SERÃO RESPONSÁVEIS POR QUAISQUER DANOS INDIRETOS, INCIDENTAIS, ESPECIAIS, CONSEQUENCIAIS OU PUNITIVOS, OU QUALQUER PERDA DE LUCROS OU RECEITAS, SEJA INCORRIDA DIRETA OU INDIRETAMENTE, OU QUALQUER PERDA DE DADOS, USO, BOA VONTADE OU OUTRAS PERDAS INTANGÍVEIS, RESULTANTES DE:</p>
<ul>
<li>(a) SEU ACESSO OU USO OU INCAPACIDADE DE ACESSAR OU USAR O SERVIÇO;</li>
<li>(b) QUALQUER CONDUTA OU CONTEÚDO DE TERCEIROS NO SERVIÇO;</li>
@ -66,8 +63,8 @@
<h4 class="mt-5 fw-bold">9. Disposições Gerais</h4>
<ul>
<li><strong>Legislação Aplicável:</strong> Estes Termos serão regidos e interpretados de acordo com as leis da República Federativa do Brasil, sem consideração com o conflito de disposições legais.</li>
<li><strong>Alterações nos Termos:</strong> Podemos modificar estes Termos a qualquer momento. Se fizermos alterações materiais, forneceremos um aviso com antecedência razoável. Ao continuar a usar o @tenant.SiteName após as alterações entrarem em vigor, você concorda em ficar vinculado aos termos revisados.</li>
<li><strong>Contato:</strong> Para qualquer dúvida sobre estes Termos, entre em contato conosco pelo e-mail <a class="email-obfuscated" data-user="@supportParts[0]" data-domain="@supportParts[1]">[carregando e-mail...]</a>.</li>
<li><strong>Alterações nos Termos:</strong> Podemos modificar estes Termos a qualquer momento. Se fizermos alterações materiais, forneceremos um aviso com antecedência razoável. Ao continuar a usar o BCards após as alterações entrarem em vigor, você concorda em ficar vinculado aos termos revisados.</li>
<li><strong>Contato:</strong> Para qualquer dúvida sobre estes Termos, entre em contato conosco pelo e-mail <a class="email-obfuscated" data-user="suporte" data-domain="bcards.site">[carregando e-mail...]</a>.</li>
</ul>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More