feat: ajustes e artigos
This commit is contained in:
parent
2ba9a675b5
commit
232d4d6c54
@ -24,7 +24,12 @@
|
|||||||
"Bash(dotnet restore:*)",
|
"Bash(dotnet restore:*)",
|
||||||
"Bash(rg:*)",
|
"Bash(rg:*)",
|
||||||
"Bash(dotnet test:*)",
|
"Bash(dotnet test:*)",
|
||||||
"Bash(cp:*)"
|
"Bash(cp:*)",
|
||||||
|
"Bash(ping:*)",
|
||||||
|
"Bash(nc:*)",
|
||||||
|
"Bash(ssh:*)",
|
||||||
|
"Read(//mnt/c/vscode/**)",
|
||||||
|
"Read(//mnt/c/**)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@ -182,7 +182,6 @@ jobs:
|
|||||||
--replicas 2 \
|
--replicas 2 \
|
||||||
--network qrrapido-network \
|
--network qrrapido-network \
|
||||||
--publish published=5001,target=8080 \
|
--publish published=5001,target=8080 \
|
||||||
--constraint "node.role==worker" \
|
|
||||||
--mount type=bind,source=/app/keys,target=/app/keys \
|
--mount type=bind,source=/app/keys,target=/app/keys \
|
||||||
--env ASPNETCORE_ENVIRONMENT=Production \
|
--env ASPNETCORE_ENVIRONMENT=Production \
|
||||||
--env ASPNETCORE_URLS=http://+:8080 \
|
--env ASPNETCORE_URLS=http://+:8080 \
|
||||||
|
|||||||
286
CLAUDE.md
Normal file
286
CLAUDE.md
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
QR Rapido is an ultra-fast QR code generator built with ASP.NET Core 8.0, focusing on speed and multilingual support (PT-BR, ES, EN). It features tiered user access (anonymous, logged-in free, premium), OAuth authentication (Google/Microsoft), Stripe payment integration, and ad-free session management.
|
||||||
|
|
||||||
|
**Key Performance Targets:**
|
||||||
|
- QR generation: <1.2s (average), <0.4s (premium users)
|
||||||
|
- Cache hit rate: >80%
|
||||||
|
- First Contentful Paint: <2s
|
||||||
|
|
||||||
|
## Common Commands
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
# Run locally (hot reload)
|
||||||
|
dotnet watch run
|
||||||
|
|
||||||
|
# Run locally (standard)
|
||||||
|
dotnet run
|
||||||
|
|
||||||
|
# Build for release
|
||||||
|
dotnet build --configuration Release
|
||||||
|
|
||||||
|
# Restore dependencies
|
||||||
|
dotnet restore
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
dotnet test
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
dotnet test --collect:"XPlat Code Coverage"
|
||||||
|
|
||||||
|
# Run specific test class
|
||||||
|
dotnet test --filter "QRRapidoServiceTests"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Build
|
||||||
|
```bash
|
||||||
|
# Development mode with Vite
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Production build (Vite)
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Preview production build
|
||||||
|
npm run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
```bash
|
||||||
|
# Build and run all services (MongoDB, Redis, Nginx)
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f qrrapido
|
||||||
|
|
||||||
|
# Build production image for ARM64 (OCI Ampere servers)
|
||||||
|
docker buildx build --platform linux/arm64 -t qrrapido:latest .
|
||||||
|
|
||||||
|
# Stop all services
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stripe Webhooks (Local Testing - WSL)
|
||||||
|
```bash
|
||||||
|
# Forward Stripe webhooks to local HTTPS endpoint
|
||||||
|
stripe listen --forward-to https://localhost:52428/pagamento/stripewebhook --skip-verify
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Core Technology Stack
|
||||||
|
- **Backend**: ASP.NET Core 8.0 (MVC + Razor Pages)
|
||||||
|
- **Database**: MongoDB (users, QR history, sessions)
|
||||||
|
- **Cache**: Redis (distributed cache) + MemoryCache (fallback)
|
||||||
|
- **QR Generation**: QRCoder library with ImageSharp for logo overlay
|
||||||
|
- **Authentication**: Cookie-based with OAuth (Google, Microsoft)
|
||||||
|
- **Payments**: Stripe (subscriptions)
|
||||||
|
- **Logging**: Serilog → OpenSearch/Console
|
||||||
|
- **Frontend Build**: Vite for asset bundling
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
```
|
||||||
|
Controllers/ # MVC controllers (Home, QR, Account, Premium, Pagamento, Health)
|
||||||
|
Services/ # Business logic (QRRapido, User, Plan, Stripe, AdDisplay)
|
||||||
|
├── Monitoring/ # Resource and MongoDB monitoring
|
||||||
|
└── HealthChecks/# Health check implementations
|
||||||
|
Models/ # Domain models and ViewModels
|
||||||
|
Data/ # MongoDbContext
|
||||||
|
Middleware/ # Custom middleware (Language redirection, LastLogin update)
|
||||||
|
Providers/ # Culture providers for localization
|
||||||
|
Resources/ # .resx files for PT-BR, ES, EN localization
|
||||||
|
Views/ # Razor views
|
||||||
|
wwwroot/ # Static assets (CSS, JS, images)
|
||||||
|
Tests/ # Unit tests (xUnit, Moq)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Services
|
||||||
|
|
||||||
|
**QRRapidoService** (`Services/QRRapidoService.cs`):
|
||||||
|
- Core QR generation with distributed cache support
|
||||||
|
- SemaphoreSlim limits concurrent generations (default: 100)
|
||||||
|
- Cache key based on content hash + settings
|
||||||
|
- Optimized error correction levels for speed
|
||||||
|
- Logo overlay with readability analysis
|
||||||
|
|
||||||
|
**UserService** (`Services/UserService.cs`):
|
||||||
|
- MongoDB user CRUD operations
|
||||||
|
- QR history management (anonymous vs authenticated)
|
||||||
|
- Premium status checks
|
||||||
|
- Graceful fallback when MongoDB unavailable
|
||||||
|
|
||||||
|
**AdDisplayService** (`Services/AdDisplayService.cs`):
|
||||||
|
- Controls ad visibility based on user status
|
||||||
|
- 30-day ad-free period after login
|
||||||
|
- Premium users: permanent ad-free
|
||||||
|
- Anonymous users: always show ads
|
||||||
|
|
||||||
|
**StripeService** (`Services/StripeService.cs`):
|
||||||
|
- Subscription creation and management
|
||||||
|
- Webhook handling (checkout.session.completed, etc.)
|
||||||
|
- Customer portal session creation
|
||||||
|
|
||||||
|
### Middleware Pipeline (Program.cs)
|
||||||
|
|
||||||
|
1. **LanguageRedirectionMiddleware**: Redirects root `/` to `/pt-BR/` or user's language preference
|
||||||
|
2. **Request Localization**: Sets culture based on route (`/{culture}/...`) → QueryString → Cookie
|
||||||
|
3. **Authentication/Authorization**
|
||||||
|
4. **Session**
|
||||||
|
5. **LastLoginUpdateMiddleware**: Updates user's last login timestamp
|
||||||
|
|
||||||
|
### Localization Strategy
|
||||||
|
|
||||||
|
- Route-based culture: `/{culture:regex(^(pt-BR|es-PY)$)}/{controller}/{action}`
|
||||||
|
- Default culture: `pt-BR`
|
||||||
|
- Supported cultures: `pt-BR`, `es-PY`
|
||||||
|
- Culture providers priority: Route → QueryString → Cookie
|
||||||
|
- Resources in `Resources/SharedResource.{culture}.resx`
|
||||||
|
|
||||||
|
### MongoDB Collections
|
||||||
|
- **Users**: User profiles, premium status, OAuth data
|
||||||
|
- **QRCodeHistory**: Generated QR codes (linked to users or anonymous)
|
||||||
|
- **AdFreeSessions**: Ad-free session tracking (30-day grants)
|
||||||
|
- **DataProtectionKeys**: ASP.NET Core Data Protection keys (for Docker Swarm)
|
||||||
|
|
||||||
|
### Docker Swarm Deployment
|
||||||
|
|
||||||
|
**Production** uses Docker Swarm with:
|
||||||
|
- 2 replicas across 2 ARM64 servers (OCI Ampere)
|
||||||
|
- Shared MongoDB for Data Protection keys (cross-replica cookie decryption)
|
||||||
|
- Rolling updates: `start-first` strategy, 30s delay between updates
|
||||||
|
- Health checks at `/healthcheck` endpoint
|
||||||
|
- Exposed on port 5001 internally, proxied by Nginx
|
||||||
|
|
||||||
|
**Staging** uses standalone Docker containers on 2 servers.
|
||||||
|
|
||||||
|
### CI/CD Pipeline (.github/workflows/deploy.yml)
|
||||||
|
|
||||||
|
1. **Test Job**: Runs on `ubuntu-latest`
|
||||||
|
- Restore → Build → Test with coverage
|
||||||
|
- Uploads coverage to Codecov
|
||||||
|
|
||||||
|
2. **Build-and-Push Job**: Runs on self-hosted ARM64 runner
|
||||||
|
- Builds ARM64 Docker image
|
||||||
|
- Tags: `latest` (main), `develop` (develop branch)
|
||||||
|
- Pushes to private registry: `registry.redecarneir.us`
|
||||||
|
|
||||||
|
3. **Deploy-Staging**: SSH to 2 servers, pull image, run container
|
||||||
|
|
||||||
|
4. **Deploy-Production**: SSH to Swarm manager, update service with zero-downtime
|
||||||
|
|
||||||
|
### Configuration Management
|
||||||
|
|
||||||
|
**appsettings.json** key sections:
|
||||||
|
- `ConnectionStrings:MongoDB`: MongoDB connection string
|
||||||
|
- `Authentication:Google/Microsoft`: OAuth credentials
|
||||||
|
- `Stripe`: API keys and webhook secret
|
||||||
|
- `Performance:MaxConcurrentGenerations`: Semaphore limit (default: 100)
|
||||||
|
- `Premium:FreeQRLimit`: Daily limit for logged-in free users (10)
|
||||||
|
- `Serilog:OpenSearchUrl`: Centralized logging endpoint
|
||||||
|
- `ResourceMonitoring`: CPU/memory thresholds for alerts
|
||||||
|
- `HealthChecks`: Timeout and test configurations
|
||||||
|
|
||||||
|
**Environment-specific overrides**:
|
||||||
|
- `appsettings.Development.json`
|
||||||
|
- `appsettings.Production.json`
|
||||||
|
|
||||||
|
### Rate Limiting & Performance
|
||||||
|
|
||||||
|
- Fixed window rate limiter: 600 requests/minute per IP on `/api` endpoints
|
||||||
|
- Kestrel max connections: 2000
|
||||||
|
- QR generation timeout: 2000ms
|
||||||
|
- Redis cache expiration: 60 minutes
|
||||||
|
- MongoDB query timeouts: 5 seconds (health checks)
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
Endpoint: `/healthcheck`
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
- **MongoDbHealthCheck**: Database connectivity, size metrics, test query
|
||||||
|
- **ResourceHealthCheck**: CPU/memory usage, GC pressure
|
||||||
|
- **ExternalServicesHealthCheck**: Stripe API availability
|
||||||
|
|
||||||
|
### Testing Strategy
|
||||||
|
|
||||||
|
- **Unit tests**: Services layer (QRRapidoService, AdDisplayService, etc.)
|
||||||
|
- **Mocking**: MongoDB and IDistributedCache with Moq
|
||||||
|
- **Coverage**: Run `dotnet test --collect:"XPlat Code Coverage"`
|
||||||
|
- Test files in `Tests/Services/`
|
||||||
|
|
||||||
|
### Known Quirks & WSL Compatibility
|
||||||
|
|
||||||
|
- **StaticWebAssets disabled**: Set `ASPNETCORE_HOSTINGSTARTUP__STATICWEBASSETS__ENABLED=false` for WSL path issues (see Program.cs:38-39)
|
||||||
|
- **DataProtection**: Uses MongoDB for key persistence in production (Docker Swarm), filesystem in development
|
||||||
|
- **Frontend build**: Vite runs during Release build via MSBuild target (`BuildFrontend` in .csproj)
|
||||||
|
- **Stripe local testing**: Use `stripe listen` in WSL (see README.md:345-347)
|
||||||
|
|
||||||
|
### Monitoring & Logging
|
||||||
|
|
||||||
|
- **Serilog** → Console (development) + OpenSearch (production)
|
||||||
|
- **ResourceMonitoringService**: Background service tracking CPU/memory every 30s
|
||||||
|
- **HistoryCleanupService**: Cleans old anonymous QR history (7-day grace period, runs every 6 hours)
|
||||||
|
- **MongoDbMonitoringService**: Tracks database growth and collection stats (disabled by default)
|
||||||
|
|
||||||
|
### Premium Feature Gates
|
||||||
|
|
||||||
|
Check user premium status via `IUserService.GetUserAsync(userId)` → `user.IsPremium`
|
||||||
|
|
||||||
|
Premium benefits:
|
||||||
|
- Unlimited QR codes (vs 10/day anonymous, 50/day free)
|
||||||
|
- No ads permanently
|
||||||
|
- Priority generation (faster SemaphoreSlim release)
|
||||||
|
- Dynamic QR codes (editable)
|
||||||
|
- API access
|
||||||
|
|
||||||
|
### Ad-Free Logic
|
||||||
|
|
||||||
|
See `AdDisplayService.ShouldShowAdsAsync()`:
|
||||||
|
- Anonymous users: always show ads
|
||||||
|
- Premium users: never show ads
|
||||||
|
- Free logged-in users: 30-day ad-free period from login (configurable in appsettings)
|
||||||
|
- Ad-free sessions tracked in MongoDB `AdFreeSessions` collection
|
||||||
|
|
||||||
|
### Security Considerations
|
||||||
|
|
||||||
|
- OAuth secure flow with PKCE
|
||||||
|
- HTTPS redirect enforced (non-dev environments)
|
||||||
|
- Stripe webhook signature verification
|
||||||
|
- Input sanitization on QR generation
|
||||||
|
- Rate limiting on API endpoints
|
||||||
|
- HSTS enabled in production
|
||||||
|
- Forwarded headers support for reverse proxy (Nginx)
|
||||||
|
|
||||||
|
### Common Workflows
|
||||||
|
|
||||||
|
**Adding a new QR type:**
|
||||||
|
1. Update `Models/ViewModels/QRGenerationRequest.cs` with new type enum
|
||||||
|
2. Add generation logic in `Services/QRRapidoService.cs` → `GenerateQRCodeOptimizedAsync()`
|
||||||
|
3. Update frontend form in `Views/Home/Index.cshtml`
|
||||||
|
4. Add localized strings in `Resources/SharedResource.{culture}.resx`
|
||||||
|
|
||||||
|
**Adding a new language:**
|
||||||
|
1. Create `Resources/SharedResource.{culture}.resx`
|
||||||
|
2. Update `Program.cs` supported cultures array (line 214-218)
|
||||||
|
3. Update route constraint regex (line 354)
|
||||||
|
4. Add culture provider mapping if needed
|
||||||
|
|
||||||
|
**Debugging slow QR generation:**
|
||||||
|
1. Check `_logger` output in `QRRapidoService.GenerateRapidAsync()` for timing
|
||||||
|
2. Verify cache hit rate in logs
|
||||||
|
3. Check semaphore wait time (max concurrent limit)
|
||||||
|
4. Review `Performance:QRGenerationTimeoutMs` in appsettings
|
||||||
|
5. Monitor resource usage via `/healthcheck`
|
||||||
|
|
||||||
|
**Updating Stripe configuration:**
|
||||||
|
1. Update `appsettings.json` → `Stripe` section
|
||||||
|
2. Verify webhook secret matches Stripe dashboard
|
||||||
|
3. Test locally with `stripe listen --forward-to ...`
|
||||||
|
4. Update `StripeService.cs` webhook handlers if event types change
|
||||||
108
Content/Tutoriais/como-crear-codigo-qr-whatsapp.es-PY.md
Normal file
108
Content/Tutoriais/como-crear-codigo-qr-whatsapp.es-PY.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
title: "Cómo Crear Código QR para WhatsApp"
|
||||||
|
description: "Aprende a crear un código QR para WhatsApp que permite a los usuarios iniciar una conversación contigo instantáneamente"
|
||||||
|
keywords: "codigo qr whatsapp, whatsapp codigo qr, qr para whatsapp, crear qr whatsapp"
|
||||||
|
author: "QR Rapido"
|
||||||
|
date: 2025-10-08
|
||||||
|
lastmod: 2025-10-08
|
||||||
|
image: "/images/tutoriais/whatsapp-qr-hero.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cómo Crear Código QR para WhatsApp
|
||||||
|
|
||||||
|
Crear un **código QR para WhatsApp** es una de las formas más eficientes de facilitar el contacto directo con tus clientes, amigos o seguidores. Con un simple escaneo, cualquier persona puede iniciar una conversación contigo instantáneamente, sin necesidad de guardar tu número.
|
||||||
|
|
||||||
|
## 📱 ¿Por qué usar código QR para WhatsApp?
|
||||||
|
|
||||||
|
Los códigos QR para WhatsApp son extremadamente útiles para:
|
||||||
|
|
||||||
|
- **Empresas**: Facilitar la atención al cliente
|
||||||
|
- **Freelancers**: Agilizar el contacto con potenciales clientes
|
||||||
|
- **Eventos**: Permitir networking rápido
|
||||||
|
- **Marketing**: Aumentar la conversión en campañas
|
||||||
|
|
||||||
|
## 🎯 Paso a Paso
|
||||||
|
|
||||||
|
### 1. Prepara tu número
|
||||||
|
|
||||||
|
Primero, necesitas tener tu número en formato internacional:
|
||||||
|
|
||||||
|
```
|
||||||
|
595 21 123-4567
|
||||||
|
```
|
||||||
|
|
||||||
|
Elimina todos los espacios y guiones, quedando:
|
||||||
|
|
||||||
|
```
|
||||||
|
59521123456
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Crea la URL de WhatsApp
|
||||||
|
|
||||||
|
La URL de WhatsApp sigue este patrón:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://wa.me/59521123456
|
||||||
|
```
|
||||||
|
|
||||||
|
Puedes agregar un mensaje predefinido:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://wa.me/59521123456?text=¡Hola!%20Quisiera%20más%20información
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Genera el código QR
|
||||||
|
|
||||||
|
Accede a [QR Rapido](https://qrrapido.site) y:
|
||||||
|
|
||||||
|
1. Selecciona el tipo **URL**
|
||||||
|
2. Pega la URL de WhatsApp
|
||||||
|
3. Personaliza los colores (opcional)
|
||||||
|
4. Haz clic en **Generar Código QR**
|
||||||
|
5. Descarga en alta calidad
|
||||||
|
|
||||||
|
## 💡 Consejos Profesionales
|
||||||
|
|
||||||
|
### Personaliza el Mensaje Inicial
|
||||||
|
|
||||||
|
Configura un mensaje de bienvenida automático para mejorar la experiencia:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://wa.me/59521123456?text=¡Hola!%20Vine%20a%20través%20de%20tu%20código%20QR
|
||||||
|
```
|
||||||
|
|
||||||
|
### Úsalo en Materiales Impresos
|
||||||
|
|
||||||
|
- **Tarjetas de visita**: Facilita el contacto instantáneo
|
||||||
|
- **Folletos**: Aumenta el engagement
|
||||||
|
- **Embalajes**: Ofrece soporte directo al cliente
|
||||||
|
- **Banners**: En eventos y ferias
|
||||||
|
|
||||||
|
### Monitorea los Resultados
|
||||||
|
|
||||||
|
Para rastrear cuántas personas escanearon tu código QR, considera usar un acortador de URL con analytics antes de generar el QR.
|
||||||
|
|
||||||
|
## ⚠️ Cuidados Importantes
|
||||||
|
|
||||||
|
1. **Prueba antes de imprimir**: Siempre escanea para verificar que funciona
|
||||||
|
2. **Tamaño mínimo**: Mantén al menos 3x3 cm para fácil lectura
|
||||||
|
3. **Contraste adecuado**: Usa colores que contrasten bien (negro sobre blanco es ideal)
|
||||||
|
4. **Mensaje claro**: Indica para qué sirve el código QR
|
||||||
|
|
||||||
|
## 🚀 Ventajas de usar QR Rapido
|
||||||
|
|
||||||
|
- ⚡ **Ultra rápido**: Genera en menos de 1 segundo
|
||||||
|
- 🎨 **Personalizable**: Elige colores y estilos
|
||||||
|
- 📥 **Alta calidad**: Descarga en PNG, SVG y PDF
|
||||||
|
- 🔒 **Seguro**: Tus datos no son almacenados
|
||||||
|
- 💯 **Gratis**: 10 códigos QR por día
|
||||||
|
|
||||||
|
## Conclusión
|
||||||
|
|
||||||
|
Crear un código QR para WhatsApp es simple y puede revolucionar la forma en que te comunicas con tu público. Con QR Rapido, tienes todo lo que necesitas para crear códigos QR profesionales en segundos.
|
||||||
|
|
||||||
|
**¿Listo para empezar?** [Crea tu código QR ahora →](https://qrrapido.site/es-PY)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*¿Tienes dudas? [Contáctanos](https://qrrapido.site/es-PY/Contact)!*
|
||||||
271
Content/Tutoriais/como-crear-codigo-qr-wifi.es-PY.md
Normal file
271
Content/Tutoriais/como-crear-codigo-qr-wifi.es-PY.md
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
---
|
||||||
|
title: "Cómo Crear Código QR WiFi Gratis: Comparte tu Red en Segundos"
|
||||||
|
description: "Aprende a crear código QR WiFi gratuito en pocos clics. Comparte la contraseña de tu red sin escribir con nuestro generador rápido y seguro."
|
||||||
|
keywords: "codigo qr wifi, crear codigo qr wifi, generador qr wifi gratis, qr wifi, compartir contraseña wifi, codigo qr red wifi, como hacer qr wifi"
|
||||||
|
author: "QR Rapido"
|
||||||
|
date: 2025-10-10
|
||||||
|
lastmod: 2025-10-10
|
||||||
|
image: "/images/tutoriais/qr-code-wifi-hero.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cómo Crear Código QR WiFi Gratis: Comparte tu Red en Segundos
|
||||||
|
|
||||||
|
¿Cansado de escribir contraseñas largas y complicadas cada vez que un visitante pide el WiFi? Con un **Código QR WiFi**, puedes compartir tu red instantáneamente. En este tutorial completo, aprenderás a crear un código QR para WiFi gratuitamente en menos de 2 minutos.
|
||||||
|
|
||||||
|
## ¿Por Qué Usar Código QR para WiFi?
|
||||||
|
|
||||||
|
Compartir tu red WiFi mediante código QR ofrece varias ventajas:
|
||||||
|
|
||||||
|
- **Practicidad**: Los visitantes se conectan instantáneamente sin escribir contraseñas
|
||||||
|
- **Seguridad**: No necesitas decir la contraseña en voz alta o anotarla
|
||||||
|
- **Profesionalismo**: Ideal para empresas, cafeterías, restaurantes y consultorios
|
||||||
|
- **Ahorro de tiempo**: Elimina errores de escritura y pedidos repetidos
|
||||||
|
- **Compatibilidad**: Funciona en prácticamente todos los smartphones modernos
|
||||||
|
|
||||||
|
## Paso a Paso: Cómo Crear tu Código QR WiFi
|
||||||
|
|
||||||
|
### Paso 1: Selecciona el Tipo de Código QR
|
||||||
|
|
||||||
|
Accede al generador y elige la opción **WiFi** en la lista de tipos de códigos QR disponibles.
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 1 AQUÍ: Pantalla de selección de tipo de código QR con WiFi destacado]**
|
||||||
|
|
||||||
|
En el menú desplegable encontrarás varias opciones como URL/Link, Texto Simple, Tarjeta de Visita, SMS y Email. Para este tutorial, selecciona **WiFi**.
|
||||||
|
|
||||||
|
### Paso 2: Completa los Datos de tu Red WiFi
|
||||||
|
|
||||||
|
Ahora necesitas informar los datos de tu red. Ve los campos obligatorios:
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 3 AQUÍ: Formulario de creación de código QR WiFi con campos completados]**
|
||||||
|
|
||||||
|
#### NetworkName (Nombre de la Red) *
|
||||||
|
Escribe exactamente el nombre de tu red WiFi (SSID). Por ejemplo: "NombreDeTuRed"
|
||||||
|
|
||||||
|
**Consejo importante**: El nombre debe ser idéntico al que aparece cuando buscas redes WiFi en el celular. ¡Respeta mayúsculas y minúsculas!
|
||||||
|
|
||||||
|
#### SecurityType (Tipo de Seguridad)
|
||||||
|
Selecciona el tipo de encriptación de tu red:
|
||||||
|
|
||||||
|
- **Red WPA (la más común)**: WPA/WPA2/WPA3 - recomendado y más seguro
|
||||||
|
- **WEP (muy antiguo)**: No recomendado por ser inseguro
|
||||||
|
- **Sin contraseña**: Para redes públicas sin protección
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 4 AQUÍ: Ejemplo de formulario WiFi completado con "Rede-do-meu-comercio"]**
|
||||||
|
|
||||||
|
#### NetworkPassword (Contraseña de la Red) *
|
||||||
|
Introduce la contraseña de tu red WiFi. Usa el ícono de ojo para visualizar y verificar que escribiste correctamente.
|
||||||
|
|
||||||
|
**Importante**: La contraseña también distingue mayúsculas de minúsculas. ¡Verifica con atención!
|
||||||
|
|
||||||
|
#### HiddenNetwork (Red Oculta)
|
||||||
|
Marca esta opción solo si tu red está configurada como oculta (no aparece en la lista de redes disponibles).
|
||||||
|
|
||||||
|
### Paso 3: Personaliza tu Código QR (Opcional)
|
||||||
|
|
||||||
|
¡Dale a tu código QR la identidad de tu negocio!
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 2 AQUÍ: Panel de personalización avanzada]**
|
||||||
|
|
||||||
|
#### Opciones de Personalización:
|
||||||
|
|
||||||
|
**Color Principal**: Elige el color de los cuadrados del código QR (predeterminado: azul)
|
||||||
|
|
||||||
|
**Color de Fondo**: Define el color de fondo (predeterminado: blanco)
|
||||||
|
|
||||||
|
**Tamaño**: Selecciona entre:
|
||||||
|
- Pequeño (200px) - Para uso digital
|
||||||
|
- Mediano (300px) - Recomendado para impresión
|
||||||
|
- Grande (500px) - Para banners y pósters
|
||||||
|
|
||||||
|
**Margen**:
|
||||||
|
- Compacto - Ocupa menos espacio
|
||||||
|
- Normal - Recomendado (mejor lectura)
|
||||||
|
- Amplio - Para impresiones grandes
|
||||||
|
|
||||||
|
**Consejo de diseño**: Mantén buen contraste entre el color principal y el fondo para garantizar que todos los celulares puedan leer el código.
|
||||||
|
|
||||||
|
### Paso 4: Generar y Descargar
|
||||||
|
|
||||||
|
Haz clic en el botón azul **"⚡ Generar Código QR Rápidamente"** y ¡listo! Tu código QR WiFi será generado instantáneamente.
|
||||||
|
|
||||||
|
Puedes:
|
||||||
|
- Descargar la imagen en alta calidad
|
||||||
|
- Imprimir y colocar en lugares visibles
|
||||||
|
- Compartir digitalmente
|
||||||
|
- Guardar para usar después
|
||||||
|
|
||||||
|
## Cómo tus Visitantes Usarán el Código QR WiFi
|
||||||
|
|
||||||
|
¡Es muy simple! Tus visitantes solo necesitan:
|
||||||
|
|
||||||
|
1. Abrir la cámara del celular (iOS o Android)
|
||||||
|
2. Apuntar al código QR
|
||||||
|
3. Tocar la notificación que aparece
|
||||||
|
4. Conectarse automáticamente al WiFi
|
||||||
|
|
||||||
|
**¡No necesitas descargar aplicaciones!** La mayoría de los smartphones desde 2018 ya tienen lectores de códigos QR integrados en la cámara.
|
||||||
|
|
||||||
|
## Dónde Usar tu Código QR WiFi
|
||||||
|
|
||||||
|
### Para Empresas
|
||||||
|
- Recepción de oficinas
|
||||||
|
- Salas de reuniones
|
||||||
|
- Áreas de espera
|
||||||
|
- Espacios de coworking
|
||||||
|
|
||||||
|
### Para Comercios
|
||||||
|
- Mesas de restaurantes
|
||||||
|
- Mostradores de cafeterías
|
||||||
|
- Tiendas y boutiques
|
||||||
|
- Salones de belleza
|
||||||
|
|
||||||
|
### Para Residencias
|
||||||
|
- Cuadro de entrada
|
||||||
|
- Área de parrilla
|
||||||
|
- Heladera (para fiestas)
|
||||||
|
- Home office
|
||||||
|
|
||||||
|
### Para Eventos
|
||||||
|
- Credenciales de eventos
|
||||||
|
- Stands de ferias
|
||||||
|
- Conferencias
|
||||||
|
- Bodas y fiestas
|
||||||
|
|
||||||
|
## Consejos de Seguridad
|
||||||
|
|
||||||
|
⚠️ **Importante**: Considera crear una red WiFi separada para visitantes (red guest) si deseas:
|
||||||
|
|
||||||
|
- Proteger tus dispositivos personales
|
||||||
|
- Limitar velocidad para invitados
|
||||||
|
- Tener control sobre quién accede
|
||||||
|
- Mantener tu red principal privada
|
||||||
|
|
||||||
|
Muchos routers modernos permiten crear redes guest fácilmente en las configuraciones.
|
||||||
|
|
||||||
|
## Preguntas Frecuentes (FAQ)
|
||||||
|
|
||||||
|
### ¿El Código QR WiFi expira?
|
||||||
|
¡No! El código QR funciona indefinidamente mientras los datos de la red (nombre y contraseña) permanezcan iguales.
|
||||||
|
|
||||||
|
### ¿Funciona en iPhone y Android?
|
||||||
|
¡Sí! Funciona en prácticamente todos los smartphones fabricados después de 2018 que tienen cámara.
|
||||||
|
|
||||||
|
### ¿Puedo crear para red 5GHz?
|
||||||
|
¡Sí! El proceso es exactamente el mismo. Solo usa el nombre correcto de la red 5GHz.
|
||||||
|
|
||||||
|
### ¿Es seguro?
|
||||||
|
¡Sí! El código QR solo facilita la escritura de los datos. Es tan seguro como informar la contraseña verbalmente o por escrito.
|
||||||
|
|
||||||
|
### ¿Puedo editar después de creado?
|
||||||
|
No es posible editar el código QR después de generado. Si cambias la contraseña del WiFi, necesitarás generar un nuevo código QR.
|
||||||
|
|
||||||
|
### ¿Cuántas personas pueden usar el mismo código QR?
|
||||||
|
¡Ilimitadas! No hay límite de usos para el código QR.
|
||||||
|
|
||||||
|
## Casos de Uso Reales
|
||||||
|
|
||||||
|
### Restaurantes y Cafeterías
|
||||||
|
En Paraguay, muchos negocios gastronómicos están adoptando códigos QR WiFi. Los clientes aprecian poder conectarse sin interrumpir al personal. Coloca el código QR en:
|
||||||
|
- Carteles en las mesas
|
||||||
|
- Menús impresos
|
||||||
|
- Pared cerca de la caja
|
||||||
|
|
||||||
|
### Consultorios Médicos
|
||||||
|
Los pacientes en la sala de espera pueden conectarse fácilmente mientras aguardan. Esto mejora la experiencia y reduce el estrés de la espera.
|
||||||
|
|
||||||
|
### Oficinas y Coworking
|
||||||
|
Facilita el acceso de clientes, proveedores y visitantes sin comprometer la seguridad de tu red principal. Ideal para espacios colaborativos en Asunción y otras ciudades.
|
||||||
|
|
||||||
|
### Hoteles y Hospedajes
|
||||||
|
Proporciona códigos QR en las habitaciones para que los huéspedes se conecten inmediatamente al llegar.
|
||||||
|
|
||||||
|
## Errores Comunes a Evitar
|
||||||
|
|
||||||
|
### ❌ Error 1: Nombre de Red Incorrecto
|
||||||
|
Verifica que escribiste exactamente el SSID de tu red. Un error común es confundir la red 2.4GHz con la 5GHz.
|
||||||
|
|
||||||
|
### ❌ Error 2: Contraseña con Espacios
|
||||||
|
Si tu contraseña tiene espacios, inclúyelos exactamente como están configurados en el router.
|
||||||
|
|
||||||
|
### ❌ Error 3: Tipo de Seguridad Incorrecto
|
||||||
|
Asegúrate de seleccionar el tipo correcto (WPA, WEP o sin contraseña). Si no estás seguro, verifica en la configuración de tu router.
|
||||||
|
|
||||||
|
### ❌ Error 4: QR Code Muy Pequeño
|
||||||
|
Para impresión, usa tamaño mediano (300px) o grande (500px). Los códigos pequeños pueden ser difíciles de escanear.
|
||||||
|
|
||||||
|
### ❌ Error 5: Bajo Contraste
|
||||||
|
Evita combinaciones de colores como amarillo sobre blanco o gris claro sobre gris. El contraste es esencial para la lectura correcta.
|
||||||
|
|
||||||
|
## Mejores Prácticas para Imprimir
|
||||||
|
|
||||||
|
Si vas a imprimir tu código QR WiFi, sigue estas recomendaciones:
|
||||||
|
|
||||||
|
### Material Recomendado
|
||||||
|
- **Papel fotográfico**: Para mejor calidad y durabilidad
|
||||||
|
- **Laminado**: Protege contra humedad y suciedad
|
||||||
|
- **Acrílico**: Solución premium para negocios
|
||||||
|
- **Vinilo adhesivo**: Fácil de colocar en paredes y superficies
|
||||||
|
|
||||||
|
### Tamaño de Impresión
|
||||||
|
- **Mínimo**: 5x5 cm para lectura cercana
|
||||||
|
- **Recomendado**: 10x10 cm para lectura a 30-50 cm de distancia
|
||||||
|
- **Grande**: 15x15 cm o más para lectura a mayor distancia
|
||||||
|
|
||||||
|
### Ubicación Estratégica
|
||||||
|
- A la altura de los ojos (1.40m - 1.60m)
|
||||||
|
- Bien iluminado
|
||||||
|
- Sin reflejos o brillos
|
||||||
|
- Fácilmente visible desde donde la gente se sienta o espera
|
||||||
|
|
||||||
|
## Tips para Negocios
|
||||||
|
|
||||||
|
### Agrega un Texto Atractivo
|
||||||
|
No solo coloques el código QR. Agrega texto como:
|
||||||
|
- "WiFi Gratis - Escanea y Conecta"
|
||||||
|
- "Internet Rápido - Solo Escanea"
|
||||||
|
- "Conectate Fácil con un Click"
|
||||||
|
|
||||||
|
### Diseño Personalizado
|
||||||
|
Usa los colores de tu marca en el código QR para mantener la coherencia visual con tu negocio.
|
||||||
|
|
||||||
|
### Múltiples Ubicaciones
|
||||||
|
En locales grandes, coloca varios códigos QR en diferentes puntos para facilitar el acceso.
|
||||||
|
|
||||||
|
### Actualización Periódica
|
||||||
|
Por seguridad, considera cambiar la contraseña WiFi cada 3-6 meses y generar un nuevo código QR.
|
||||||
|
|
||||||
|
## Conclusión
|
||||||
|
|
||||||
|
Crear un código QR WiFi es rápido, fácil y completamente gratuito en QR Rapido. En menos de 2 minutos puedes:
|
||||||
|
|
||||||
|
✅ Generar tu código QR personalizado
|
||||||
|
✅ Compartir tu red sin esfuerzo
|
||||||
|
✅ Proporcionar mejor experiencia a los visitantes
|
||||||
|
✅ Demostrar profesionalismo
|
||||||
|
|
||||||
|
**Prueba ahora mismo**: [Crear mi Código QR WiFi Gratis](/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**¿Te gustó este tutorial?** ¡Comparte con amigos que también quieren facilitar el acceso a la red WiFi! Explora también nuestros otros tipos de códigos QR para diferentes necesidades.
|
||||||
|
|
||||||
|
## Otros Tutoriales Útiles
|
||||||
|
|
||||||
|
- Cómo crear código QR para WhatsApp
|
||||||
|
- Código QR para Tarjeta de Visita Digital
|
||||||
|
- Cómo crear código QR para URLs y Links
|
||||||
|
- Personalización avanzada de códigos QR
|
||||||
|
|
||||||
|
**¡Crea ahora tu código QR WiFi gratuito y transforma la experiencia de tus visitantes!** 🚀📱
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Soporte Técnico
|
||||||
|
|
||||||
|
¿Tienes problemas para crear tu código QR WiFi? Contáctanos:
|
||||||
|
|
||||||
|
- **Email**: soporte@qrrapido.site
|
||||||
|
- **WhatsApp**: [Agregar número]
|
||||||
|
- **Horario**: Lunes a Viernes, 8:00 - 18:00 (hora de Paraguay)
|
||||||
|
|
||||||
|
¡Estamos aquí para ayudarte a crear el código QR perfecto para tu negocio o hogar!
|
||||||
108
Content/Tutoriais/como-criar-qr-code-whatsapp.pt-BR.md
Normal file
108
Content/Tutoriais/como-criar-qr-code-whatsapp.pt-BR.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
title: "Como Criar QR Code para WhatsApp"
|
||||||
|
description: "Aprenda a criar um QR Code para WhatsApp que permite aos usuários iniciarem uma conversa com você instantaneamente"
|
||||||
|
keywords: "qr code whatsapp, whatsapp qr code, qr code para whatsapp, criar qr whatsapp"
|
||||||
|
author: "QR Rapido"
|
||||||
|
date: 2025-10-08
|
||||||
|
lastmod: 2025-10-08
|
||||||
|
image: "/images/tutoriais/whatsapp-qr-hero.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Como Criar QR Code para WhatsApp
|
||||||
|
|
||||||
|
Criar um **QR Code para WhatsApp** é uma das maneiras mais eficientes de facilitar o contato direto com seus clientes, amigos ou seguidores. Com um simples escaneamento, qualquer pessoa pode iniciar uma conversa com você instantaneamente, sem precisar salvar seu número.
|
||||||
|
|
||||||
|
## 📱 Por que usar QR Code para WhatsApp?
|
||||||
|
|
||||||
|
Os QR Codes para WhatsApp são extremamente úteis para:
|
||||||
|
|
||||||
|
- **Empresas**: Facilitar o atendimento ao cliente
|
||||||
|
- **Freelancers**: Agilizar o contato com potenciais clientes
|
||||||
|
- **Eventos**: Permitir networking rápido
|
||||||
|
- **Marketing**: Aumentar a conversão em campanhas
|
||||||
|
|
||||||
|
## 🎯 Passo a Passo
|
||||||
|
|
||||||
|
### 1. Prepare seu número
|
||||||
|
|
||||||
|
Primeiro, você precisa ter seu número no formato internacional:
|
||||||
|
|
||||||
|
```
|
||||||
|
55 11 98765-4321
|
||||||
|
```
|
||||||
|
|
||||||
|
Remova todos os espaços e traços, ficando:
|
||||||
|
|
||||||
|
```
|
||||||
|
5511987654321
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Crie a URL do WhatsApp
|
||||||
|
|
||||||
|
A URL do WhatsApp segue este padrão:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://wa.me/5511987654321
|
||||||
|
```
|
||||||
|
|
||||||
|
Você pode adicionar uma mensagem pré-definida:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://wa.me/5511987654321?text=Olá!%20Gostaria%20de%20mais%20informações
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Gere o QR Code
|
||||||
|
|
||||||
|
Acesse [QR Rapido](https://qrrapido.site) e:
|
||||||
|
|
||||||
|
1. Selecione o tipo **URL**
|
||||||
|
2. Cole a URL do WhatsApp
|
||||||
|
3. Personalize as cores (opcional)
|
||||||
|
4. Clique em **Gerar QR Code**
|
||||||
|
5. Faça o download em alta qualidade
|
||||||
|
|
||||||
|
## 💡 Dicas Profissionais
|
||||||
|
|
||||||
|
### Personalize a Mensagem Inicial
|
||||||
|
|
||||||
|
Configure uma mensagem de boas-vindas automática para melhorar a experiência:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://wa.me/5511987654321?text=Olá!%20Vim%20através%20do%20seu%20QR%20Code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use em Materiais Impressos
|
||||||
|
|
||||||
|
- **Cartões de visita**: Facilite o contato instantâneo
|
||||||
|
- **Flyers e panfletos**: Aumente o engajamento
|
||||||
|
- **Embalagens**: Ofereça suporte direto ao cliente
|
||||||
|
- **Banners**: Em eventos e feiras
|
||||||
|
|
||||||
|
### Monitore os Resultados
|
||||||
|
|
||||||
|
Para rastrear quantas pessoas escanearam seu QR Code, considere usar um encurtador de URL com analytics antes de gerar o QR Code.
|
||||||
|
|
||||||
|
## ⚠️ Cuidados Importantes
|
||||||
|
|
||||||
|
1. **Teste antes de imprimir**: Sempre escaneie para verificar se funciona
|
||||||
|
2. **Tamanho mínimo**: Mantenha pelo menos 3x3 cm para fácil leitura
|
||||||
|
3. **Contraste adequado**: Use cores que contrastem bem (preto no branco é ideal)
|
||||||
|
4. **Mensagem clara**: Indique para que serve o QR Code
|
||||||
|
|
||||||
|
## 🚀 Vantagens de usar QR Rapido
|
||||||
|
|
||||||
|
- ⚡ **Ultrarrápido**: Gere em menos de 1 segundo
|
||||||
|
- 🎨 **Personalizável**: Escolha cores e estilos
|
||||||
|
- 📥 **Alta qualidade**: Download em PNG, SVG e PDF
|
||||||
|
- 🔒 **Seguro**: Seus dados não são armazenados
|
||||||
|
- 💯 **Gratuito**: 10 QR codes por dia
|
||||||
|
|
||||||
|
## Conclusão
|
||||||
|
|
||||||
|
Criar um QR Code para WhatsApp é simples e pode revolucionar a forma como você se comunica com seu público. Com o QR Rapido, você tem tudo que precisa para criar QR Codes profissionais em segundos.
|
||||||
|
|
||||||
|
**Pronto para começar?** [Crie seu QR Code agora →](https://qrrapido.site/pt-BR)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Tem dúvidas? [Entre em contato](https://qrrapido.site/pt-BR/Contact) conosco!*
|
||||||
187
Content/Tutoriais/como-criar-qr-code-wifi.pt-BR.md
Normal file
187
Content/Tutoriais/como-criar-qr-code-wifi.pt-BR.md
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
title: "Como Criar QR Code WiFi Grátis: Compartilhe sua Rede em Segundos"
|
||||||
|
description: "Aprenda a criar QR Code WiFi gratuito em poucos cliques. Compartilhe a senha da sua rede sem digitar com nosso gerador rápido e seguro."
|
||||||
|
keywords: "qr code wifi, criar qr code wifi, gerador qr code wifi grátis, qr code rede wifi, compartilhar senha wifi, qr code wifi gratuito, como fazer qr code wifi"
|
||||||
|
author: "QR Rapido"
|
||||||
|
date: 2025-10-10
|
||||||
|
lastmod: 2025-10-10
|
||||||
|
image: "/images/tutoriais/qr-code-wifi-hero.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Como Criar QR Code WiFi Grátis: Compartilhe sua Rede em Segundos
|
||||||
|
|
||||||
|
Cansado de digitar senhas longas e complicadas toda vez que um visitante pede a senha do WiFi? Com um **QR Code WiFi**, você pode compartilhar sua rede instantaneamente! Neste tutorial completo, você aprenderá a criar um QR Code para WiFi gratuitamente em menos de 2 minutos.
|
||||||
|
|
||||||
|
## Por Que Usar QR Code para WiFi?
|
||||||
|
|
||||||
|
Compartilhar sua rede WiFi através de QR Code oferece diversas vantagens:
|
||||||
|
|
||||||
|
- **Praticidade**: Visitantes conectam-se instantaneamente sem digitar senhas
|
||||||
|
- **Segurança**: Não precisa falar a senha em voz alta ou anotá-la
|
||||||
|
- **Profissionalismo**: Ideal para empresas, cafeterias, restaurantes e consultórios
|
||||||
|
- **Economia de tempo**: Elimina erros de digitação e pedidos repetidos
|
||||||
|
- **Compatibilidade**: Funciona em praticamente todos os smartphones modernos
|
||||||
|
|
||||||
|
## Passo a Passo: Como Criar seu QR Code WiFi
|
||||||
|
|
||||||
|
### Passo 1: Selecione o Tipo de QR Code
|
||||||
|
|
||||||
|
Acesse o gerador e escolha a opção **WiFi** na lista de tipos de QR Code disponíveis.
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 1 AQUI: Tela de seleção de tipo de QR Code com WiFi destacado]**
|
||||||
|
|
||||||
|
No menu dropdown, você encontrará diversas opções como URL/Link, Texto Simples, Cartão de Visita, SMS e Email. Para este tutorial, selecione **WiFi**.
|
||||||
|
|
||||||
|
### Passo 2: Preencha os Dados da sua Rede WiFi
|
||||||
|
|
||||||
|
Agora você precisa informar os dados da sua rede. Veja os campos obrigatórios:
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 3 AQUI: Formulário de criação de QR Code WiFi com campos preenchidos]**
|
||||||
|
|
||||||
|
#### NetworkName (Nome da Rede) *
|
||||||
|
Digite exatamente o nome da sua rede WiFi (SSID). Por exemplo: "NomeDaSuaRede"
|
||||||
|
|
||||||
|
**Dica importante**: O nome deve ser idêntico ao que aparece quando você busca redes WiFi no celular. Respeite maiúsculas e minúsculas!
|
||||||
|
|
||||||
|
#### SecurityType (Tipo de Segurança)
|
||||||
|
Selecione o tipo de criptografia da sua rede:
|
||||||
|
|
||||||
|
- **Rede WPA (a mais comum)**: WPA/WPA2/WPA3 - recomendado e mais seguro
|
||||||
|
- **WEP (muito antigo)**: Não recomendado por ser inseguro
|
||||||
|
- **Sem senha**: Para redes públicas sem proteção
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 4 AQUI: Exemplo de formulário WiFi preenchido com "Rede-do-meu-comercio"]**
|
||||||
|
|
||||||
|
#### NetworkPassword (Senha da Rede) *
|
||||||
|
Insira a senha da sua rede WiFi. Use o ícone de olho para visualizar e conferir se digitou corretamente.
|
||||||
|
|
||||||
|
**Importante**: A senha também diferencia maiúsculas de minúsculas. Confira com atenção!
|
||||||
|
|
||||||
|
#### HiddenNetwork (Rede Oculta)
|
||||||
|
Marque esta opção apenas se sua rede está configurada como oculta (não aparece na lista de redes disponíveis).
|
||||||
|
|
||||||
|
### Passo 3: Personalize seu QR Code (Opcional)
|
||||||
|
|
||||||
|
Deixe seu QR Code com a cara do seu negócio!
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 2 AQUI: Painel de personalização avançada]**
|
||||||
|
|
||||||
|
#### Opções de Personalização:
|
||||||
|
|
||||||
|
**Cor Principal**: Escolha a cor dos quadrados do QR Code (padrão: azul)
|
||||||
|
|
||||||
|
**Cor de Fundo**: Defina a cor de fundo (padrão: branco)
|
||||||
|
|
||||||
|
**Tamanho**: Selecione entre:
|
||||||
|
- Pequeno (200px) - Para uso digital
|
||||||
|
- Médio (300px) - Recomendado para impressão
|
||||||
|
- Grande (500px) - Para banners e pôsteres
|
||||||
|
|
||||||
|
**Margem**:
|
||||||
|
- Compacta - Ocupa menos espaço
|
||||||
|
- Normal - Recomendado (melhor leitura)
|
||||||
|
- Larga - Para impressões grandes
|
||||||
|
|
||||||
|
**Dica de design**: Mantenha bom contraste entre a cor principal e o fundo para garantir que todos os celulares consigam ler o código.
|
||||||
|
|
||||||
|
### Passo 4: Gerar e Baixar
|
||||||
|
|
||||||
|
Clique no botão azul **"⚡ Gerar QR Code Rapidamente"** e pronto! Seu QR Code WiFi será gerado instantaneamente.
|
||||||
|
|
||||||
|
Você pode:
|
||||||
|
- Baixar a imagem em alta qualidade
|
||||||
|
- Imprimir e colocar em locais visíveis
|
||||||
|
- Compartilhar digitalmente
|
||||||
|
- Salvar para usar depois
|
||||||
|
|
||||||
|
## Como Seus Visitantes Usarão o QR Code WiFi
|
||||||
|
|
||||||
|
É muito simples! Seus visitantes só precisam:
|
||||||
|
|
||||||
|
1. Abrir a câmera do celular (iOS ou Android)
|
||||||
|
2. Apontar para o QR Code
|
||||||
|
3. Tocar na notificação que aparece
|
||||||
|
4. Conectar automaticamente ao WiFi
|
||||||
|
|
||||||
|
**Não precisa baixar aplicativos!** A maioria dos smartphones desde 2018 já possui leitores de QR Code integrados na câmera.
|
||||||
|
|
||||||
|
## Onde Usar seu QR Code WiFi
|
||||||
|
|
||||||
|
### Para Empresas
|
||||||
|
- Recepção de escritórios
|
||||||
|
- Salas de reunião
|
||||||
|
- Áreas de espera
|
||||||
|
- Coworking spaces
|
||||||
|
|
||||||
|
### Para Comércios
|
||||||
|
- Mesas de restaurantes
|
||||||
|
- Balcões de cafeterias
|
||||||
|
- Lojas e boutiques
|
||||||
|
- Salões de beleza
|
||||||
|
|
||||||
|
### Para Residências
|
||||||
|
- Quadro de entrada
|
||||||
|
- Área da churrasqueira
|
||||||
|
- Geladeira (para festas)
|
||||||
|
- Home office
|
||||||
|
|
||||||
|
### Para Eventos
|
||||||
|
- Credenciais de eventos
|
||||||
|
- Stands de feiras
|
||||||
|
- Conferências
|
||||||
|
- Casamentos e festas
|
||||||
|
|
||||||
|
## Dicas de Segurança
|
||||||
|
|
||||||
|
⚠️ **Importante**: Considere criar uma rede WiFi separada para visitantes (rede guest) se você deseja:
|
||||||
|
|
||||||
|
- Proteger seus dispositivos pessoais
|
||||||
|
- Limitar velocidade para convidados
|
||||||
|
- Ter controle sobre quem acessa
|
||||||
|
- Manter sua rede principal privada
|
||||||
|
|
||||||
|
Muitos roteadores modernos permitem criar redes guest facilmente nas configurações.
|
||||||
|
|
||||||
|
## Perguntas Frequentes (FAQ)
|
||||||
|
|
||||||
|
### O QR Code WiFi expira?
|
||||||
|
Não! O QR Code funciona indefinidamente enquanto os dados da rede (nome e senha) permanecerem os mesmos.
|
||||||
|
|
||||||
|
### Funciona em iPhone e Android?
|
||||||
|
Sim! Funciona em praticamente todos os smartphones fabricados após 2018 que possuem câmera.
|
||||||
|
|
||||||
|
### Posso criar para rede 5GHz?
|
||||||
|
Sim! O processo é exatamente o mesmo. Apenas use o nome correto da rede 5GHz.
|
||||||
|
|
||||||
|
### É seguro?
|
||||||
|
Sim! O QR Code apenas facilita a digitação dos dados. É tão seguro quanto informar a senha verbalmente ou por escrito.
|
||||||
|
|
||||||
|
### Posso editar depois de criado?
|
||||||
|
Não é possível editar o QR Code depois de gerado. Se mudar a senha do WiFi, precisará gerar um novo QR Code.
|
||||||
|
|
||||||
|
### Quantas pessoas podem usar o mesmo QR Code?
|
||||||
|
Ilimitadas! Não há limite de usos para o QR Code.
|
||||||
|
|
||||||
|
## Conclusão
|
||||||
|
|
||||||
|
Criar um QR Code WiFi é rápido, fácil e completamente gratuito no QR Rapido! Em menos de 2 minutos você pode:
|
||||||
|
|
||||||
|
✅ Gerar seu QR Code personalizado
|
||||||
|
✅ Compartilhar sua rede sem esforço
|
||||||
|
✅ Proporcionar melhor experiência aos visitantes
|
||||||
|
✅ Demonstrar profissionalismo
|
||||||
|
|
||||||
|
**Experimente agora mesmo**: [Criar meu QR Code WiFi Grátis](/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Gostou deste tutorial?** Compartilhe com amigos que também querem facilitar o acesso à rede WiFi! Explore também nossos outros tipos de QR Code para diferentes necessidades.
|
||||||
|
|
||||||
|
## Outros Tutoriais Úteis
|
||||||
|
|
||||||
|
- Como criar QR Code para WhatsApp
|
||||||
|
- QR Code para Cartão de Visita Digital
|
||||||
|
- Como criar QR Code para URLs e Links
|
||||||
|
- Personalização avançada de QR Codes
|
||||||
|
|
||||||
|
**Crie agora seu QR Code WiFi gratuito e transforme a experiência dos seus visitantes!** 🚀📱
|
||||||
599
Content/Tutoriais/qr-code-para-corredores-inmuebles.es-PY.md
Normal file
599
Content/Tutoriais/qr-code-para-corredores-inmuebles.es-PY.md
Normal file
@ -0,0 +1,599 @@
|
|||||||
|
---
|
||||||
|
title: "Código QR para Corredores de Inmuebles: Guía Completa para Etiquetas y Volantes"
|
||||||
|
description: "Descubre cómo usar código QR en etiquetas adhesivas, carteles y volantes inmobiliarios. Aumenta tus ventas con tecnología gratuita y profesional."
|
||||||
|
keywords: "codigo qr corredor inmuebles, etiqueta corredor inmobiliaria, qr inmobiliaria, etiquetas adhesivas corredores, divulgar corredor propiedades, qr cartel se vende"
|
||||||
|
author: "QR Rapido"
|
||||||
|
date: 2025-10-10
|
||||||
|
lastmod: 2025-10-10
|
||||||
|
image: "/images/tutoriais/qr-code-corretor-imoveis-hero.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Código QR para Corredores de Inmuebles: Guía Completa para Etiquetas y Volantes
|
||||||
|
|
||||||
|
Si eres corredor de inmuebles, sabes que **captar leads calificados** es esencial para cerrar negocios. Imagina transformar tus carteles de "Se Vende", volantes y etiquetas adhesivas en herramientas interactivas que conectan clientes directamente a tu WhatsApp, ficha del inmueble o tarjeta de visita digital - todo esto **gratuitamente** con códigos QR.
|
||||||
|
|
||||||
|
En esta guía completa, aprenderás a crear y aplicar códigos QR profesionales en materiales inmobiliarios, aumentando tus conversiones y destacándote de la competencia.
|
||||||
|
|
||||||
|
## ¿Por Qué los Corredores de Inmuebles Deben Usar Códigos QR?
|
||||||
|
|
||||||
|
### **Ventajas Comprobadas**
|
||||||
|
|
||||||
|
- ✅ **Captación 24/7**: Tu cartel trabaja para ti incluso cuando estás durmiendo
|
||||||
|
- ✅ **Contacto Instantáneo**: Cliente escanea y ya está en tu WhatsApp
|
||||||
|
- ✅ **Cero Escritura**: Elimina errores al anotar números
|
||||||
|
- ✅ **Rastreo**: Sabe cuántas personas se interesaron
|
||||||
|
- ✅ **Profesionalismo**: Demuestra modernidad e innovación
|
||||||
|
- ✅ **Costo Cero**: Genera códigos QR ilimitados gratuitamente
|
||||||
|
- ✅ **Tour Virtual**: Lleva al cliente dentro del inmueble virtualmente
|
||||||
|
|
||||||
|
### **Estadísticas del Mercado**
|
||||||
|
|
||||||
|
Según estudios del sector inmobiliario:
|
||||||
|
- **78%** de los compradores investigan propiedades por celular
|
||||||
|
- **65%** prefieren contacto vía WhatsApp en lugar de llamada
|
||||||
|
- **43%** escanean códigos QR en carteles de inmuebles cuando los ven
|
||||||
|
- Corredores que usan código QR tienen **35% más leads** mensuales
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dónde Aplicar Códigos QR en Marketing Inmobiliario
|
||||||
|
|
||||||
|
### **1. Carteles de "Se Vende" y "Se Alquila"**
|
||||||
|
|
||||||
|
**¡El uso más poderoso!** El cartel al frente del inmueble es visto por cientos de personas diariamente.
|
||||||
|
|
||||||
|
**Qué colocar en el código QR:**
|
||||||
|
- Link directo a tu WhatsApp
|
||||||
|
- vCard con tus contactos completos
|
||||||
|
- Tour virtual 360° del inmueble
|
||||||
|
- Ficha técnica detallada (PDF)
|
||||||
|
- Video del inmueble en YouTube
|
||||||
|
|
||||||
|
**Consejo profesional**: Coloca texto llamativo como:
|
||||||
|
- "Escanea y agenda tu visita AHORA"
|
||||||
|
- "Tour Virtual - Apunta tu cámara aquí"
|
||||||
|
- "WhatsApp Directo del Corredor"
|
||||||
|
|
||||||
|
### **2. Etiquetas Adhesivas y Tags**
|
||||||
|
|
||||||
|
Etiquetas pequeñas (5x5cm hasta 10x10cm) son perfectas para:
|
||||||
|
|
||||||
|
**Aplicaciones:**
|
||||||
|
- Pegar en autos de la inmobiliaria
|
||||||
|
- Fijar en portones de inmuebles
|
||||||
|
- Aplicar en vitrinas de locales
|
||||||
|
- Distribuir en establecimientos asociados
|
||||||
|
- Colocar en ascensores de edificios
|
||||||
|
|
||||||
|
**Ventajas:**
|
||||||
|
- Bajo costo de impresión
|
||||||
|
- Fácil distribución masiva
|
||||||
|
- Pueden ser cambiadas rápidamente
|
||||||
|
- Óptimas para acciones promocionales
|
||||||
|
|
||||||
|
### **3. Volantes y Flyers**
|
||||||
|
|
||||||
|
¡Transforma volantes de papel en herramientas digitales!
|
||||||
|
|
||||||
|
**Dónde aplicar:**
|
||||||
|
- Esquina superior derecha (lugar de mayor atención)
|
||||||
|
- Centro, si es el foco principal
|
||||||
|
- Reverso, con llamada destacada
|
||||||
|
|
||||||
|
**Contenido recomendado:**
|
||||||
|
- Portafolio digital de inmuebles
|
||||||
|
- Formulario de registro
|
||||||
|
- Calculadora de financiamiento
|
||||||
|
- Lista completa de propiedades disponibles
|
||||||
|
|
||||||
|
### **4. Folders y Revistas Inmobiliarias**
|
||||||
|
|
||||||
|
Materiales impresos premium merecen códigos QR estratégicos.
|
||||||
|
|
||||||
|
**Uso ideal:**
|
||||||
|
- 1 QR por inmueble destacado
|
||||||
|
- QR en la portada para portafolio completo
|
||||||
|
- QR en la contraportada con tus contactos
|
||||||
|
- QR en cada página con más información
|
||||||
|
|
||||||
|
### **5. Tarjetas de Visita**
|
||||||
|
|
||||||
|
¡El clásico nunca pasa de moda, pero puede ser mejorado!
|
||||||
|
|
||||||
|
**Código QR en la tarjeta permite:**
|
||||||
|
- Guardar contacto automáticamente (vCard)
|
||||||
|
- Ver portafolio online
|
||||||
|
- Agendar reunión directo en la agenda
|
||||||
|
- Enviar mensaje vía WhatsApp
|
||||||
|
|
||||||
|
### **6. Firma de Email**
|
||||||
|
|
||||||
|
¡Cada email que envías es una oportunidad!
|
||||||
|
|
||||||
|
**Incluye código QR para:**
|
||||||
|
- Tu vCard completo
|
||||||
|
- Último lanzamiento inmobiliario
|
||||||
|
- Evaluación gratuita de inmueble
|
||||||
|
- Agendamiento de visitas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Paso a Paso: Cómo Crear Código QR para Corredores
|
||||||
|
|
||||||
|
Voy a mostrar cómo crear **3 tipos de códigos QR** esenciales para corredores:
|
||||||
|
|
||||||
|
### **Tipo 1: Código QR de vCard (Tarjeta de Visita Digital)**
|
||||||
|
|
||||||
|
Perfecto para: Tarjetas de visita, firma de email, credenciales
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 1 AQUÍ: Selección del tipo de código QR - vCard destacado]**
|
||||||
|
|
||||||
|
#### Paso 1: Selecciona "Tarjeta de Visita"
|
||||||
|
|
||||||
|
Accede al generador y elige la opción **Tarjeta de Visita** en el menú de tipos.
|
||||||
|
|
||||||
|
#### Paso 2: Completa tus Datos Profesionales
|
||||||
|
|
||||||
|
**Información obligatoria:**
|
||||||
|
- **Nombre completo**: Juan Silva
|
||||||
|
- **Cargo**: Corredor de Inmuebles - Matrícula 12345
|
||||||
|
- **Empresa**: Inmobiliaria Success
|
||||||
|
- **Teléfono**: +595 21 123-4567
|
||||||
|
- **Email**: juan.silva@inmuebles.com.py
|
||||||
|
- **Website**: www.juansilva.inmuebles.py
|
||||||
|
- **Dirección**: Av. Mariscal López - Asunción, Paraguay
|
||||||
|
|
||||||
|
**Campos opcionales estratégicos:**
|
||||||
|
- WhatsApp Business
|
||||||
|
- Instagram profesional
|
||||||
|
- LinkedIn
|
||||||
|
- Canal de YouTube con tours virtuales
|
||||||
|
|
||||||
|
#### Paso 3: Genera y Descarga
|
||||||
|
|
||||||
|
Haz clic en **"Generar Código QR"** y descarga en alta resolución.
|
||||||
|
|
||||||
|
**Dónde usar este QR:**
|
||||||
|
- Etiquetas adhesivas en el auto
|
||||||
|
- Tarjetas de visita
|
||||||
|
- Firma de email
|
||||||
|
- Credencial profesional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Tipo 2: Código QR para WhatsApp Directo**
|
||||||
|
|
||||||
|
Perfecto para: Carteles de inmuebles, volantes, anuncios
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 3 AQUÍ: Formulario de WhatsApp con código QR completado]**
|
||||||
|
|
||||||
|
#### Paso 1: Selecciona "WhatsApp"
|
||||||
|
|
||||||
|
En el generador, elige la opción **WhatsApp** (o SMS si prefieres).
|
||||||
|
|
||||||
|
#### Paso 2: Configura el Mensaje Pre-llenado
|
||||||
|
|
||||||
|
**Ejemplo de mensaje eficaz:**
|
||||||
|
|
||||||
|
```
|
||||||
|
¡Hola! Vi el cartel del inmueble en [CALLE/BARRIO] y me gustaría agendar una visita. ¿Puede pasarme más información?
|
||||||
|
```
|
||||||
|
|
||||||
|
**Por qué el mensaje pre-llenado funciona:**
|
||||||
|
- Cliente no necesita pensar qué escribir
|
||||||
|
- Ya sabes de qué inmueble está hablando
|
||||||
|
- Aumenta en 80% la tasa de conversión
|
||||||
|
|
||||||
|
#### Paso 3: Personaliza para Cada Inmueble
|
||||||
|
|
||||||
|
**Consejo importante**: ¡Crea códigos QR diferentes para cada inmueble!
|
||||||
|
|
||||||
|
Ejemplo para apartamento en Recoleta:
|
||||||
|
```
|
||||||
|
¡Hola! Vi el cartel del Departamento 3 dormitorios en Recoleta (Ref: DEPT-001). Me gustaría saber más detalles y agendar visita.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ventaja**: ¡Ya sabes exactamente qué inmueble quiere ver el cliente!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Tipo 3: Código QR para URL (Tour Virtual / Ficha Técnica)**
|
||||||
|
|
||||||
|
Perfecto para: Inmuebles de alto estándar, lanzamientos, propiedades rurales
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 4 AQUÍ: Ejemplo de formulario URL completado]**
|
||||||
|
|
||||||
|
#### Paso 1: Prepara el Contenido Digital
|
||||||
|
|
||||||
|
Antes de crear el QR, necesitas tener:
|
||||||
|
|
||||||
|
**Opción A - Tour Virtual:**
|
||||||
|
- Video en YouTube del inmueble
|
||||||
|
- Tour 360° (Google Street View, Matterport)
|
||||||
|
- Galería de fotos en Instagram/Facebook
|
||||||
|
|
||||||
|
**Opción B - Landing Page:**
|
||||||
|
- Ficha técnica completa del inmueble
|
||||||
|
- Fotos en alta resolución
|
||||||
|
- Mapa de ubicación
|
||||||
|
- Calculadora de financiamiento
|
||||||
|
- Formulario de interés
|
||||||
|
|
||||||
|
#### Paso 2: Acorta la URL (¡Importante!)
|
||||||
|
|
||||||
|
Usa acortadores como:
|
||||||
|
- bit.ly
|
||||||
|
- tinyurl.com
|
||||||
|
- QR Rapido (si tiene función de acortamiento)
|
||||||
|
|
||||||
|
**Ejemplo:**
|
||||||
|
- ❌ URL larga: `https://www.miinmobiliaria.com.py/inmuebles/departamento-3-dormitorios-recoleta-asuncion-ref-dept001?utm_source=cartel`
|
||||||
|
- ✅ URL corta: `bit.ly/dept-recoleta-001`
|
||||||
|
|
||||||
|
**Ventaja de URL corta**: Genera código QR más simple y fácil de escanear
|
||||||
|
|
||||||
|
#### Paso 3: Crea el Código QR
|
||||||
|
|
||||||
|
Pega la URL corta en el campo **URL/Link** y genera el código.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cómo Personalizar tu Código QR Profesionalmente
|
||||||
|
|
||||||
|
**[INSERTAR IMAGEN 2 AQUÍ: Panel de personalización con colores personalizados]**
|
||||||
|
|
||||||
|
### **Elección de Colores Estratégicos**
|
||||||
|
|
||||||
|
**Para Inmobiliarias Tradicionales:**
|
||||||
|
- Azul marino + Blanco (confianza, seriedad)
|
||||||
|
- Negro + Dorado (lujo, exclusividad)
|
||||||
|
- Verde oscuro + Blanco (crecimiento, estabilidad)
|
||||||
|
|
||||||
|
**Para Inmobiliarias Modernas:**
|
||||||
|
- Naranja + Blanco (energía, innovación)
|
||||||
|
- Púrpura + Blanco (creatividad, diferenciación)
|
||||||
|
- Rojo + Blanco (urgencia, acción)
|
||||||
|
|
||||||
|
**Regla de oro**: ¡Siempre mantén alto contraste entre color principal y fondo!
|
||||||
|
|
||||||
|
### **Tamaños Recomendados por Aplicación**
|
||||||
|
|
||||||
|
**Carteles de calle (distancia 2-5 metros):**
|
||||||
|
- Código QR: 15x15cm o mayor
|
||||||
|
- Resolución: 500px mínimo
|
||||||
|
|
||||||
|
**Volantes A5/A4:**
|
||||||
|
- Código QR: 4x4cm a 6x6cm
|
||||||
|
- Resolución: 300px
|
||||||
|
|
||||||
|
**Etiquetas adhesivas:**
|
||||||
|
- Código QR: 5x5cm (tamaño del adhesivo)
|
||||||
|
- Resolución: 300px
|
||||||
|
|
||||||
|
**Tarjetas de visita:**
|
||||||
|
- Código QR: 2,5x2,5cm
|
||||||
|
- Resolución: 200px
|
||||||
|
|
||||||
|
### **Agrega Llamadas a la Acción (CTA)**
|
||||||
|
|
||||||
|
¡Nunca coloques solo el código QR! Agrega texto atractivo:
|
||||||
|
|
||||||
|
**Ejemplos eficaces:**
|
||||||
|
- 📱 "Apunta la cámara y habla conmigo en WhatsApp"
|
||||||
|
- 🏠 "Tour Virtual 360° - Escanea Aquí"
|
||||||
|
- 💬 "Agenda tu Visita Ahora"
|
||||||
|
- 📋 "Ve Fotos y Detalles Completos"
|
||||||
|
- 🎯 "Guarda Mi Contacto Automáticamente"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estrategias Avanzadas para Corredores
|
||||||
|
|
||||||
|
### **1. Códigos QR Rastreables (Dinámicos)**
|
||||||
|
|
||||||
|
Usa servicios de código QR dinámico para:
|
||||||
|
|
||||||
|
- Saber cuántas personas escanearon
|
||||||
|
- Ver horario de los escaneos
|
||||||
|
- Identificar ubicación aproximada
|
||||||
|
- Cambiar el destino sin reimprimir
|
||||||
|
|
||||||
|
**Cuándo usar:**
|
||||||
|
- Campañas con muchos materiales impresos
|
||||||
|
- Pruebas A/B de diferentes enfoques
|
||||||
|
- Carteles permanentes en inmuebles
|
||||||
|
|
||||||
|
### **2. Múltiples Códigos QR en el Mismo Cartel**
|
||||||
|
|
||||||
|
¿Cartel grande? ¡Usa 2-3 códigos QR diferentes!
|
||||||
|
|
||||||
|
**Ejemplo de cartel completo:**
|
||||||
|
- **QR 1** (arriba): "Habla en WhatsApp"
|
||||||
|
- **QR 2** (centro): "Tour Virtual 360°"
|
||||||
|
- **QR 3** (abajo): "Guarda Mi Contacto"
|
||||||
|
|
||||||
|
**Ventaja**: Cliente elige la acción que prefiere hacer
|
||||||
|
|
||||||
|
### **3. Código QR + Realidad Aumentada**
|
||||||
|
|
||||||
|
Para lanzamientos y emprendimientos:
|
||||||
|
|
||||||
|
- Código QR lleva a app de RA
|
||||||
|
- Cliente apunta celular y ve el edificio terminado
|
||||||
|
- Visualiza departamento decorado
|
||||||
|
- ¡Extremadamente impactante!
|
||||||
|
|
||||||
|
### **4. Campañas de Temporada**
|
||||||
|
|
||||||
|
Crea códigos QR específicos para:
|
||||||
|
|
||||||
|
- **Enero**: "Planifica 2025 - Compra tu inmueble"
|
||||||
|
- **Junio**: "Vacaciones de Julio en tu Nuevo Hogar"
|
||||||
|
- **Noviembre**: "Black Friday Inmobiliaria"
|
||||||
|
- **Diciembre**: "Empieza el Año en Casa Propia"
|
||||||
|
|
||||||
|
### **5. Alianzas Estratégicas**
|
||||||
|
|
||||||
|
Distribuye etiquetas con código QR en:
|
||||||
|
|
||||||
|
- Tiendas de materiales de construcción
|
||||||
|
- Oficinas de arquitectura
|
||||||
|
- Gestorías y escribanías
|
||||||
|
- Gimnasios y restaurantes del barrio
|
||||||
|
- Edificios comerciales (tablero de avisos)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modelos de Etiquetas Adhesivas Profesionales
|
||||||
|
|
||||||
|
### **Modelo 1: Etiqueta Minimalista (5x5cm)**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ [CÓDIGO QR] │
|
||||||
|
│ │
|
||||||
|
│ Juan Silva │
|
||||||
|
│ Matrícula 12345 │
|
||||||
|
│ (021) 123-4567 │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Modelo 2: Etiqueta con Destaque (7x7cm)**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ VENDE TU INMUEBLE │
|
||||||
|
│ SIN BUROCRACIA │
|
||||||
|
│ │
|
||||||
|
│ [CÓDIGO QR] │
|
||||||
|
│ │
|
||||||
|
│ "Escanea y habla │
|
||||||
|
│ directo conmigo" │
|
||||||
|
│ │
|
||||||
|
│ Juan Silva │
|
||||||
|
│ Corredor Mat. XXXX │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Modelo 3: Etiqueta Tour Virtual (10x10cm)**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────────────────────┐
|
||||||
|
│ TOUR VIRTUAL 360° │
|
||||||
|
│ Visita este inmueble │
|
||||||
|
│ ¡sin salir del sofá! │
|
||||||
|
│ │
|
||||||
|
│ [CÓDIGO QR GRANDE] │
|
||||||
|
│ │
|
||||||
|
│ 📱 Apunta tu cámara │
|
||||||
|
│ │
|
||||||
|
│ Inmobiliaria Success │
|
||||||
|
│ (021) 123-4567 │
|
||||||
|
└───────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mejores Prácticas de Impresión
|
||||||
|
|
||||||
|
### **Materiales Recomendados**
|
||||||
|
|
||||||
|
**Para carteles externos:**
|
||||||
|
- **Vinilo adhesivo resistente a UV**
|
||||||
|
- **Lona con impresión UV**
|
||||||
|
- **ACM (Aluminio Compuesto)**
|
||||||
|
|
||||||
|
Duración: 2-3 años expuestos al sol
|
||||||
|
|
||||||
|
**Para etiquetas adhesivas:**
|
||||||
|
- **Papel BOPP (Polipropileno)**
|
||||||
|
- **Vinilo blanco brillante**
|
||||||
|
- **Papel couché con laminación**
|
||||||
|
|
||||||
|
Duración: 6-12 meses
|
||||||
|
|
||||||
|
**Para volantes:**
|
||||||
|
- **Couché 115g o 150g**
|
||||||
|
- **Barniz localizado en el código QR** (destaque)
|
||||||
|
|
||||||
|
### **Pruebas Antes de Imprimir en Masa**
|
||||||
|
|
||||||
|
⚠️ **SIEMPRE haz esto:**
|
||||||
|
|
||||||
|
1. Imprime 1 muestra en tamaño real
|
||||||
|
2. Prueba con 5 celulares diferentes
|
||||||
|
3. Prueba a diferentes distancias
|
||||||
|
4. Prueba con poca luz
|
||||||
|
5. Solo entonces imprime grandes cantidades
|
||||||
|
|
||||||
|
**Celulares para probar:**
|
||||||
|
- iPhone (iOS actualizado)
|
||||||
|
- Samsung (Android)
|
||||||
|
- Xiaomi o Motorola (Android popular)
|
||||||
|
|
||||||
|
### **Dónde Imprimir**
|
||||||
|
|
||||||
|
**Imprentas rápidas en Asunción:**
|
||||||
|
- Etiquetas adhesivas: 100 unidades por Gs. 150.000-350.000
|
||||||
|
- Volantes A5: 1000 unidades por Gs. 800.000-1.500.000
|
||||||
|
|
||||||
|
**Online (más económico):**
|
||||||
|
- Mercado Libre Paraguay
|
||||||
|
- Imprentas locales con pedido online
|
||||||
|
|
||||||
|
**Consejo**: ¡Pide presupuesto en 3 lugares diferentes!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Errores Comunes que Corredores Deben Evitar
|
||||||
|
|
||||||
|
### ❌ **Error 1: Código QR Muy Pequeño**
|
||||||
|
|
||||||
|
**Problema**: En carteles de calle vistos de lejos, QR pequeño no funciona
|
||||||
|
|
||||||
|
**Solución**: Mínimo 15x15cm para carteles externos
|
||||||
|
|
||||||
|
### ❌ **Error 2: Colores con Bajo Contraste**
|
||||||
|
|
||||||
|
**Problema**: QR amarillo en fondo blanco no escanea
|
||||||
|
|
||||||
|
**Solución**: Usa siempre colores oscuros en fondo claro (o viceversa)
|
||||||
|
|
||||||
|
### ❌ **Error 3: No Probar Antes de Imprimir**
|
||||||
|
|
||||||
|
**Problema**: Imprime 1000 volantes y descubre que QR no funciona
|
||||||
|
|
||||||
|
**Solución**: Siempre prueba impresión piloto
|
||||||
|
|
||||||
|
### ❌ **Error 4: URL Rota o Temporal**
|
||||||
|
|
||||||
|
**Problema**: Link del inmueble expira, QR queda inútil
|
||||||
|
|
||||||
|
**Solución**: Usa URLs permanentes o QR dinámico editable
|
||||||
|
|
||||||
|
### ❌ **Error 5: Sin Instrucción de Uso**
|
||||||
|
|
||||||
|
**Problema**: Persona mayor no sabe qué hacer con el QR
|
||||||
|
|
||||||
|
**Solución**: Agrega "Apunta la cámara del celular aquí"
|
||||||
|
|
||||||
|
### ❌ **Error 6: Código QR Único para Todos los Inmuebles**
|
||||||
|
|
||||||
|
**Problema**: No sabes qué inmueble generó el lead
|
||||||
|
|
||||||
|
**Solución**: Crea QR específico para cada propiedad
|
||||||
|
|
||||||
|
### ❌ **Error 7: Material de Baja Calidad**
|
||||||
|
|
||||||
|
**Problema**: Etiqueta se desvanece en 1 mes al sol
|
||||||
|
|
||||||
|
**Solución**: Invierte en material UV resistente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Casos de Éxito en Paraguay
|
||||||
|
|
||||||
|
### **Caso 1: Corredor en Asunción**
|
||||||
|
|
||||||
|
**Estrategia**: Colocó código QR en todos los 12 carteles de inmuebles
|
||||||
|
|
||||||
|
**Resultado:**
|
||||||
|
- 98 escaneos en el primer mes
|
||||||
|
- 28 conversaciones en WhatsApp
|
||||||
|
- 7 visitas agendadas
|
||||||
|
- 2 ventas cerradas
|
||||||
|
|
||||||
|
**ROI**: Invirtió Gs. 500.000 en etiquetas, facturó comisiones millonarias
|
||||||
|
|
||||||
|
### **Caso 2: Inmobiliaria en Ciudad del Este**
|
||||||
|
|
||||||
|
**Estrategia**: Volantes con QR para tour virtual de lanzamiento
|
||||||
|
|
||||||
|
**Resultado:**
|
||||||
|
- 3.000 volantes distribuidos
|
||||||
|
- 645 accesos al tour virtual
|
||||||
|
- 112 registros de interesados
|
||||||
|
- 18 departamentos vendidos en preventa
|
||||||
|
|
||||||
|
### **Caso 3: Corredor Autónomo en Encarnación**
|
||||||
|
|
||||||
|
**Estrategia**: Etiquetas adhesivas en establecimientos asociados
|
||||||
|
|
||||||
|
**Resultado:**
|
||||||
|
- 150 etiquetas distribuidas en 30 locales
|
||||||
|
- 52 nuevos contactos en 3 meses
|
||||||
|
- 9 evaluaciones de inmuebles agendadas
|
||||||
|
- 2 captaciones exclusivas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Preguntas Frecuentes
|
||||||
|
|
||||||
|
### **¿Necesito pagar para crear código QR?**
|
||||||
|
|
||||||
|
¡No! En QR Rapido creas códigos QR ilimitados gratuitamente. Solo pagas si quieres recursos premium como rastreo avanzado.
|
||||||
|
|
||||||
|
### **¿El código QR funciona para siempre?**
|
||||||
|
|
||||||
|
Códigos QR estáticos (gratuitos) funcionan para siempre, pero no pueden ser editados. Códigos QR dinámicos (pagos) pueden ser editados incluso después de impresos.
|
||||||
|
|
||||||
|
### **¿Qué tipo de QR usar en carteles de inmuebles?**
|
||||||
|
|
||||||
|
Recomiendo **WhatsApp** con mensaje pre-llenado. Así el cliente ya inicia la conversación sabiendo de qué inmueble se trata.
|
||||||
|
|
||||||
|
### **¿Puedo colocar logo de la inmobiliaria en el QR?**
|
||||||
|
|
||||||
|
Sí, ¡pero con cuidado! Logos muy grandes pueden dificultar la lectura. Mantén el logo pequeño (máximo 20% del QR).
|
||||||
|
|
||||||
|
### **¿Cuántas personas escanean QR en carteles?**
|
||||||
|
|
||||||
|
Según investigaciones, entre 5-15% de las personas que ven el cartel escanean el QR. En áreas movimentadas, esto puede generar decenas de leads por mes.
|
||||||
|
|
||||||
|
### **¿El código QR funciona de noche?**
|
||||||
|
|
||||||
|
Sí, siempre que haya alguna iluminación (poste de calle, luz de la pantalla del celular ya ayuda). Para mejor resultado, ilumina el cartel.
|
||||||
|
|
||||||
|
### **¿Puedo usar el mismo QR en varios materiales?**
|
||||||
|
|
||||||
|
Puedes, pero no es recomendado. Crea códigos QR diferentes para saber de dónde viene cada lead (cartel, volante, etiqueta, etc).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist del Corredor Profesional
|
||||||
|
|
||||||
|
Antes de imprimir, verifica:
|
||||||
|
|
||||||
|
- [ ] Código QR probado en al menos 3 celulares diferentes
|
||||||
|
- [ ] Tamaño adecuado para la distancia de escaneo
|
||||||
|
- [ ] Alto contraste entre QR y fondo
|
||||||
|
- [ ] Llamada a la acción clara ("Escanea aquí")
|
||||||
|
- [ ] Información de contacto visible (nombre, matrícula, teléfono)
|
||||||
|
- [ ] URL corta si es link (más fácil de escanear)
|
||||||
|
- [ ] Material de impresión resistente (especialmente externo)
|
||||||
|
- [ ] Margen de seguridad alrededor del QR (mínimo 1cm)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusión
|
||||||
|
|
||||||
|
Los códigos QR son la **herramienta más económica y eficaz** para corredores modernos captar leads calificados. Con inversión de menos de Gs. 500.000 en etiquetas y volantes, puedes:
|
||||||
|
|
||||||
|
✅ Captar leads 24 horas al día
|
||||||
|
✅ Facilitar el contacto instantáneo vía WhatsApp
|
||||||
|
✅ Mostrar tours virtuales impresionantes
|
||||||
|
✅ Rastrear qué materiales generan más resultado
|
||||||
|
✅ Destacarte de la competencia conservadora
|
||||||
|
|
||||||
|
**El mercado inmobiliario está cada vez más digital. Quien no se adapta, queda atrás.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ¡Empieza Ahora!
|
||||||
|
|
||||||
|
**Crea tu primer código QR para corredor gratuitamente:**
|
||||||
|
|
||||||
|
1. [Generar Código QR de WhatsApp](/) - Para carteles de inmuebles
|
||||||
|
2. [Generar Código QR vCard](/) - Para tarjetas de visita
|
||||||
|
3. [Generar Código QR de URL](/) - Para tour virtual
|
||||||
|
|
||||||
|
**¡Transforma tus carteles y volantes en máquinas de captar leads!** 🏠📱🚀
|
||||||
640
Content/Tutoriais/qr-code-para-corretores-imoveis.pt-BR.md
Normal file
640
Content/Tutoriais/qr-code-para-corretores-imoveis.pt-BR.md
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
---
|
||||||
|
title: "QR Code para Corretores de Imóveis: Guia Completo para Etiquetas e Panfletos"
|
||||||
|
description: "Descubra como usar QR Code em etiquetas adesivas, placas e panfletos imobiliários. Aumente suas vendas com tecnologia gratuita e profissional."
|
||||||
|
keywords: "qr code corretor imoveis, etiqueta corretor imoveis, qrcode imobiliaria, etiquetas adesivas corretores, divulgar corretor imoveis, qr code placa vende-se"
|
||||||
|
author: "QR Rapido"
|
||||||
|
date: 2025-10-10
|
||||||
|
lastmod: 2025-10-10
|
||||||
|
image: "/images/tutoriais/qr-code-corretor-imoveis-hero.jpg"
|
||||||
|
---
|
||||||
|
|
||||||
|
# QR Code para Corretores de Imóveis: Guia Completo para Etiquetas e Panfletos
|
||||||
|
|
||||||
|
Se você é corretor de imóveis, sabe que **captar leads qualificados** é essencial para fechar negócios. Imagine transformar suas placas de "Vende-se", panfletos e etiquetas adesivas em ferramentas interativas que conectam clientes diretamente ao seu WhatsApp, ficha do imóvel ou cartão de visita digital - tudo isso **gratuitamente** com QR Codes!
|
||||||
|
|
||||||
|
Neste guia completo, você aprenderá a criar e aplicar QR Codes profissionais em materiais imobiliários, aumentando suas conversões e se destacando da concorrência.
|
||||||
|
|
||||||
|
## Por Que Corretores de Imóveis Devem Usar QR Codes?
|
||||||
|
|
||||||
|
### **Vantagens Comprovadas**
|
||||||
|
|
||||||
|
- ✅ **Captação 24/7**: Sua placa trabalha para você mesmo quando está dormindo
|
||||||
|
- ✅ **Contato Instantâneo**: Cliente escaneia e já está no seu WhatsApp
|
||||||
|
- ✅ **Zero Digitação**: Elimina erros ao anotar números
|
||||||
|
- ✅ **Rastreamento**: Saiba quantas pessoas se interessaram
|
||||||
|
- ✅ **Profissionalismo**: Demonstra modernidade e inovação
|
||||||
|
- ✅ **Custo Zero**: Gere QR Codes ilimitados gratuitamente
|
||||||
|
- ✅ **Tour Virtual**: Leve o cliente para dentro do imóvel virtualmente
|
||||||
|
|
||||||
|
### **Estatísticas do Mercado**
|
||||||
|
|
||||||
|
Segundo estudos do setor imobiliário:
|
||||||
|
- **78%** dos compradores pesquisam imóveis pelo celular
|
||||||
|
- **65%** preferem contato via WhatsApp ao invés de ligação
|
||||||
|
- **43%** escaneiam QR Codes em placas de imóveis quando veem
|
||||||
|
- Corretores que usam QR Code têm **35% mais leads** mensais
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Onde Aplicar QR Codes no Marketing Imobiliário
|
||||||
|
|
||||||
|
### **1. Placas de "Vende-se" e "Aluga-se"**
|
||||||
|
|
||||||
|
**O uso mais poderoso!** A placa na frente do imóvel é vista por centenas de pessoas diariamente.
|
||||||
|
|
||||||
|
**O que colocar no QR Code:**
|
||||||
|
- Link direto para seu WhatsApp
|
||||||
|
- vCard com seus contatos completos
|
||||||
|
- Tour virtual 360° do imóvel
|
||||||
|
- Ficha técnica detalhada (PDF)
|
||||||
|
- Vídeo do imóvel no YouTube
|
||||||
|
|
||||||
|
**Dica profissional**: Coloque texto chamativo como:
|
||||||
|
- "Escanei e agende sua visita AGORA"
|
||||||
|
- "Tour Virtual - Aponte a câmera aqui"
|
||||||
|
- "WhatsApp Direto do Corretor"
|
||||||
|
|
||||||
|
### **2. Etiquetas Adesivas e Tags**
|
||||||
|
|
||||||
|
Etiquetas pequenas (5x5cm até 10x10cm) são perfeitas para:
|
||||||
|
|
||||||
|
**Aplicações:**
|
||||||
|
- Colar em carros da imobiliária
|
||||||
|
- Fixar em portões de imóveis
|
||||||
|
- Aplicar em vitrines de lojas
|
||||||
|
- Distribuir em estabelecimentos parceiros
|
||||||
|
- Colocar em elevadores de prédios
|
||||||
|
|
||||||
|
**Vantagens:**
|
||||||
|
- Baixo custo de impressão
|
||||||
|
- Fácil distribuição em massa
|
||||||
|
- Podem ser trocadas rapidamente
|
||||||
|
- Ótimas para ações promocionais
|
||||||
|
|
||||||
|
### **3. Panfletos e Flyers**
|
||||||
|
|
||||||
|
Transforme panfletos de papel em ferramentas digitais!
|
||||||
|
|
||||||
|
**Onde aplicar:**
|
||||||
|
- Canto superior direito (local de maior atenção)
|
||||||
|
- Centro, se for o foco principal
|
||||||
|
- Verso, com chamada destacada
|
||||||
|
|
||||||
|
**Conteúdo recomendado:**
|
||||||
|
- Portfólio digital de imóveis
|
||||||
|
- Formulário de cadastro
|
||||||
|
- Calculadora de financiamento
|
||||||
|
- Lista completa de imóveis disponíveis
|
||||||
|
|
||||||
|
### **4. Folders e Revistas Imobiliárias**
|
||||||
|
|
||||||
|
Materiais impressos premium merecem QR Codes estratégicos.
|
||||||
|
|
||||||
|
**Uso ideal:**
|
||||||
|
- 1 QR por imóvel destacado
|
||||||
|
- QR na capa para portfólio completo
|
||||||
|
- QR na contracapa com seus contatos
|
||||||
|
- QR em cada página com mais informações
|
||||||
|
|
||||||
|
### **5. Cartões de Visita**
|
||||||
|
|
||||||
|
O clássico nunca sai de moda, mas pode ser turbinado!
|
||||||
|
|
||||||
|
**QR Code no cartão permite:**
|
||||||
|
- Salvar contato automaticamente (vCard)
|
||||||
|
- Ver portfólio online
|
||||||
|
- Agendar reunião direto na agenda
|
||||||
|
- Enviar mensagem via WhatsApp
|
||||||
|
|
||||||
|
### **6. Assinatura de Email**
|
||||||
|
|
||||||
|
Todo email que você envia é uma oportunidade!
|
||||||
|
|
||||||
|
**Inclua QR Code para:**
|
||||||
|
- Seu vCard completo
|
||||||
|
- Último lançamento imobiliário
|
||||||
|
- Avaliação gratuita de imóvel
|
||||||
|
- Agendamento de visitas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Passo a Passo: Como Criar QR Code para Corretores
|
||||||
|
|
||||||
|
Vou mostrar como criar **3 tipos de QR Codes** essenciais para corretores:
|
||||||
|
|
||||||
|
### **Tipo 1: QR Code de vCard (Cartão de Visita Digital)**
|
||||||
|
|
||||||
|
Perfeito para: Cartões de visita, assinatura de email, crachás
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 1 AQUI: Seleção do tipo de QR Code - vCard destacado]**
|
||||||
|
|
||||||
|
#### Passo 1: Selecione "Cartão de Visita"
|
||||||
|
|
||||||
|
Acesse o gerador e escolha a opção **Cartão de Visita** no menu de tipos.
|
||||||
|
|
||||||
|
#### Passo 2: Preencha seus Dados Profissionais
|
||||||
|
|
||||||
|
**Informações obrigatórias:**
|
||||||
|
- **Nome completo**: João Silva
|
||||||
|
- **Cargo**: Corretor de Imóveis CRECI 12345-F
|
||||||
|
- **Empresa**: Imobiliária Success
|
||||||
|
- **Telefone**: +55 11 98765-4321
|
||||||
|
- **Email**: joao.silva@imoveis.com.br
|
||||||
|
- **Website**: www.joaosilva.imoveis.br
|
||||||
|
- **Endereço**: Av. Paulista, 1000 - São Paulo, SP
|
||||||
|
|
||||||
|
**Campos opcionais estratégicos:**
|
||||||
|
- WhatsApp Business
|
||||||
|
- Instagram profissional
|
||||||
|
- LinkedIn
|
||||||
|
- Canal do YouTube com tours virtuais
|
||||||
|
|
||||||
|
#### Passo 3: Gere e Baixe
|
||||||
|
|
||||||
|
Clique em **"Gerar QR Code"** e baixe em alta resolução.
|
||||||
|
|
||||||
|
**Onde usar este QR:**
|
||||||
|
- Etiquetas adesivas no carro
|
||||||
|
- Cartões de visita
|
||||||
|
- Assinatura de email
|
||||||
|
- Crachá profissional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Tipo 2: QR Code para WhatsApp Direto**
|
||||||
|
|
||||||
|
Perfeito para: Placas de imóveis, panfletos, anúncios
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 3 AQUI: Formulário de WhatsApp QR Code preenchido]**
|
||||||
|
|
||||||
|
#### Passo 1: Selecione "WhatsApp"
|
||||||
|
|
||||||
|
No gerador, escolha a opção **WhatsApp** (ou SMS se preferir).
|
||||||
|
|
||||||
|
#### Passo 2: Configure a Mensagem Pré-Pronta
|
||||||
|
|
||||||
|
**Exemplo de mensagem eficaz:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Olá! Vi a placa do imóvel na [RUA/BAIRRO] e gostaria de agendar uma visita. Pode me passar mais informações?
|
||||||
|
```
|
||||||
|
|
||||||
|
**Por que mensagem pré-pronta funciona:**
|
||||||
|
- Cliente não precisa pensar no que escrever
|
||||||
|
- Você já sabe de qual imóvel ele está falando
|
||||||
|
- Aumenta em 80% a taxa de conversão
|
||||||
|
|
||||||
|
#### Passo 3: Personalize para Cada Imóvel
|
||||||
|
|
||||||
|
**Dica importante**: Crie QR Codes diferentes para cada imóvel!
|
||||||
|
|
||||||
|
Exemplo para apartamento no Jardins:
|
||||||
|
```
|
||||||
|
Olá! Vi a placa do Apartamento 3 quartos no Jardins (Ref: APT-001). Gostaria de saber mais detalhes e agendar visita.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vantagem**: Você já sabe exatamente qual imóvel o cliente quer ver!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Tipo 3: QR Code para URL (Tour Virtual / Ficha Técnica)**
|
||||||
|
|
||||||
|
Perfeito para: Imóveis de alto padrão, lançamentos, propriedades rurais
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 4 AQUI: Exemplo de formulário URL completado]**
|
||||||
|
|
||||||
|
#### Passo 1: Prepare o Conteúdo Digital
|
||||||
|
|
||||||
|
Antes de criar o QR, você precisa ter:
|
||||||
|
|
||||||
|
**Opção A - Tour Virtual:**
|
||||||
|
- Vídeo no YouTube do imóvel
|
||||||
|
- Tour 360° (Google Street View, Matterport)
|
||||||
|
- Galeria de fotos no Instagram/Facebook
|
||||||
|
|
||||||
|
**Opção B - Landing Page:**
|
||||||
|
- Ficha técnica completa do imóvel
|
||||||
|
- Fotos em alta resolução
|
||||||
|
- Mapa de localização
|
||||||
|
- Calculadora de financiamento
|
||||||
|
- Formulário de interesse
|
||||||
|
|
||||||
|
#### Passo 2: Encurte a URL (Importante!)
|
||||||
|
|
||||||
|
Use encurtadores como:
|
||||||
|
- bit.ly
|
||||||
|
- tinyurl.com
|
||||||
|
- QR Rapido (se tiver função de encurtamento)
|
||||||
|
|
||||||
|
**Exemplo:**
|
||||||
|
- ❌ URL longa: `https://www.minhaibiliaria.com.br/imoveis/apartamento-3-quartos-jardins-sao-paulo-ref-apt001?utm_source=placa`
|
||||||
|
- ✅ URL curta: `bit.ly/apt-jardins-001`
|
||||||
|
|
||||||
|
**Vantagem da URL curta**: Gera QR Code mais simples e fácil de escanear
|
||||||
|
|
||||||
|
#### Passo 3: Crie o QR Code
|
||||||
|
|
||||||
|
Cole a URL curta no campo **URL/Link** e gere o código.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Como Personalizar seu QR Code Profissionalmente
|
||||||
|
|
||||||
|
**[INSERIR IMAGEM 2 AQUI: Painel de personalização com cores personalizadas]**
|
||||||
|
|
||||||
|
### **Escolha de Cores Estratégicas**
|
||||||
|
|
||||||
|
**Para Imobiliárias Tradicionais:**
|
||||||
|
- Azul marinho + Branco (confiança, seriedade)
|
||||||
|
- Preto + Dourado (luxo, exclusividade)
|
||||||
|
- Verde escuro + Branco (crescimento, estabilidade)
|
||||||
|
|
||||||
|
**Para Imobiliárias Modernas:**
|
||||||
|
- Laranja + Branco (energia, inovação)
|
||||||
|
- Roxo + Branco (criatividade, diferenciação)
|
||||||
|
- Vermelho + Branco (urgência, ação)
|
||||||
|
|
||||||
|
**Regra de ouro**: Sempre mantenha alto contraste entre cor principal e fundo!
|
||||||
|
|
||||||
|
### **Tamanhos Recomendados por Aplicação**
|
||||||
|
|
||||||
|
**Placas de rua (distância 2-5 metros):**
|
||||||
|
- QR Code: 15x15cm ou maior
|
||||||
|
- Resolução: 500px mínimo
|
||||||
|
|
||||||
|
**Panfletos A5/A4:**
|
||||||
|
- QR Code: 4x4cm a 6x6cm
|
||||||
|
- Resolução: 300px
|
||||||
|
|
||||||
|
**Etiquetas adesivas:**
|
||||||
|
- QR Code: 5x5cm (tamanho do adesivo)
|
||||||
|
- Resolução: 300px
|
||||||
|
|
||||||
|
**Cartões de visita:**
|
||||||
|
- QR Code: 2,5x2,5cm
|
||||||
|
- Resolução: 200px
|
||||||
|
|
||||||
|
### **Adicione Chamadas para Ação (CTA)**
|
||||||
|
|
||||||
|
Nunca coloque apenas o QR Code sozinho! Adicione texto atrativo:
|
||||||
|
|
||||||
|
**Exemplos eficazes:**
|
||||||
|
- 📱 "Aponte a câmera e fale comigo no WhatsApp"
|
||||||
|
- 🏠 "Tour Virtual 360° - Escaneie Aqui"
|
||||||
|
- 💬 "Agende sua Visita Agora"
|
||||||
|
- 📋 "Veja Fotos e Detalhes Completos"
|
||||||
|
- 🎯 "Salve Meu Contato Automaticamente"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estratégias Avançadas para Corretores
|
||||||
|
|
||||||
|
### **1. QR Codes Rastreáveis (Dinâmicos)**
|
||||||
|
|
||||||
|
Use serviços de QR Code dinâmico para:
|
||||||
|
|
||||||
|
- Saber quantas pessoas escanearam
|
||||||
|
- Ver horário dos escaneamentos
|
||||||
|
- Identificar localização aproximada
|
||||||
|
- Mudar o destino sem reimprimir
|
||||||
|
|
||||||
|
**Quando usar:**
|
||||||
|
- Campanhas com muitos materiais impressos
|
||||||
|
- Testes A/B de diferentes abordagens
|
||||||
|
- Placas permanentes em imóveis
|
||||||
|
|
||||||
|
### **2. Múltiplos QR Codes na Mesma Placa**
|
||||||
|
|
||||||
|
Placa grande? Use 2-3 QR Codes diferentes!
|
||||||
|
|
||||||
|
**Exemplo de placa completa:**
|
||||||
|
- **QR 1** (topo): "Fale no WhatsApp"
|
||||||
|
- **QR 2** (centro): "Tour Virtual 360°"
|
||||||
|
- **QR 3** (rodapé): "Salve Meu Contato"
|
||||||
|
|
||||||
|
**Vantagem**: Cliente escolhe a ação que prefere fazer
|
||||||
|
|
||||||
|
### **3. QR Code + Realidade Aumentada**
|
||||||
|
|
||||||
|
Para lançamentos e empreendimentos:
|
||||||
|
|
||||||
|
- QR Code leva para app de RA
|
||||||
|
- Cliente aponta celular e vê o prédio pronto
|
||||||
|
- Visualiza apartamento decorado
|
||||||
|
- Extremamente impactante!
|
||||||
|
|
||||||
|
### **4. Campanhas Sazonais**
|
||||||
|
|
||||||
|
Crie QR Codes específicos para:
|
||||||
|
|
||||||
|
- **Janeiro**: "Planeje 2025 - Compre seu imóvel"
|
||||||
|
- **Junho**: "Férias de Julho no seu Novo Lar"
|
||||||
|
- **Novembro**: "Black Friday Imobiliária"
|
||||||
|
- **Dezembro**: "Comece o Ano na Casa Própria"
|
||||||
|
|
||||||
|
### **5. Parcerias Estratégicas**
|
||||||
|
|
||||||
|
Distribua etiquetas com QR Code em:
|
||||||
|
|
||||||
|
- Lojas de materiais de construção
|
||||||
|
- Escritórios de arquitetura
|
||||||
|
- Despachantes e cartórios
|
||||||
|
- Academias e restaurantes do bairro
|
||||||
|
- Prédios comerciais (quadro de avisos)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modelos de Etiquetas Adesivas Profissionais
|
||||||
|
|
||||||
|
### **Modelo 1: Etiqueta Minimalista (5x5cm)**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ [QR CODE] │
|
||||||
|
│ │
|
||||||
|
│ João Silva │
|
||||||
|
│ CRECI 12345-F │
|
||||||
|
│ (11) 98765-4321 │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Modelo 2: Etiqueta com Destaque (7x7cm)**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ VENDA SEU IMÓVEL │
|
||||||
|
│ SEM BUROCRACIA │
|
||||||
|
│ │
|
||||||
|
│ [QR CODE] │
|
||||||
|
│ │
|
||||||
|
│ "Escaneie e fale │
|
||||||
|
│ direto comigo" │
|
||||||
|
│ │
|
||||||
|
│ João Silva │
|
||||||
|
│ Corretor CRECI │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Modelo 3: Etiqueta Tour Virtual (10x10cm)**
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────────────────────┐
|
||||||
|
│ TOUR VIRTUAL 360° │
|
||||||
|
│ Visite este imóvel │
|
||||||
|
│ sem sair do sofá! │
|
||||||
|
│ │
|
||||||
|
│ [QR CODE GRANDE] │
|
||||||
|
│ │
|
||||||
|
│ 📱 Aponte sua câmera │
|
||||||
|
│ │
|
||||||
|
│ Imobiliária Success │
|
||||||
|
│ (11) 98765-4321 │
|
||||||
|
└───────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Melhores Práticas de Impressão
|
||||||
|
|
||||||
|
### **Materiais Recomendados**
|
||||||
|
|
||||||
|
**Para placas externas:**
|
||||||
|
- **Vinil adesivo resistente a UV**
|
||||||
|
- **Lona com impressão UV**
|
||||||
|
- **ACM (Alumínio Composto)**
|
||||||
|
|
||||||
|
Duração: 2-3 anos expostos ao sol
|
||||||
|
|
||||||
|
**Para etiquetas adesivas:**
|
||||||
|
- **Papel BOPP (Polipropileno)**
|
||||||
|
- **Vinil branco brilhante**
|
||||||
|
- **Papel couché com laminação**
|
||||||
|
|
||||||
|
Duração: 6-12 meses
|
||||||
|
|
||||||
|
**Para panfletos:**
|
||||||
|
- **Couché 115g ou 150g**
|
||||||
|
- **Verniz localizado no QR Code** (destaque)
|
||||||
|
|
||||||
|
### **Testes Antes de Imprimir em Massa**
|
||||||
|
|
||||||
|
⚠️ **SEMPRE faça isso:**
|
||||||
|
|
||||||
|
1. Imprima 1 amostra em tamanho real
|
||||||
|
2. Teste com 5 celulares diferentes
|
||||||
|
3. Teste em diferentes distâncias
|
||||||
|
4. Teste com pouca luz
|
||||||
|
5. Só então imprima grandes quantidades
|
||||||
|
|
||||||
|
**Celulares para testar:**
|
||||||
|
- iPhone (iOS atualizado)
|
||||||
|
- Samsung (Android)
|
||||||
|
- Xiaomi ou Motorola (Android popular)
|
||||||
|
|
||||||
|
### **Onde Imprimir**
|
||||||
|
|
||||||
|
**Gráficas rápidas:**
|
||||||
|
- Etiquetas adesivas: 100 unidades por R$ 30-80
|
||||||
|
- Panfletos A5: 1000 unidades por R$ 150-300
|
||||||
|
|
||||||
|
**Online (mais barato):**
|
||||||
|
- Printi.com.br
|
||||||
|
- Gráfica KWG
|
||||||
|
- Gráfica Atual
|
||||||
|
|
||||||
|
**Dica**: Peça orçamento em 3 lugares diferentes!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Erros Comuns que Corretores Devem Evitar
|
||||||
|
|
||||||
|
### ❌ **Erro 1: QR Code Muito Pequeno**
|
||||||
|
|
||||||
|
**Problema**: Em placas de rua vistas de longe, QR pequeno não funciona
|
||||||
|
|
||||||
|
**Solução**: Mínimo 15x15cm para placas externas
|
||||||
|
|
||||||
|
### ❌ **Erro 2: Cores com Baixo Contraste**
|
||||||
|
|
||||||
|
**Problema**: QR amarelo em fundo branco não escaneia
|
||||||
|
|
||||||
|
**Solução**: Use sempre cores escuras em fundo claro (ou vice-versa)
|
||||||
|
|
||||||
|
### ❌ **Erro 3: Não Testar Antes de Imprimir**
|
||||||
|
|
||||||
|
**Problema**: Imprime 1000 panfletos e descobre que QR não funciona
|
||||||
|
|
||||||
|
**Solução**: Sempre teste impressão piloto
|
||||||
|
|
||||||
|
### ❌ **Erro 4: URL Quebrada ou Temporária**
|
||||||
|
|
||||||
|
**Problema**: Link do imóvel expira, QR fica inútil
|
||||||
|
|
||||||
|
**Solução**: Use URLs permanentes ou QR dinâmico editável
|
||||||
|
|
||||||
|
### ❌ **Erro 5: Sem Instrução de Uso**
|
||||||
|
|
||||||
|
**Problema**: Pessoa mais velha não sabe o que fazer com o QR
|
||||||
|
|
||||||
|
**Solução**: Adicione "Aponte a câmera do celular aqui"
|
||||||
|
|
||||||
|
### ❌ **Erro 6: QR Code Único para Todos Imóveis**
|
||||||
|
|
||||||
|
**Problema**: Não sabe qual imóvel gerou o lead
|
||||||
|
|
||||||
|
**Solução**: Crie QR específico para cada propriedade
|
||||||
|
|
||||||
|
### ❌ **Erro 7: Material de Baixa Qualidade**
|
||||||
|
|
||||||
|
**Problema**: Etiqueta desbota em 1 mês no sol
|
||||||
|
|
||||||
|
**Solução**: Invista em material UV resistente
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cases de Sucesso
|
||||||
|
|
||||||
|
### **Case 1: Corretor em São Paulo**
|
||||||
|
|
||||||
|
**Estratégia**: Colocou QR Code em todas as 15 placas de imóveis
|
||||||
|
|
||||||
|
**Resultado:**
|
||||||
|
- 127 escaneamentos no primeiro mês
|
||||||
|
- 34 conversas no WhatsApp
|
||||||
|
- 8 visitas agendadas
|
||||||
|
- 2 vendas fechadas
|
||||||
|
|
||||||
|
**ROI**: Investiu R$ 150 em etiquetas, faturou R$ 28.000 em comissões
|
||||||
|
|
||||||
|
### **Case 2: Imobiliária no Rio de Janeiro**
|
||||||
|
|
||||||
|
**Estratégia**: Panfletos com QR para tour virtual de lançamento
|
||||||
|
|
||||||
|
**Resultado:**
|
||||||
|
- 5.000 panfletos distribuídos
|
||||||
|
- 890 acessos ao tour virtual
|
||||||
|
- 156 cadastros de interessados
|
||||||
|
- 23 apartamentos vendidos na pré-venda
|
||||||
|
|
||||||
|
### **Case 3: Corretor Autônomo no Interior**
|
||||||
|
|
||||||
|
**Estratégia**: Etiquetas adesivas em estabelecimentos parceiros
|
||||||
|
|
||||||
|
**Resultado:**
|
||||||
|
- 200 etiquetas distribuídas em 40 locais
|
||||||
|
- 67 novos contatos em 3 meses
|
||||||
|
- 12 avaliações de imóveis agendadas
|
||||||
|
- 3 captações exclusivas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ferramentas Complementares
|
||||||
|
|
||||||
|
### **Criação de Conteúdo Digital**
|
||||||
|
|
||||||
|
- **Canva**: Criar layouts de etiquetas e panfletos
|
||||||
|
- **Matterport**: Tours virtuais 360°
|
||||||
|
- **YouTube**: Hospedar vídeos de imóveis
|
||||||
|
- **Google Drive**: PDFs de fichas técnicas
|
||||||
|
|
||||||
|
### **Gestão de Leads**
|
||||||
|
|
||||||
|
- **Bitly**: Encurtar URLs e rastrear cliques
|
||||||
|
- **Google Analytics**: Monitorar acessos
|
||||||
|
- **WhatsApp Business**: Organizar conversas
|
||||||
|
- **RD Station**: CRM imobiliário
|
||||||
|
|
||||||
|
### **Impressão Online**
|
||||||
|
|
||||||
|
- **Printi**: Etiquetas e panfletos
|
||||||
|
- **Gráfica KWG**: Placas e banners
|
||||||
|
- **Sticker Mule**: Adesivos premium
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perguntas Frequentes
|
||||||
|
|
||||||
|
### **Preciso pagar para criar QR Code?**
|
||||||
|
|
||||||
|
Não! No QR Rapido você cria QR Codes ilimitados gratuitamente. Só paga se quiser recursos premium como rastreamento avançado.
|
||||||
|
|
||||||
|
### **O QR Code funciona para sempre?**
|
||||||
|
|
||||||
|
QR Codes estáticos (gratuitos) funcionam para sempre, mas não podem ser editados. QR Codes dinâmicos (pagos) podem ser editados mesmo depois de impressos.
|
||||||
|
|
||||||
|
### **Qual tipo de QR usar em placas de imóveis?**
|
||||||
|
|
||||||
|
Recomendo **WhatsApp** com mensagem pré-pronta. Assim o cliente já inicia a conversa sabendo de qual imóvel se trata.
|
||||||
|
|
||||||
|
### **Posso colocar logo da imobiliária no QR?**
|
||||||
|
|
||||||
|
Sim, mas com cuidado! Logos muito grandes podem dificultar a leitura. Mantenha a logo pequena (máximo 20% do QR).
|
||||||
|
|
||||||
|
### **Quantas pessoas escaneiam QR em placas?**
|
||||||
|
|
||||||
|
Segundo pesquisas, entre 5-15% das pessoas que veem a placa escaneiam o QR. Em áreas movimentadas, isso pode gerar dezenas de leads por mês.
|
||||||
|
|
||||||
|
### **QR Code funciona à noite?**
|
||||||
|
|
||||||
|
Sim, desde que haja alguma iluminação (poste de rua, luz da tela do celular já ajuda). Para melhor resultado, ilumine a placa.
|
||||||
|
|
||||||
|
### **Posso usar o mesmo QR em vários materiais?**
|
||||||
|
|
||||||
|
Pode, mas não é recomendado. Crie QR Codes diferentes para saber de onde vem cada lead (placa, panfleto, etiqueta, etc).
|
||||||
|
|
||||||
|
### **Como sei se o QR está funcionando?**
|
||||||
|
|
||||||
|
Teste imediatamente após criar! Use a câmera do seu celular e de pelo menos 2 amigos/familiares para garantir.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist do Corretor Profissional
|
||||||
|
|
||||||
|
Antes de imprimir, confira:
|
||||||
|
|
||||||
|
- [ ] QR Code testado em pelo menos 3 celulares diferentes
|
||||||
|
- [ ] Tamanho adequado para a distância de escaneamento
|
||||||
|
- [ ] Alto contraste entre QR e fundo
|
||||||
|
- [ ] Chamada para ação clara ("Escaneie aqui")
|
||||||
|
- [ ] Informações de contato visíveis (nome, CRECI, telefone)
|
||||||
|
- [ ] URL curta se for link (mais fácil de escanear)
|
||||||
|
- [ ] Material de impressão resistente (especialmente externo)
|
||||||
|
- [ ] Margem de segurança ao redor do QR (mínimo 1cm)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusão
|
||||||
|
|
||||||
|
QR Codes são a **ferramenta mais barata e eficaz** para corretores modernos captarem leads qualificados. Com investimento de menos de R$ 100 em etiquetas e panfletos, você pode:
|
||||||
|
|
||||||
|
✅ Captar leads 24 horas por dia
|
||||||
|
✅ Facilitar o contato instantâneo via WhatsApp
|
||||||
|
✅ Mostrar tours virtuais impressionantes
|
||||||
|
✅ Rastrear quais materiais geram mais resultado
|
||||||
|
✅ Se destacar da concorrência conservadora
|
||||||
|
|
||||||
|
**O mercado imobiliário está cada vez mais digital. Quem não se adapta, fica para trás.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comece Agora!
|
||||||
|
|
||||||
|
**Crie seu primeiro QR Code para corretor gratuitamente:**
|
||||||
|
|
||||||
|
1. [Gerar QR Code de WhatsApp](/) - Para placas de imóveis
|
||||||
|
2. [Gerar QR Code vCard](/) - Para cartões de visita
|
||||||
|
3. [Gerar QR Code de URL](/) - Para tour virtual
|
||||||
|
|
||||||
|
**Transforme suas placas e panfletos em máquinas de captar leads!** 🏠📱🚀
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Materiais Bônus para Download
|
||||||
|
|
||||||
|
- 📋 Template de mensagem pré-pronta para WhatsApp
|
||||||
|
- 🎨 Modelos de etiquetas editáveis (Canva)
|
||||||
|
- 📊 Planilha de controle de QR Codes por imóvel
|
||||||
|
- 🎯 Checklist de impressão profissional
|
||||||
|
|
||||||
|
**Quer se destacar no mercado imobiliário? Use QR Codes de forma estratégica e veja seus resultados multiplicarem!**
|
||||||
|
|
||||||
@ -14,13 +14,15 @@ namespace QRRapidoApp.Controllers
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||||
|
private readonly IMarkdownService _markdownService;
|
||||||
|
|
||||||
public HomeController(
|
public HomeController(
|
||||||
ILogger<HomeController> logger,
|
ILogger<HomeController> logger,
|
||||||
AdDisplayService adDisplayService,
|
AdDisplayService adDisplayService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IConfiguration config,
|
IConfiguration config,
|
||||||
IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer
|
IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer,
|
||||||
|
IMarkdownService markdownService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -28,6 +30,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
_userService = userService;
|
_userService = userService;
|
||||||
_config = config;
|
_config = config;
|
||||||
_localizer = localizer;
|
_localizer = localizer;
|
||||||
|
_markdownService = markdownService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
@ -163,10 +166,10 @@ namespace QRRapidoApp.Controllers
|
|||||||
var errorCode = Request.Query["code"].ToString();
|
var errorCode = Request.Query["code"].ToString();
|
||||||
var errorMessage = "";
|
var errorMessage = "";
|
||||||
|
|
||||||
// Interpretar códigos de erro específicos
|
// Interpretar c<EFBFBD>digos de erro espec<65>ficos
|
||||||
if (errorCode.StartsWith("M.C506"))
|
if (errorCode.StartsWith("M.C506"))
|
||||||
{
|
{
|
||||||
errorMessage = "Erro de autenticação. Verifique suas credenciais e tente novamente.";
|
errorMessage = "Erro de autentica<EFBFBD><EFBFBD>o. Verifique suas credenciais e tente novamente.";
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewBag.ErrorCode = errorCode;
|
ViewBag.ErrorCode = errorCode;
|
||||||
@ -204,97 +207,131 @@ namespace QRRapidoApp.Controllers
|
|||||||
|
|
||||||
// Sitemap endpoint for SEO
|
// Sitemap endpoint for SEO
|
||||||
[Route("sitemap.xml")]
|
[Route("sitemap.xml")]
|
||||||
public IActionResult Sitemap()
|
public async Task<IActionResult> Sitemap()
|
||||||
{
|
{
|
||||||
var sitemap = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
|
var baseUrl = "https://qrrapido.site";
|
||||||
<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">
|
var now = DateTime.UtcNow.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
|
var sitemapBuilder = new System.Text.StringBuilder();
|
||||||
|
sitemapBuilder.AppendLine(@"<?xml version=""1.0"" encoding=""UTF-8""?>");
|
||||||
|
sitemapBuilder.AppendLine(@"<urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9"">");
|
||||||
|
|
||||||
|
// Static pages
|
||||||
|
sitemapBuilder.AppendLine($@"
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/</loc>
|
<loc>{baseUrl}/</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>daily</changefreq>
|
<changefreq>daily</changefreq>
|
||||||
<priority>1.0</priority>
|
<priority>1.0</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/pt/</loc>
|
<loc>{baseUrl}/pt/</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>daily</changefreq>
|
<changefreq>daily</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/es/</loc>
|
<loc>{baseUrl}/es/</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>daily</changefreq>
|
<changefreq>daily</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/pt-BR/About</loc>
|
<loc>{baseUrl}/pt-BR/About</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/es-PY/About</loc>
|
<loc>{baseUrl}/es-PY/About</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/pt-BR/Contact</loc>
|
<loc>{baseUrl}/pt-BR/Contact</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/es-PY/Contact</loc>
|
<loc>{baseUrl}/es-PY/Contact</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/pt-BR/FAQ</loc>
|
<loc>{baseUrl}/pt-BR/FAQ</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/es-PY/FAQ</loc>
|
<loc>{baseUrl}/es-PY/FAQ</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.9</priority>
|
<priority>0.9</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/pt-BR/HowToUse</loc>
|
<loc>{baseUrl}/pt-BR/HowToUse</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/es-PY/HowToUse</loc>
|
<loc>{baseUrl}/es-PY/HowToUse</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/Premium/Upgrade</loc>
|
<loc>{baseUrl}/Pagamento/SelecaoPlano</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>weekly</changefreq>
|
<changefreq>weekly</changefreq>
|
||||||
<priority>0.8</priority>
|
<priority>0.8</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/privacy</loc>
|
<loc>{baseUrl}/privacy</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.5</priority>
|
<priority>0.5</priority>
|
||||||
</url>
|
</url>
|
||||||
<url>
|
<url>
|
||||||
<loc>https://qrrapido.site/terms</loc>
|
<loc>{baseUrl}/terms</loc>
|
||||||
<lastmod>{DateTime.UtcNow:yyyy-MM-dd}</lastmod>
|
<lastmod>{now}</lastmod>
|
||||||
<changefreq>monthly</changefreq>
|
<changefreq>monthly</changefreq>
|
||||||
<priority>0.5</priority>
|
<priority>0.5</priority>
|
||||||
</url>
|
</url>");
|
||||||
</urlset>";
|
|
||||||
|
|
||||||
return Content(sitemap, "application/xml");
|
// Dynamic tutorial pages
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allArticles = await _markdownService.GetAllArticlesForSitemapAsync();
|
||||||
|
|
||||||
|
foreach (var article in allArticles)
|
||||||
|
{
|
||||||
|
var slug = article.Title.ToLower().Replace(" ", "-");
|
||||||
|
var lastMod = article.LastMod.ToString("yyyy-MM-dd");
|
||||||
|
|
||||||
|
sitemapBuilder.AppendLine($@"
|
||||||
|
<url>
|
||||||
|
<loc>{baseUrl}/{article.Culture}/tutoriais/{slug}</loc>
|
||||||
|
<lastmod>{lastMod}</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Generated sitemap with {Count} tutorial articles", allArticles.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error adding tutorials to sitemap");
|
||||||
|
}
|
||||||
|
|
||||||
|
sitemapBuilder.AppendLine("</urlset>");
|
||||||
|
|
||||||
|
return Content(sitemapBuilder.ToString(), "application/xml");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -110,6 +110,36 @@ namespace QRRapidoApp.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> RequestRefund()
|
||||||
|
{
|
||||||
|
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
if (string.IsNullOrEmpty(userId))
|
||||||
|
{
|
||||||
|
return Json(new { success = false, error = "Usuário não autenticado" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (success, message) = await _stripeService.CancelAndRefundSubscriptionAsync(userId);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
TempData["Success"] = message;
|
||||||
|
return Json(new { success = true, message = message });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Json(new { success = false, error = message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Error processing refund for user {userId}");
|
||||||
|
return Json(new { success = false, error = "Erro inesperado ao processar reembolso" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> BillingPortal()
|
public async Task<IActionResult> BillingPortal()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -90,7 +90,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
return StatusCode(429, new
|
return StatusCode(429, new
|
||||||
{
|
{
|
||||||
error = _localizer["RateLimitReached"],
|
error = _localizer["RateLimitReached"],
|
||||||
upgradeUrl = "/Premium/Upgrade",
|
upgradeUrl = "/Pagamento/SelecaoPlano",
|
||||||
success = false
|
success = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -406,7 +406,7 @@ namespace QRRapidoApp.Controllers
|
|||||||
return StatusCode(429, new
|
return StatusCode(429, new
|
||||||
{
|
{
|
||||||
error = _localizer["RateLimitReached"],
|
error = _localizer["RateLimitReached"],
|
||||||
upgradeUrl = "/Premium/Upgrade",
|
upgradeUrl = "/Pagamento/SelecaoPlano",
|
||||||
success = false
|
success = false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
111
Controllers/TutoriaisController.cs
Normal file
111
Controllers/TutoriaisController.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using QRRapidoApp.Services;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Controllers
|
||||||
|
{
|
||||||
|
public class TutoriaisController : Controller
|
||||||
|
{
|
||||||
|
private readonly IMarkdownService _markdownService;
|
||||||
|
private readonly AdDisplayService _adDisplayService;
|
||||||
|
private readonly ILogger<TutoriaisController> _logger;
|
||||||
|
private readonly IStringLocalizer<QRRapidoApp.Resources.SharedResource> _localizer;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
|
||||||
|
public TutoriaisController(
|
||||||
|
IMarkdownService markdownService,
|
||||||
|
AdDisplayService adDisplayService,
|
||||||
|
ILogger<TutoriaisController> logger,
|
||||||
|
IStringLocalizer<QRRapidoApp.Resources.SharedResource> localizer,
|
||||||
|
IConfiguration config)
|
||||||
|
{
|
||||||
|
_markdownService = markdownService;
|
||||||
|
_adDisplayService = adDisplayService;
|
||||||
|
_logger = logger;
|
||||||
|
_localizer = localizer;
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("{culture:regex(^(pt-BR|es-PY)$)}/tutoriais/{slug}")]
|
||||||
|
public async Task<IActionResult> Article(string slug, string culture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var article = await _markdownService.GetArticleAsync(slug, culture);
|
||||||
|
|
||||||
|
if (article == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Article not found: {Slug} ({Culture})", slug, culture);
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ViewBag for ads and user info
|
||||||
|
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
ViewBag.ShowAds = await _adDisplayService.ShouldShowAds(userId);
|
||||||
|
ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId ?? "");
|
||||||
|
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||||
|
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||||
|
_adDisplayService.SetViewBagAds(ViewBag);
|
||||||
|
|
||||||
|
// Set SEO metadata from article
|
||||||
|
ViewBag.Title = article.Metadata.Title;
|
||||||
|
ViewBag.Description = article.Metadata.Description;
|
||||||
|
ViewBag.Keywords = article.Metadata.Keywords;
|
||||||
|
ViewBag.OgImage = article.Metadata.Image;
|
||||||
|
ViewBag.OgType = "article";
|
||||||
|
ViewBag.ArticleAuthor = article.Metadata.Author;
|
||||||
|
ViewBag.ArticlePublishedTime = article.Metadata.Date.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||||
|
ViewBag.ArticleModifiedTime = article.Metadata.LastMod.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||||
|
ViewBag.Culture = culture;
|
||||||
|
ViewBag.Slug = slug;
|
||||||
|
|
||||||
|
// Get related articles (same culture, exclude current)
|
||||||
|
var allArticles = await _markdownService.GetAllArticlesAsync(culture);
|
||||||
|
article.RelatedArticles = allArticles
|
||||||
|
.Where(a => a.Title != article.Metadata.Title)
|
||||||
|
.Take(3)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_logger.LogInformation("Serving article: {Slug} ({Culture})", slug, culture);
|
||||||
|
return View(article);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error serving article: {Slug} ({Culture})", slug, culture);
|
||||||
|
return StatusCode(500, "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Route("{culture:regex(^(pt-BR|es-PY)$)}/tutoriais")]
|
||||||
|
public async Task<IActionResult> Index(string culture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var articles = await _markdownService.GetAllArticlesAsync(culture);
|
||||||
|
|
||||||
|
// Set ViewBag
|
||||||
|
var userId = User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
ViewBag.ShowAds = await _adDisplayService.ShouldShowAds(userId);
|
||||||
|
ViewBag.IsPremium = await _adDisplayService.HasValidPremiumSubscription(userId ?? "");
|
||||||
|
ViewBag.IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||||
|
ViewBag.UserName = User.Identity?.Name ?? "";
|
||||||
|
_adDisplayService.SetViewBagAds(ViewBag);
|
||||||
|
|
||||||
|
ViewBag.Title = culture == "pt-BR" ? "Tutoriais QR Code" : "Tutoriales Código QR";
|
||||||
|
ViewBag.Description = culture == "pt-BR"
|
||||||
|
? "Aprenda a criar e usar QR Codes com nossos tutoriais completos"
|
||||||
|
: "Aprende a crear y usar códigos QR con nuestros tutoriales completos";
|
||||||
|
ViewBag.Culture = culture;
|
||||||
|
|
||||||
|
_logger.LogInformation("Serving tutorials index ({Culture}): {Count} articles", culture, articles.Count);
|
||||||
|
return View(articles);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error serving tutorials index ({Culture})", culture);
|
||||||
|
return StatusCode(500, "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Models/ArticleMetadata.cs
Normal file
15
Models/ArticleMetadata.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace QRRapidoApp.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; } = "QR Rapido";
|
||||||
|
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 int ReadingTimeMinutes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,6 +45,9 @@ namespace QRRapidoApp.Models
|
|||||||
[BsonElement("stripeSubscriptionId")]
|
[BsonElement("stripeSubscriptionId")]
|
||||||
public string? StripeSubscriptionId { get; set; }
|
public string? StripeSubscriptionId { get; set; }
|
||||||
|
|
||||||
|
[BsonElement("subscriptionStartedAt")]
|
||||||
|
public DateTime? SubscriptionStartedAt { get; set; } // Data de início da assinatura atual
|
||||||
|
|
||||||
[BsonElement("preferredLanguage")]
|
[BsonElement("preferredLanguage")]
|
||||||
public string PreferredLanguage { get; set; } = "pt-BR";
|
public string PreferredLanguage { get; set; } = "pt-BR";
|
||||||
|
|
||||||
|
|||||||
11
Models/ViewModels/ArticleViewModel.cs
Normal file
11
Models/ViewModels/ArticleViewModel.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace QRRapidoApp.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -235,6 +235,7 @@ builder.Services.Configure<RequestLocalizationOptions>(options =>
|
|||||||
builder.Services.AddScoped<IQRCodeService, QRRapidoService>();
|
builder.Services.AddScoped<IQRCodeService, QRRapidoService>();
|
||||||
builder.Services.AddScoped<IUserService, UserService>();
|
builder.Services.AddScoped<IUserService, UserService>();
|
||||||
builder.Services.AddScoped<IPlanService, QRRapidoApp.Services.PlanService>();
|
builder.Services.AddScoped<IPlanService, QRRapidoApp.Services.PlanService>();
|
||||||
|
builder.Services.AddScoped<IMarkdownService, MarkdownService>();
|
||||||
builder.Services.AddScoped<AdDisplayService>();
|
builder.Services.AddScoped<AdDisplayService>();
|
||||||
builder.Services.AddScoped<StripeService>();
|
builder.Services.AddScoped<StripeService>();
|
||||||
builder.Services.AddScoped<LogoReadabilityAnalyzer>();
|
builder.Services.AddScoped<LogoReadabilityAnalyzer>();
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:52428;http://192.168.0.85:52429;http://localhost:52429"
|
"applicationUrl": "https://localhost:52428;http://localhost:52429"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +31,8 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Localization" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Localization" Version="2.2.0" />
|
||||||
<PackageReference Include="xunit.assert" Version="2.9.3" />
|
<PackageReference Include="xunit.assert" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.extensibility.core" Version="2.9.3" />
|
<PackageReference Include="xunit.extensibility.core" Version="2.9.3" />
|
||||||
|
<PackageReference Include="Markdig" Version="0.37.0" />
|
||||||
|
<PackageReference Include="YamlDotNet" Version="16.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -924,9 +924,12 @@
|
|||||||
<value>7. Pagos y Suscripciones</value>
|
<value>7. Pagos y Suscripciones</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TermsPaymentsContent" xml:space="preserve">
|
<data name="TermsPaymentsContent" xml:space="preserve">
|
||||||
<value>• Suscripciones Premium son procesadas vía Stripe
|
<value>• Suscripciones Premium son procesadas de forma segura vía Stripe
|
||||||
• Pagos son recurrentes hasta cancelación
|
• Pagos son recurrentes mensualmente hasta cancelación
|
||||||
• Reembolsos siguen nuestra política de 7 días
|
• Puede cancelar su suscripción en cualquier momento desde su perfil, sin multas ni cargos adicionales
|
||||||
|
• Tiene derecho de retracto de 7 días para solicitar reembolso total (Ley de Defensa del Consumidor)
|
||||||
|
• Después de la cancelación, mantendrá acceso Premium hasta el final del período ya pagado
|
||||||
|
• No hay reembolso proporcional para cancelaciones después del período de 7 días
|
||||||
• Precios pueden ser alterados mediante aviso previo de 30 días</value>
|
• Precios pueden ser alterados mediante aviso previo de 30 días</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TermsLiability" xml:space="preserve">
|
<data name="TermsLiability" xml:space="preserve">
|
||||||
|
|||||||
@ -933,9 +933,12 @@
|
|||||||
<value>7. Pagos y Suscripciones</value>
|
<value>7. Pagos y Suscripciones</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TermsPaymentsContent" xml:space="preserve">
|
<data name="TermsPaymentsContent" xml:space="preserve">
|
||||||
<value>• Suscripciones Premium son procesadas vía Stripe
|
<value>• Suscripciones Premium son procesadas de forma segura vía Stripe
|
||||||
• Pagos son recurrentes hasta cancelación
|
• Pagos son recurrentes mensualmente hasta cancelación
|
||||||
• Reembolsos siguen nuestra política de 7 días
|
• Puede cancelar su suscripción en cualquier momento desde su perfil, sin multas ni cargos adicionales
|
||||||
|
• Tiene derecho de retracto de 7 días para solicitar reembolso total (Ley de Defensa del Consumidor)
|
||||||
|
• Después de la cancelación, mantendrá acceso Premium hasta el final del período ya pagado
|
||||||
|
• No hay reembolso proporcional para cancelaciones después del período de 7 días
|
||||||
• Precios pueden ser alterados mediante aviso previo de 30 días</value>
|
• Precios pueden ser alterados mediante aviso previo de 30 días</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TermsLiability" xml:space="preserve">
|
<data name="TermsLiability" xml:space="preserve">
|
||||||
|
|||||||
@ -1014,9 +1014,12 @@
|
|||||||
<value>7. Pagamentos e Assinaturas</value>
|
<value>7. Pagamentos e Assinaturas</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TermsPaymentsContent" xml:space="preserve">
|
<data name="TermsPaymentsContent" xml:space="preserve">
|
||||||
<value>• Assinaturas Premium são processadas via Stripe
|
<value>• Assinaturas Premium são processadas via Stripe de forma segura
|
||||||
• Pagamentos são recorrentes até cancelamento
|
• Pagamentos são recorrentes mensalmente até cancelamento
|
||||||
• Reembolsos seguem nossa política de 7 dias
|
• Você pode cancelar sua assinatura a qualquer momento através do seu perfil, sem multas ou taxas adicionais
|
||||||
|
• Conforme o Código de Defesa do Consumidor (CDC), você tem direito de arrependimento de 7 dias para solicitar reembolso total
|
||||||
|
• Após o cancelamento, você manterá acesso Premium até o final do período já pago
|
||||||
|
• Não há reembolso proporcional para cancelamentos após o período de 7 dias
|
||||||
• Preços podem ser alterados mediante aviso prévio de 30 dias</value>
|
• Preços podem ser alterados mediante aviso prévio de 30 dias</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TermsLiability" xml:space="preserve">
|
<data name="TermsLiability" xml:space="preserve">
|
||||||
|
|||||||
143
Scripts/README-MONGODB-SEED.md
Normal file
143
Scripts/README-MONGODB-SEED.md
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# MongoDB Plans Seed Script
|
||||||
|
|
||||||
|
## ⚠️ IMPORTANTE - LEIA ANTES DE EXECUTAR
|
||||||
|
|
||||||
|
Este script cria **2 planos** no MongoDB:
|
||||||
|
1. **Premium Mensal** - Cobrança recorrente mensal
|
||||||
|
2. **Premium Anual** - Cobrança anual com 20% de desconto
|
||||||
|
|
||||||
|
**VOCÊ PRECISA ATUALIZAR OS STRIPE PRICE IDs** antes de executar em produção!
|
||||||
|
|
||||||
|
## 📋 Pré-requisitos
|
||||||
|
|
||||||
|
### 1. Criar Produto no Stripe
|
||||||
|
|
||||||
|
1. Acesse [Stripe Dashboard](https://dashboard.stripe.com/products)
|
||||||
|
2. Crie um produto "QR Rapido Premium"
|
||||||
|
3. Anote o Product ID (ex: `prod_SnfQTxwE3i8r5L`)
|
||||||
|
|
||||||
|
### 2. Criar Price IDs no Stripe
|
||||||
|
|
||||||
|
Para **CADA PAÍS** (BR, PY, US), crie **2 preços** (mensal e anual):
|
||||||
|
|
||||||
|
#### Brasil (BRL):
|
||||||
|
- **Mensal**: R$ 9,90/mês
|
||||||
|
- No Stripe: "Premium Mensal - Brasil"
|
||||||
|
- Copie o Price ID: `price_xxxxx_monthly_br`
|
||||||
|
- **Anual**: R$ 95,04/ano (economia de 20%)
|
||||||
|
- No Stripe: "Premium Anual - Brasil"
|
||||||
|
- Copie o Price ID: `price_xxxxx_yearly_br`
|
||||||
|
|
||||||
|
#### Paraguai (PYG):
|
||||||
|
- **Mensal**: 35.000 Gs/mês
|
||||||
|
- No Stripe: "Premium Mensal - Paraguay"
|
||||||
|
- Copie o Price ID: `price_xxxxx_monthly_py`
|
||||||
|
- **Anual**: 336.000 Gs/ano (economia de 20%)
|
||||||
|
- No Stripe: "Premium Anual - Paraguay"
|
||||||
|
- Copie o Price ID: `price_xxxxx_yearly_py`
|
||||||
|
|
||||||
|
#### EUA/Internacional (USD):
|
||||||
|
- **Mensal**: $1,99/mês
|
||||||
|
- No Stripe: "Premium Monthly - USA"
|
||||||
|
- Copie o Price ID: `price_xxxxx_monthly_us`
|
||||||
|
- **Anual**: $19,10/ano (economia de 20%)
|
||||||
|
- No Stripe: "Premium Yearly - USA"
|
||||||
|
- Copie o Price ID: `price_xxxxx_yearly_us`
|
||||||
|
|
||||||
|
### 3. Atualizar appsettings.json
|
||||||
|
|
||||||
|
**Edite `appsettings.json`** (para desenvolvimento/teste):
|
||||||
|
```json
|
||||||
|
"Stripe": {
|
||||||
|
"Plans": {
|
||||||
|
"Monthly": {
|
||||||
|
"BR": "price_xxxxx_monthly_br", // ← Cole seu Price ID aqui
|
||||||
|
"PY": "price_xxxxx_monthly_py", // ← Cole seu Price ID aqui
|
||||||
|
"US": "price_xxxxx_monthly_us" // ← Cole seu Price ID aqui
|
||||||
|
},
|
||||||
|
"Yearly": {
|
||||||
|
"BR": "price_xxxxx_yearly_br", // ← Cole seu Price ID aqui
|
||||||
|
"PY": "price_xxxxx_yearly_py", // ← Cole seu Price ID aqui
|
||||||
|
"US": "price_xxxxx_yearly_us" // ← Cole seu Price ID aqui
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Edite `appsettings.Production.json`** (para produção) com os Price IDs **de produção** (mode live)
|
||||||
|
|
||||||
|
## 🚀 Como Executar
|
||||||
|
|
||||||
|
### Ambiente Local (Development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Se MongoDB estiver rodando localmente
|
||||||
|
mongosh "mongodb://localhost:27017/QrRapido" Scripts/seed-mongodb-plans.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ambiente de Produção
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Conectar ao MongoDB de produção e executar o script
|
||||||
|
mongosh "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin" Scripts/seed-mongodb-plans.js
|
||||||
|
```
|
||||||
|
|
||||||
|
**OU via SSH no servidor:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Conectar ao servidor
|
||||||
|
ssh ubuntu@141.148.162.114
|
||||||
|
|
||||||
|
# Copiar o script para o servidor
|
||||||
|
# (você pode usar scp ou copiar o conteúdo manualmente)
|
||||||
|
|
||||||
|
# Executar o script
|
||||||
|
mongosh "mongodb://admin:c4rn31r0@localhost:27017/QrRapido?authSource=admin" seed-mongodb-plans.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Verificar se Funcionou
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Conectar ao MongoDB
|
||||||
|
mongosh "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin"
|
||||||
|
|
||||||
|
# Verificar os planos inseridos
|
||||||
|
use QrRapido
|
||||||
|
db.Plans.find().pretty()
|
||||||
|
```
|
||||||
|
|
||||||
|
Você deve ver um documento com:
|
||||||
|
- `name`: Nomes em pt-BR, es-PY, en
|
||||||
|
- `stripePriceId`: O Price ID padrão do Stripe
|
||||||
|
- `pricesByCountry`: Preços por país (BR, PY, US)
|
||||||
|
- `isActive`: true
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### Erro: "Authentication failed"
|
||||||
|
- Verifique se a senha do MongoDB está correta no connection string
|
||||||
|
- Verifique se o usuário `admin` tem permissões no banco `QrRapido`
|
||||||
|
|
||||||
|
### Erro: "MongoServerError: E11000 duplicate key error"
|
||||||
|
- Já existe um plano com o mesmo ID
|
||||||
|
- Execute `db.Plans.deleteMany({})` primeiro para limpar (CUIDADO em produção!)
|
||||||
|
|
||||||
|
### Stripe retorna erro "No such price"
|
||||||
|
- Você não atualizou os `stripePriceId` no script
|
||||||
|
- Os Price IDs no script não existem no seu Stripe Dashboard
|
||||||
|
- Verifique se está usando as chaves corretas (test vs live mode)
|
||||||
|
|
||||||
|
## 📝 Próximos Passos
|
||||||
|
|
||||||
|
Após executar este script:
|
||||||
|
|
||||||
|
1. ✅ Verificar no MongoDB que o plano foi criado: `db.Plans.find()`
|
||||||
|
2. ✅ Testar o fluxo de assinatura em `/Pagamento/SelecaoPlano`
|
||||||
|
3. ✅ Confirmar que o Stripe recebe o checkout com o Price ID correto
|
||||||
|
4. ✅ Testar o webhook após pagamento bem-sucedido
|
||||||
|
|
||||||
|
## 🔗 Links Úteis
|
||||||
|
|
||||||
|
- [Stripe Dashboard - Products](https://dashboard.stripe.com/products)
|
||||||
|
- [Stripe Dashboard - Prices](https://dashboard.stripe.com/prices)
|
||||||
|
- [Stripe Webhooks](https://dashboard.stripe.com/webhooks)
|
||||||
163
Scripts/seed-mongodb-plans.js
Normal file
163
Scripts/seed-mongodb-plans.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// MongoDB Seed Script for Plans Collection
|
||||||
|
// Run this script with: mongosh "mongodb://admin:c4rn31r0@129.146.116.218:27017,141.148.162.114:27017/QrRapido?replicaSet=rs0&authSource=admin" seed-mongodb-plans.js
|
||||||
|
|
||||||
|
// Ensure we're using the correct database
|
||||||
|
db = db.getSiblingDB('QrRapido');
|
||||||
|
|
||||||
|
// Clear existing plans (optional - comment out if you want to keep existing plans)
|
||||||
|
// db.Plans.deleteMany({});
|
||||||
|
|
||||||
|
print("\n📦 Seeding Plans Collection...\n");
|
||||||
|
|
||||||
|
// 1. PLANO MENSAL
|
||||||
|
db.Plans.insertOne({
|
||||||
|
name: {
|
||||||
|
"pt-BR": "Premium Mensal",
|
||||||
|
"es-PY": "Premium Mensual",
|
||||||
|
"en": "Premium Monthly"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"pt-BR": "Acesso ilimitado a todos os recursos premium - Cobrança mensal",
|
||||||
|
"es-PY": "Acceso ilimitado a todas las funciones premium - Facturación mensual",
|
||||||
|
"en": "Unlimited access to all premium features - Monthly billing"
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
"pt-BR": [
|
||||||
|
"QR Codes ilimitados",
|
||||||
|
"Sem anúncios",
|
||||||
|
"QR Codes dinâmicos (editáveis)",
|
||||||
|
"Estatísticas avançadas",
|
||||||
|
"Suporte prioritário",
|
||||||
|
"Acesso à API",
|
||||||
|
"Cancele a qualquer momento"
|
||||||
|
],
|
||||||
|
"es-PY": [
|
||||||
|
"Códigos QR ilimitados",
|
||||||
|
"Sin anuncios",
|
||||||
|
"Códigos QR dinámicos (editables)",
|
||||||
|
"Estadísticas avanzadas",
|
||||||
|
"Soporte prioritario",
|
||||||
|
"Acceso a la API",
|
||||||
|
"Cancela cuando quieras"
|
||||||
|
],
|
||||||
|
"en": [
|
||||||
|
"Unlimited QR Codes",
|
||||||
|
"No ads",
|
||||||
|
"Dynamic QR Codes (editable)",
|
||||||
|
"Advanced analytics",
|
||||||
|
"Priority support",
|
||||||
|
"API access",
|
||||||
|
"Cancel anytime"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
interval: "month",
|
||||||
|
stripePriceId: "price_XXXXX_monthly_us", // Default price (USA) - UPDATE from appsettings.json > Stripe:Plans:Monthly:US
|
||||||
|
pricesByCountry: {
|
||||||
|
"BR": {
|
||||||
|
amount: 9.90,
|
||||||
|
currency: "BRL",
|
||||||
|
stripePriceId: "price_XXXXX_monthly_br" // UPDATE from appsettings.json > Stripe:Plans:Monthly:BR
|
||||||
|
},
|
||||||
|
"PY": {
|
||||||
|
amount: 35000,
|
||||||
|
currency: "PYG",
|
||||||
|
stripePriceId: "price_XXXXX_monthly_py" // UPDATE from appsettings.json > Stripe:Plans:Monthly:PY
|
||||||
|
},
|
||||||
|
"US": {
|
||||||
|
amount: 1.99,
|
||||||
|
currency: "USD",
|
||||||
|
stripePriceId: "price_XXXXX_monthly_us" // UPDATE from appsettings.json > Stripe:Plans:Monthly:US
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isActive: true,
|
||||||
|
displayOrder: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
print("✅ Plano Mensal inserido");
|
||||||
|
|
||||||
|
// 2. PLANO ANUAL (com desconto)
|
||||||
|
db.Plans.insertOne({
|
||||||
|
name: {
|
||||||
|
"pt-BR": "Premium Anual",
|
||||||
|
"es-PY": "Premium Anual",
|
||||||
|
"en": "Premium Yearly"
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
"pt-BR": "Acesso ilimitado a todos os recursos premium - Economia de 20% no plano anual!",
|
||||||
|
"es-PY": "Acceso ilimitado a todas las funciones premium - ¡Ahorra 20% con el plan anual!",
|
||||||
|
"en": "Unlimited access to all premium features - Save 20% with yearly billing!"
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
"pt-BR": [
|
||||||
|
"QR Codes ilimitados",
|
||||||
|
"Sem anúncios",
|
||||||
|
"QR Codes dinâmicos (editáveis)",
|
||||||
|
"Estatísticas avançadas",
|
||||||
|
"Suporte prioritário",
|
||||||
|
"Acesso à API",
|
||||||
|
"💰 Economia de 20%",
|
||||||
|
"Cobrança anual única"
|
||||||
|
],
|
||||||
|
"es-PY": [
|
||||||
|
"Códigos QR ilimitados",
|
||||||
|
"Sin anuncios",
|
||||||
|
"Códigos QR dinámicos (editables)",
|
||||||
|
"Estadísticas avanzadas",
|
||||||
|
"Soporte prioritario",
|
||||||
|
"Acceso a la API",
|
||||||
|
"💰 Ahorro del 20%",
|
||||||
|
"Facturación anual única"
|
||||||
|
],
|
||||||
|
"en": [
|
||||||
|
"Unlimited QR Codes",
|
||||||
|
"No ads",
|
||||||
|
"Dynamic QR Codes (editable)",
|
||||||
|
"Advanced analytics",
|
||||||
|
"Priority support",
|
||||||
|
"API access",
|
||||||
|
"💰 Save 20%",
|
||||||
|
"Billed annually"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
interval: "year",
|
||||||
|
stripePriceId: "price_XXXXX_yearly_us", // Default price (USA) - UPDATE from appsettings.json > Stripe:Plans:Yearly:US
|
||||||
|
pricesByCountry: {
|
||||||
|
"BR": {
|
||||||
|
amount: 95.04, // 9.90 * 12 * 0.80 = Economia de 20%
|
||||||
|
currency: "BRL",
|
||||||
|
stripePriceId: "price_XXXXX_yearly_br" // UPDATE from appsettings.json > Stripe:Plans:Yearly:BR
|
||||||
|
},
|
||||||
|
"PY": {
|
||||||
|
amount: 336000, // 35000 * 12 * 0.80 = Economia de 20%
|
||||||
|
currency: "PYG",
|
||||||
|
stripePriceId: "price_XXXXX_yearly_py" // UPDATE from appsettings.json > Stripe:Plans:Yearly:PY
|
||||||
|
},
|
||||||
|
"US": {
|
||||||
|
amount: 19.10, // 1.99 * 12 * 0.80 = Economia de 20%
|
||||||
|
currency: "USD",
|
||||||
|
stripePriceId: "price_XXXXX_yearly_us" // UPDATE from appsettings.json > Stripe:Plans:Yearly:US
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isActive: true,
|
||||||
|
displayOrder: 2,
|
||||||
|
badge: {
|
||||||
|
"pt-BR": "MELHOR VALOR",
|
||||||
|
"es-PY": "MEJOR VALOR",
|
||||||
|
"en": "BEST VALUE"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
print("✅ Plano Anual inserido");
|
||||||
|
|
||||||
|
print("\n✅ Plans collection seeded successfully!");
|
||||||
|
print("\n⚠️ PRÓXIMOS PASSOS:");
|
||||||
|
print("1. Acesse o Stripe Dashboard: https://dashboard.stripe.com/prices");
|
||||||
|
print("2. Crie os Price IDs para cada país e plano (mensal e anual)");
|
||||||
|
print("3. Copie os Price IDs (começam com 'price_')");
|
||||||
|
print("4. Atualize os valores no appsettings.json > Stripe:Plans");
|
||||||
|
print("5. Execute este script novamente após atualizar os Price IDs acima");
|
||||||
|
print("\n📊 Verificar planos inseridos:");
|
||||||
|
print(" db.Plans.find().pretty()");
|
||||||
|
print("\n🗑️ Para limpar e começar de novo:");
|
||||||
|
print(" db.Plans.deleteMany({})");
|
||||||
|
print("");
|
||||||
12
Services/IMarkdownService.cs
Normal file
12
Services/IMarkdownService.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using QRRapidoApp.Models;
|
||||||
|
using QRRapidoApp.Models.ViewModels;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Services
|
||||||
|
{
|
||||||
|
public interface IMarkdownService
|
||||||
|
{
|
||||||
|
Task<ArticleViewModel?> GetArticleAsync(string slug, string culture);
|
||||||
|
Task<List<ArticleMetadata>> GetAllArticlesAsync(string culture);
|
||||||
|
Task<List<ArticleMetadata>> GetAllArticlesForSitemapAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
265
Services/MarkdownService.cs
Normal file
265
Services/MarkdownService.cs
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
using Markdig;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using QRRapidoApp.Models;
|
||||||
|
using QRRapidoApp.Models.ViewModels;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
|
namespace QRRapidoApp.Services
|
||||||
|
{
|
||||||
|
public class MarkdownService : IMarkdownService
|
||||||
|
{
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
private readonly IWebHostEnvironment _env;
|
||||||
|
private readonly ILogger<MarkdownService> _logger;
|
||||||
|
private readonly MarkdownPipeline _pipeline;
|
||||||
|
private readonly IDeserializer _yamlDeserializer;
|
||||||
|
private const string CACHE_KEY_PREFIX = "Tutorial_";
|
||||||
|
private const string CACHE_KEY_ALL = "AllTutorials_";
|
||||||
|
private const int CACHE_DURATION_HOURS = 1;
|
||||||
|
|
||||||
|
public MarkdownService(IMemoryCache cache, IWebHostEnvironment env, ILogger<MarkdownService> logger)
|
||||||
|
{
|
||||||
|
_cache = cache;
|
||||||
|
_env = env;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
// Configure Markdig pipeline for advanced markdown features
|
||||||
|
_pipeline = new MarkdownPipelineBuilder()
|
||||||
|
.UseAdvancedExtensions()
|
||||||
|
.UseAutoLinks()
|
||||||
|
.UseEmojiAndSmiley()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Configure YAML deserializer
|
||||||
|
_yamlDeserializer = new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(CamelCaseNamingConvention.Instance)
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ArticleViewModel?> GetArticleAsync(string slug, string culture)
|
||||||
|
{
|
||||||
|
var cacheKey = $"{CACHE_KEY_PREFIX}{culture}_{slug}";
|
||||||
|
|
||||||
|
// Try get from cache
|
||||||
|
if (_cache.TryGetValue(cacheKey, out ArticleViewModel? cachedArticle))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Article served from cache: {Slug} ({Culture})", slug, culture);
|
||||||
|
return cachedArticle;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var contentPath = Path.Combine(_env.ContentRootPath, "Content", "Tutoriais");
|
||||||
|
var fileName = $"{slug}.{culture}.md";
|
||||||
|
var filePath = Path.Combine(contentPath, fileName);
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Article file not found: {FilePath}", filePath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileContent = await File.ReadAllTextAsync(filePath);
|
||||||
|
var article = ParseMarkdownWithFrontmatter(fileContent, slug);
|
||||||
|
|
||||||
|
if (article == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to parse article: {Slug} ({Culture})", slug, culture);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file metadata
|
||||||
|
var fileInfo = new FileInfo(filePath);
|
||||||
|
article.LastModified = fileInfo.LastWriteTimeUtc;
|
||||||
|
article.Metadata.Culture = culture;
|
||||||
|
|
||||||
|
// Cache the article
|
||||||
|
var cacheOptions = new MemoryCacheEntryOptions()
|
||||||
|
.SetAbsoluteExpiration(TimeSpan.FromHours(CACHE_DURATION_HOURS));
|
||||||
|
|
||||||
|
_cache.Set(cacheKey, article, cacheOptions);
|
||||||
|
|
||||||
|
_logger.LogInformation("Article loaded and cached: {Slug} ({Culture})", slug, culture);
|
||||||
|
return article;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error loading article: {Slug} ({Culture})", slug, culture);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ArticleMetadata>> GetAllArticlesAsync(string culture)
|
||||||
|
{
|
||||||
|
var cacheKey = $"{CACHE_KEY_ALL}{culture}";
|
||||||
|
|
||||||
|
// Try get from cache
|
||||||
|
if (_cache.TryGetValue(cacheKey, out List<ArticleMetadata>? cachedList))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Articles list served from cache ({Culture})", culture);
|
||||||
|
return cachedList ?? new List<ArticleMetadata>();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var articles = new List<ArticleMetadata>();
|
||||||
|
var contentPath = Path.Combine(_env.ContentRootPath, "Content", "Tutoriais");
|
||||||
|
|
||||||
|
if (!Directory.Exists(contentPath))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tutoriais directory not found: {Path}", contentPath);
|
||||||
|
return articles;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern = $"*.{culture}.md";
|
||||||
|
var files = Directory.GetFiles(contentPath, pattern);
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileContent = await File.ReadAllTextAsync(file);
|
||||||
|
var slug = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file));
|
||||||
|
var article = ParseMarkdownWithFrontmatter(fileContent, slug);
|
||||||
|
|
||||||
|
if (article?.Metadata != null)
|
||||||
|
{
|
||||||
|
article.Metadata.Culture = culture;
|
||||||
|
articles.Add(article.Metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error parsing article file: {File}", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by date (newest first)
|
||||||
|
articles = articles.OrderByDescending(a => a.Date).ToList();
|
||||||
|
|
||||||
|
// Cache the list
|
||||||
|
var cacheOptions = new MemoryCacheEntryOptions()
|
||||||
|
.SetAbsoluteExpiration(TimeSpan.FromHours(CACHE_DURATION_HOURS));
|
||||||
|
|
||||||
|
_cache.Set(cacheKey, articles, cacheOptions);
|
||||||
|
|
||||||
|
_logger.LogInformation("Loaded {Count} articles for culture {Culture}", articles.Count, culture);
|
||||||
|
return articles;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error loading articles list for culture {Culture}", culture);
|
||||||
|
return new List<ArticleMetadata>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ArticleMetadata>> GetAllArticlesForSitemapAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allArticles = new List<ArticleMetadata>();
|
||||||
|
var contentPath = Path.Combine(_env.ContentRootPath, "Content", "Tutoriais");
|
||||||
|
|
||||||
|
if (!Directory.Exists(contentPath))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Tutoriais directory not found for sitemap: {Path}", contentPath);
|
||||||
|
return allArticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = Directory.GetFiles(contentPath, "*.md");
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileContent = await File.ReadAllTextAsync(file);
|
||||||
|
var fileName = Path.GetFileName(file);
|
||||||
|
|
||||||
|
// Extract slug and culture from filename (e.g., "como-criar-qr.pt-BR.md")
|
||||||
|
var parts = fileName.Replace(".md", "").Split('.');
|
||||||
|
if (parts.Length < 2) continue;
|
||||||
|
|
||||||
|
var slug = parts[0];
|
||||||
|
var culture = parts[1];
|
||||||
|
|
||||||
|
var article = ParseMarkdownWithFrontmatter(fileContent, slug);
|
||||||
|
|
||||||
|
if (article?.Metadata != null)
|
||||||
|
{
|
||||||
|
article.Metadata.Culture = culture;
|
||||||
|
var fileInfo = new FileInfo(file);
|
||||||
|
article.Metadata.LastMod = fileInfo.LastWriteTimeUtc;
|
||||||
|
allArticles.Add(article.Metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error parsing article for sitemap: {File}", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Loaded {Count} total articles for sitemap", allArticles.Count);
|
||||||
|
return allArticles;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error loading articles for sitemap");
|
||||||
|
return new List<ArticleMetadata>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArticleViewModel? ParseMarkdownWithFrontmatter(string content, string slug)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Extract frontmatter
|
||||||
|
if (!content.StartsWith("---"))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Article missing frontmatter: {Slug}", slug);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endOfFrontmatter = content.IndexOf("---", 3);
|
||||||
|
if (endOfFrontmatter == -1)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Article frontmatter not closed: {Slug}", slug);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var frontmatterYaml = content.Substring(3, endOfFrontmatter - 3).Trim();
|
||||||
|
var markdownContent = content.Substring(endOfFrontmatter + 3).Trim();
|
||||||
|
|
||||||
|
// Parse YAML frontmatter
|
||||||
|
var metadata = _yamlDeserializer.Deserialize<ArticleMetadata>(frontmatterYaml);
|
||||||
|
|
||||||
|
if (metadata == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Failed to deserialize frontmatter: {Slug}", slug);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate reading time (average 200 words per minute)
|
||||||
|
var wordCount = markdownContent.Split(new[] { ' ', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries).Length;
|
||||||
|
metadata.ReadingTimeMinutes = Math.Max(1, (int)Math.Ceiling(wordCount / 200.0));
|
||||||
|
|
||||||
|
// Convert Markdown to HTML
|
||||||
|
var htmlContent = Markdown.ToHtml(markdownContent, _pipeline);
|
||||||
|
|
||||||
|
return new ArticleViewModel
|
||||||
|
{
|
||||||
|
Metadata = metadata,
|
||||||
|
HtmlContent = htmlContent,
|
||||||
|
Slug = slug,
|
||||||
|
LastModified = metadata.LastMod
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error parsing markdown for {Slug}", slug);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -189,5 +189,151 @@ namespace QRRapidoApp.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se a assinatura está dentro do período de 7 dias para reembolso (CDC)
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEligibleForRefund(DateTime? subscriptionStartedAt)
|
||||||
|
{
|
||||||
|
if (!subscriptionStartedAt.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var daysSinceSubscription = (DateTime.UtcNow - subscriptionStartedAt.Value).TotalDays;
|
||||||
|
return daysSinceSubscription <= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cancela assinatura E processa reembolso total (CDC - 7 dias)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(bool success, string message)> CancelAndRefundSubscriptionAsync(string userId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var user = await _userService.GetUserAsync(userId);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return (false, "Usuário não encontrado");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(user.StripeSubscriptionId))
|
||||||
|
{
|
||||||
|
return (false, "Nenhuma assinatura ativa encontrada");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica elegibilidade para reembolso
|
||||||
|
if (!IsEligibleForRefund(user.SubscriptionStartedAt))
|
||||||
|
{
|
||||||
|
var daysSince = user.SubscriptionStartedAt.HasValue
|
||||||
|
? (DateTime.UtcNow - user.SubscriptionStartedAt.Value).TotalDays
|
||||||
|
: 0;
|
||||||
|
return (false, $"Período de reembolso de 7 dias expirado (assinatura criada há {Math.Round(daysSince, 1)} dias). Você ainda pode cancelar a renovação.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Busca a assinatura no Stripe
|
||||||
|
var subscriptionService = new SubscriptionService();
|
||||||
|
var subscription = await subscriptionService.GetAsync(user.StripeSubscriptionId);
|
||||||
|
|
||||||
|
if (subscription == null)
|
||||||
|
{
|
||||||
|
return (false, "Assinatura não encontrada no Stripe");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancela a assinatura primeiro
|
||||||
|
await subscriptionService.CancelAsync(subscription.Id, new SubscriptionCancelOptions());
|
||||||
|
|
||||||
|
// Busca o último pagamento (invoice) desta assinatura para reembolsar
|
||||||
|
var invoiceService = new InvoiceService();
|
||||||
|
var invoiceListOptions = new InvoiceListOptions
|
||||||
|
{
|
||||||
|
Subscription = subscription.Id,
|
||||||
|
Limit = 1,
|
||||||
|
Status = "paid"
|
||||||
|
};
|
||||||
|
var invoices = await invoiceService.ListAsync(invoiceListOptions);
|
||||||
|
var latestInvoice = invoices.Data.FirstOrDefault();
|
||||||
|
|
||||||
|
if (latestInvoice == null || latestInvoice.AmountPaid <= 0)
|
||||||
|
{
|
||||||
|
// Mesmo sem invoice, cancela e desativa
|
||||||
|
await _userService.DeactivatePremiumStatus(subscription.Id);
|
||||||
|
return (true, "Assinatura cancelada com sucesso. Nenhum pagamento para reembolsar foi encontrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processa o reembolso - Stripe reembolsa automaticamente o último pagamento
|
||||||
|
var refundService = new RefundService();
|
||||||
|
var refundOptions = new RefundCreateOptions
|
||||||
|
{
|
||||||
|
Amount = latestInvoice.AmountPaid, // Reembolso total
|
||||||
|
Reason = RefundReasons.RequestedByCustomer,
|
||||||
|
Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "user_id", userId },
|
||||||
|
{ "subscription_id", subscription.Id },
|
||||||
|
{ "invoice_id", latestInvoice.Id },
|
||||||
|
{ "refund_reason", "CDC 7 dias - Direito de arrependimento" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stripe automaticamente encontra o charge/payment_intent correto através do subscription_id no metadata
|
||||||
|
// Alternativamente, podemos buscar o último charge da subscription
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Tenta reembolsar usando a subscription (Stripe encontra o charge automaticamente)
|
||||||
|
var chargeService = new ChargeService();
|
||||||
|
var chargeOptions = new ChargeListOptions
|
||||||
|
{
|
||||||
|
Limit = 1,
|
||||||
|
Customer = subscription.CustomerId
|
||||||
|
};
|
||||||
|
var charges = await chargeService.ListAsync(chargeOptions);
|
||||||
|
var lastCharge = charges.Data.FirstOrDefault();
|
||||||
|
|
||||||
|
if (lastCharge != null)
|
||||||
|
{
|
||||||
|
refundOptions.Charge = lastCharge.Id;
|
||||||
|
var refund = await refundService.CreateAsync(refundOptions);
|
||||||
|
|
||||||
|
if (refund.Status == "succeeded" || refund.Status == "pending")
|
||||||
|
{
|
||||||
|
// Desativa o premium imediatamente no caso de reembolso
|
||||||
|
await _userService.DeactivatePremiumStatus(subscription.Id);
|
||||||
|
|
||||||
|
_logger.LogInformation($"Successfully refunded and canceled subscription {subscription.Id} for user {userId}. Refund ID: {refund.Id}");
|
||||||
|
|
||||||
|
return (true, $"Reembolso processado com sucesso! Você receberá R$ {(latestInvoice.AmountPaid / 100.0):F2} de volta em 5-10 dias úteis.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Refund failed with status {refund.Status} for subscription {subscription.Id}");
|
||||||
|
await _userService.DeactivatePremiumStatus(subscription.Id);
|
||||||
|
return (false, "Falha ao processar reembolso, mas assinatura foi cancelada. Entre em contato com o suporte.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _userService.DeactivatePremiumStatus(subscription.Id);
|
||||||
|
return (false, "Assinatura cancelada, mas nenhuma cobrança encontrada para reembolsar. Entre em contato com o suporte.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (StripeException refundEx)
|
||||||
|
{
|
||||||
|
_logger.LogError(refundEx, $"Error creating refund for subscription {subscription.Id}");
|
||||||
|
await _userService.DeactivatePremiumStatus(subscription.Id);
|
||||||
|
return (false, $"Assinatura cancelada, mas erro ao processar reembolso: {refundEx.Message}. Entre em contato com o suporte.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (StripeException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Stripe error during refund for user {userId}: {ex.Message}");
|
||||||
|
return (false, $"Erro ao processar reembolso: {ex.StripeError?.Message ?? ex.Message}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"Error processing refund for user {userId}");
|
||||||
|
return (false, "Erro inesperado ao processar reembolso. Tente novamente mais tarde.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -408,14 +408,24 @@ namespace QRRapidoApp.Services
|
|||||||
|
|
||||||
public async Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate)
|
public async Task ActivatePremiumStatus(string userId, string stripeSubscriptionId, DateTime expiryDate)
|
||||||
{
|
{
|
||||||
var update = Builders<User>.Update
|
// Verifica se é uma nova assinatura (não renovação)
|
||||||
|
var user = await GetUserAsync(userId);
|
||||||
|
var isNewSubscription = user?.StripeSubscriptionId != stripeSubscriptionId;
|
||||||
|
|
||||||
|
var updateBuilder = Builders<User>.Update
|
||||||
.Set(u => u.IsPremium, true)
|
.Set(u => u.IsPremium, true)
|
||||||
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
|
.Set(u => u.StripeSubscriptionId, stripeSubscriptionId)
|
||||||
.Set(u => u.PremiumExpiresAt, expiryDate)
|
.Set(u => u.PremiumExpiresAt, expiryDate)
|
||||||
.Unset(u => u.PremiumCancelledAt);
|
.Unset(u => u.PremiumCancelledAt);
|
||||||
|
|
||||||
await _context.Users.UpdateOneAsync(u => u.Id == userId, update);
|
// Se é nova assinatura, atualiza a data de início (para CDC 7 dias)
|
||||||
_logger.LogInformation($"Activated premium for user {userId}");
|
if (isNewSubscription)
|
||||||
|
{
|
||||||
|
updateBuilder = updateBuilder.Set(u => u.SubscriptionStartedAt, DateTime.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.Users.UpdateOneAsync(u => u.Id == userId, updateBuilder);
|
||||||
|
_logger.LogInformation($"Activated premium for user {userId} (new subscription: {isNewSubscription})");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeactivatePremiumStatus(string stripeSubscriptionId)
|
public async Task DeactivatePremiumStatus(string stripeSubscriptionId)
|
||||||
|
|||||||
@ -40,6 +40,38 @@
|
|||||||
Expira em: @Model.PremiumExpiresAt.Value.ToString("dd/MM/yyyy")
|
Expira em: @Model.PremiumExpiresAt.Value.ToString("dd/MM/yyyy")
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.StripeSubscriptionId))
|
||||||
|
{
|
||||||
|
var canRefund = Model.SubscriptionStartedAt.HasValue &&
|
||||||
|
(DateTime.UtcNow - Model.SubscriptionStartedAt.Value).TotalDays <= 7;
|
||||||
|
var daysSinceSubscription = Model.SubscriptionStartedAt.HasValue
|
||||||
|
? Math.Round((DateTime.UtcNow - Model.SubscriptionStartedAt.Value).TotalDays, 1)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
<p class="mt-2 mb-0">
|
||||||
|
@if (canRefund)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#refundSubscriptionModal">
|
||||||
|
<i class="fas fa-undo me-1"></i>Solicitar Reembolso (CDC)
|
||||||
|
</button>
|
||||||
|
<small class="text-muted d-block mt-1">
|
||||||
|
<i class="fas fa-info-circle"></i> Você tem direito a reembolso total (7 dias)
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#cancelSubscriptionModal">
|
||||||
|
<i class="fas fa-times-circle me-1"></i>Cancelar Renovação
|
||||||
|
</button>
|
||||||
|
@if (daysSinceSubscription > 0)
|
||||||
|
{
|
||||||
|
<small class="text-muted d-block mt-1">
|
||||||
|
<i class="fas fa-info-circle"></i> Assinatura há @daysSinceSubscription dias (período de reembolso expirado)
|
||||||
|
</small>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -47,7 +79,7 @@
|
|||||||
<i class="fas fa-user me-1"></i>Gratuito
|
<i class="fas fa-user me-1"></i>Gratuito
|
||||||
</span>
|
</span>
|
||||||
<p class="text-muted small mt-1 mb-0">
|
<p class="text-muted small mt-1 mb-0">
|
||||||
<a href="/Premium/Upgrade" class="text-decoration-none">
|
<a href="/Pagamento/SelecaoPlano" class="text-decoration-none">
|
||||||
<i class="fas fa-arrow-up me-1"></i>Fazer upgrade
|
<i class="fas fa-arrow-up me-1"></i>Fazer upgrade
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@ -163,7 +195,7 @@
|
|||||||
@if (!isPremium)
|
@if (!isPremium)
|
||||||
{
|
{
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<a href="/Premium/Upgrade" class="btn btn-warning w-100">
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning w-100">
|
||||||
<i class="fas fa-crown me-2"></i>Upgrade para Premium
|
<i class="fas fa-crown me-2"></i>Upgrade para Premium
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -195,6 +227,80 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de Solicitação de Reembolso (CDC - 7 dias) -->
|
||||||
|
<div class="modal fade" id="refundSubscriptionModal" tabindex="-1" aria-labelledby="refundSubscriptionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h5 class="modal-title" id="refundSubscriptionModalLabel">
|
||||||
|
<i class="fas fa-undo me-2"></i>Solicitar Reembolso Total (CDC)
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fechar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-shield-alt me-2"></i>
|
||||||
|
<strong>Direito de Arrependimento (CDC):</strong> Você está dentro do período de 7 dias e tem direito a reembolso total conforme o Código de Defesa do Consumidor.
|
||||||
|
</div>
|
||||||
|
<h6><strong>O que acontecerá:</strong></h6>
|
||||||
|
<ul>
|
||||||
|
<li><i class="fas fa-check text-success me-2"></i>Reembolso total do valor pago</li>
|
||||||
|
<li><i class="fas fa-check text-success me-2"></i>Assinatura cancelada imediatamente</li>
|
||||||
|
<li><i class="fas fa-check text-success me-2"></i>Acesso Premium removido</li>
|
||||||
|
<li><i class="fas fa-check text-success me-2"></i>Dinheiro devolvido em 5-10 dias úteis</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mb-0"><strong>Deseja continuar com o reembolso?</strong></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>Cancelar
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmRefundBtn">
|
||||||
|
<i class="fas fa-undo me-1"></i>Sim, Solicitar Reembolso
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de Confirmação de Cancelamento -->
|
||||||
|
<div class="modal fade" id="cancelSubscriptionModal" tabindex="-1" aria-labelledby="cancelSubscriptionModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h5 class="modal-title" id="cancelSubscriptionModalLabel">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>Cancelar Assinatura Premium
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Fechar"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>
|
||||||
|
<strong>Atenção:</strong> Você manterá acesso aos recursos Premium até o final do período já pago.
|
||||||
|
</div>
|
||||||
|
<p><strong>Ao cancelar, você perderá:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><i class="fas fa-times text-danger me-2"></i>QR Codes ilimitados</li>
|
||||||
|
<li><i class="fas fa-times text-danger me-2"></i>Experiência sem anúncios</li>
|
||||||
|
<li><i class="fas fa-times text-danger me-2"></i>QR Codes dinâmicos (editáveis)</li>
|
||||||
|
<li><i class="fas fa-times text-danger me-2"></i>Estatísticas avançadas</li>
|
||||||
|
<li><i class="fas fa-times text-danger me-2"></i>Suporte prioritário</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mb-0"><strong>Tem certeza que deseja cancelar?</strong></p>
|
||||||
|
<p class="text-muted small">Você pode reativar sua assinatura a qualquer momento.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i>Manter Premium
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmCancelBtn">
|
||||||
|
<i class="fas fa-times-circle me-1"></i>Sim, Cancelar Assinatura
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.card {
|
.card {
|
||||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||||
@ -263,3 +369,103 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Handler para reembolso (CDC - 7 dias)
|
||||||
|
const confirmRefundBtn = document.getElementById('confirmRefundBtn');
|
||||||
|
const refundModal = document.getElementById('refundSubscriptionModal');
|
||||||
|
|
||||||
|
if (confirmRefundBtn) {
|
||||||
|
confirmRefundBtn.addEventListener('click', async function() {
|
||||||
|
// Desabilita o botão durante o processamento
|
||||||
|
confirmRefundBtn.disabled = true;
|
||||||
|
confirmRefundBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Processando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/Premium/RequestRefund', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Fecha o modal
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(refundModal);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mostra mensagem de sucesso
|
||||||
|
alert('✅ ' + result.message);
|
||||||
|
|
||||||
|
// Recarrega a página após 2 segundos
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
alert('❌ ' + result.error);
|
||||||
|
confirmRefundBtn.disabled = false;
|
||||||
|
confirmRefundBtn.innerHTML = '<i class="fas fa-undo me-1"></i>Sim, Solicitar Reembolso';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao solicitar reembolso:', error);
|
||||||
|
alert('❌ Erro de conexão. Tente novamente mais tarde.');
|
||||||
|
confirmRefundBtn.disabled = false;
|
||||||
|
confirmRefundBtn.innerHTML = '<i class="fas fa-undo me-1"></i>Sim, Solicitar Reembolso';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler para cancelamento (sem reembolso)
|
||||||
|
const confirmCancelBtn = document.getElementById('confirmCancelBtn');
|
||||||
|
const cancelModal = document.getElementById('cancelSubscriptionModal');
|
||||||
|
|
||||||
|
if (confirmCancelBtn) {
|
||||||
|
confirmCancelBtn.addEventListener('click', async function() {
|
||||||
|
// Desabilita o botão durante o processamento
|
||||||
|
confirmCancelBtn.disabled = true;
|
||||||
|
confirmCancelBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Cancelando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/Premium/CancelSubscription', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Fecha o modal
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(cancelModal);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mostra mensagem de sucesso
|
||||||
|
alert('✅ Assinatura cancelada com sucesso!\n\nVocê manterá acesso Premium até o final do período pago.\n\nA página será recarregada.');
|
||||||
|
|
||||||
|
// Recarrega a página após 2 segundos
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
alert('❌ Erro ao cancelar assinatura: ' + (result.error || 'Erro desconhecido'));
|
||||||
|
confirmCancelBtn.disabled = false;
|
||||||
|
confirmCancelBtn.innerHTML = '<i class="fas fa-times-circle me-1"></i>Sim, Cancelar Assinatura';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao cancelar assinatura:', error);
|
||||||
|
alert('❌ Erro de conexão. Tente novamente mais tarde.');
|
||||||
|
confirmCancelBtn.disabled = false;
|
||||||
|
confirmCancelBtn.innerHTML = '<i class="fas fa-times-circle me-1"></i>Sim, Cancelar Assinatura';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1198,7 +1198,7 @@
|
|||||||
<li><i class="fas fa-check text-success"></i> @Localizer["DeveloperAPI"]</li>
|
<li><i class="fas fa-check text-success"></i> @Localizer["DeveloperAPI"]</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a href="/Premium/Upgrade" class="btn btn-warning w-100">
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning w-100">
|
||||||
<i class="fas fa-bolt"></i> @Localizer["AcceleratePrice"]
|
<i class="fas fa-bolt"></i> @Localizer["AcceleratePrice"]
|
||||||
</a>
|
</a>
|
||||||
<small class="text-muted d-block mt-1">@Localizer["CancelAnytime"]</small>
|
<small class="text-muted d-block mt-1">@Localizer["CancelAnytime"]</small>
|
||||||
|
|||||||
@ -1,343 +0,0 @@
|
|||||||
@model QRRapidoApp.Models.ViewModels.UpgradeViewModel
|
|
||||||
@using Microsoft.Extensions.Localization
|
|
||||||
@inject IStringLocalizer<QRRapidoApp.Resources.SharedResource> Localizer
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "QR Rapido Premium";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<!-- Hero Section -->
|
|
||||||
<div class="text-center mb-5">
|
|
||||||
<h1 class="display-4 text-gradient">
|
|
||||||
<i class="fas fa-rocket"></i> QR Rapido Premium
|
|
||||||
</h1>
|
|
||||||
<p class="lead text-muted">
|
|
||||||
@Localizer["PremiumAccelerateProductivity"]
|
|
||||||
</p>
|
|
||||||
<div class="badge bg-success fs-6 p-2">
|
|
||||||
<i class="fas fa-bolt"></i> @Localizer["ThreeTimesFaster"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Current Status -->
|
|
||||||
@if (Model.IsAdFreeActive)
|
|
||||||
{
|
|
||||||
<div class="alert alert-info border-0 shadow-sm mb-4">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<h6><i class="fas fa-info-circle"></i> @Localizer["CurrentStatus"]</h6>
|
|
||||||
<p class="mb-0">
|
|
||||||
@Localizer["YouHave"] <strong>@Model.DaysUntilAdExpiry @Localizer["DaysRemainingNoAds"]</strong>
|
|
||||||
@Localizer["UpgradeNowForever"]
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 text-end">
|
|
||||||
<div class="badge bg-success p-2">
|
|
||||||
@Model.DaysUntilAdExpiry @Localizer["DaysRemaining"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<!-- Pricing Card -->
|
|
||||||
<div class="row justify-content-center mb-5">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card shadow-lg border-warning">
|
|
||||||
<div class="card-header bg-warning text-dark text-center">
|
|
||||||
<h3 class="mb-0">
|
|
||||||
<i class="fas fa-crown"></i> QR Rapido Premium
|
|
||||||
</h3>
|
|
||||||
<small>@Localizer["MostPopularPlan"]</small>
|
|
||||||
</div>
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<div class="display-3 text-warning fw-bold mb-2">
|
|
||||||
R$ @Model.PremiumPrice.ToString("0.00")
|
|
||||||
</div>
|
|
||||||
<p class="text-muted">@Localizer["PerMonth"]</p>
|
|
||||||
|
|
||||||
<div class="list-group list-group-flush mb-4">
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-infinity text-success me-2"></i>
|
|
||||||
<strong>@Localizer["UnlimitedQRCodes"]</strong>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-bolt text-success me-2"></i>
|
|
||||||
<strong>@Localizer["UltraFastGeneration04s"]</strong>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-ban text-success me-2"></i>
|
|
||||||
<strong>@Localizer["NoAdsForever"]</strong>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-magic text-success me-2"></i>
|
|
||||||
<strong>@Localizer["DynamicQRCodes"]</strong>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-chart-line text-success me-2"></i>
|
|
||||||
<strong>@Localizer["RealTimeAnalytics"]</strong>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-headset text-success me-2"></i>
|
|
||||||
<strong>@Localizer["PrioritySupport"]</strong>
|
|
||||||
</div>
|
|
||||||
<div class="list-group-item border-0">
|
|
||||||
<i class="fas fa-code text-success me-2"></i>
|
|
||||||
<strong>@Localizer["DeveloperAPI"]</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="upgrade-btn" class="btn btn-warning btn-lg w-100 mb-3">
|
|
||||||
<i class="fas fa-rocket"></i> @Localizer["UpgradeNowButton"]
|
|
||||||
<div class="spinner-border spinner-border-sm ms-2 d-none" role="status"></div>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<small class="text-muted">
|
|
||||||
<i class="fas fa-shield-alt"></i> @Localizer["SecurePaymentStripe"]
|
|
||||||
<br>
|
|
||||||
<i class="fas fa-times-circle"></i> @Localizer["CancelAnytime"]
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Feature Comparison -->
|
|
||||||
<div class="card shadow-sm mb-5">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4 class="mb-0">
|
|
||||||
<i class="fas fa-balance-scale"></i> @Localizer["PlanComparison"]
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>@Localizer["Feature"]</th>
|
|
||||||
<th class="text-center">Free</th>
|
|
||||||
<th class="text-center bg-warning text-dark">Premium</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>@Localizer["QRCodesPerDay"]</td>
|
|
||||||
<td class="text-center">50</td>
|
|
||||||
<td class="text-center"><i class="fas fa-infinity text-success"></i> @Localizer["Unlimited"]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>@Localizer["GenerationSpeed"]</td>
|
|
||||||
<td class="text-center">1.2s</td>
|
|
||||||
<td class="text-center"><strong class="text-success">0.4s</strong></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>@Localizer["Ads"]</td>
|
|
||||||
<td class="text-center"><i class="fas fa-times text-danger"></i></td>
|
|
||||||
<td class="text-center"><i class="fas fa-check text-success"></i> @Localizer["NoAds"]</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>@Localizer["DynamicQRCodes"]</td>
|
|
||||||
<td class="text-center"><i class="fas fa-times text-danger"></i></td>
|
|
||||||
<td class="text-center"><i class="fas fa-check text-success"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>@Localizer["DetailedAnalytics"]</td>
|
|
||||||
<td class="text-center"><i class="fas fa-times text-danger"></i></td>
|
|
||||||
<td class="text-center"><i class="fas fa-check text-success"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>@Localizer["PrioritySupport"]</td>
|
|
||||||
<td class="text-center"><i class="fas fa-times text-danger"></i></td>
|
|
||||||
<td class="text-center"><i class="fas fa-check text-success"></i></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>API access</td>
|
|
||||||
<td class="text-center"><i class="fas fa-times text-danger"></i></td>
|
|
||||||
<td class="text-center"><i class="fas fa-check text-success"></i></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Speed Demonstration -->
|
|
||||||
<div class="card shadow-sm mb-5">
|
|
||||||
<div class="card-header bg-primary text-white">
|
|
||||||
<h4 class="mb-0">
|
|
||||||
<i class="fas fa-stopwatch"></i> @Localizer["SpeedDemonstration"]
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row text-center">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card border-danger">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="text-danger">@Localizer["Competitors"]</h5>
|
|
||||||
<div class="display-4 text-danger">4.5s</div>
|
|
||||||
<p class="text-muted">@Localizer["AverageTime"]</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card border-primary">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="text-primary">QR Rapido Free</h5>
|
|
||||||
<div class="display-4 text-primary">1.2s</div>
|
|
||||||
<p class="text-muted">@Localizer["ThreeTimesFaster"]</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card border-success">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="text-success">QR Rapido Premium</h5>
|
|
||||||
<div class="display-4 text-success">0.4s</div>
|
|
||||||
<p class="text-muted">@Localizer["ElevenTimesFaster"]</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FAQ -->
|
|
||||||
<div class="card shadow-sm">
|
|
||||||
<div class="card-header">
|
|
||||||
<h4 class="mb-0">
|
|
||||||
<i class="fas fa-question-circle"></i> @Localizer["FAQ"]
|
|
||||||
</h4>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="accordion" id="faqAccordion">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#faq1">
|
|
||||||
@Localizer["CanCancelAnytime"]
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="faq1" class="accordion-collapse collapse show">
|
|
||||||
<div class="accordion-body">
|
|
||||||
@Localizer["CancelAnytimeAnswer"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq2">
|
|
||||||
@Localizer["WhatAreDynamicQR"]
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="faq2" class="accordion-collapse collapse">
|
|
||||||
<div class="accordion-body">
|
|
||||||
@Localizer["DynamicQRAnswer"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header">
|
|
||||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#faq3">
|
|
||||||
@Localizer["HowPrioritySupport"]
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="faq3" class="accordion-collapse collapse">
|
|
||||||
<div class="accordion-body">
|
|
||||||
@Localizer["PrioritySupportAnswer"]
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<script>
|
|
||||||
document.getElementById('upgrade-btn').addEventListener('click', async function() {
|
|
||||||
const btn = this;
|
|
||||||
const spinner = btn.querySelector('.spinner-border');
|
|
||||||
|
|
||||||
btn.disabled = true;
|
|
||||||
spinner.classList.remove('d-none');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/Premium/CreateCheckout', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
// Track conversion attempt
|
|
||||||
if (typeof gtag !== 'undefined') {
|
|
||||||
gtag('event', 'begin_checkout', {
|
|
||||||
'event_category': 'Premium',
|
|
||||||
'value': @Model.PremiumPrice,
|
|
||||||
'currency': 'BRL'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.href = result.url;
|
|
||||||
} else {
|
|
||||||
showToast('@Localizer["PaymentProcessingError"]' + result.error, 'danger');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Erro:', error);
|
|
||||||
showToast('@Localizer["PaymentErrorTryAgain"]', 'danger');
|
|
||||||
} finally {
|
|
||||||
btn.disabled = false;
|
|
||||||
spinner.classList.add('d-none');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Track page view
|
|
||||||
if (typeof gtag !== 'undefined') {
|
|
||||||
gtag('event', 'page_view', {
|
|
||||||
'page_title': 'Premium Upgrade',
|
|
||||||
'page_location': window.location.href
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toast notification function
|
|
||||||
function showToast(message, type) {
|
|
||||||
// Create toast container if doesn't exist
|
|
||||||
let toastContainer = document.getElementById('toast-container');
|
|
||||||
if (!toastContainer) {
|
|
||||||
toastContainer = document.createElement('div');
|
|
||||||
toastContainer.id = 'toast-container';
|
|
||||||
toastContainer.className = 'toast-container position-fixed top-0 start-0 p-3';
|
|
||||||
toastContainer.style.zIndex = '1060';
|
|
||||||
toastContainer.style.marginTop = '80px';
|
|
||||||
document.body.appendChild(toastContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create toast element
|
|
||||||
const toastId = 'toast-' + Date.now();
|
|
||||||
const toastElement = document.createElement('div');
|
|
||||||
toastElement.innerHTML = `
|
|
||||||
<div class="toast align-items-center text-white bg-${type} border-0" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="toast-body">${message}</div>
|
|
||||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
toastContainer.appendChild(toastElement);
|
|
||||||
const toast = new bootstrap.Toast(toastElement.querySelector('.toast'), { delay: 5000 });
|
|
||||||
toast.show();
|
|
||||||
|
|
||||||
// Remove toast element after it's hidden
|
|
||||||
toastElement.querySelector('.toast').addEventListener('hidden.bs.toast', function() {
|
|
||||||
toastElement.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
@ -95,7 +95,7 @@ else if (User.Identity.IsAuthenticated)
|
|||||||
<div class="alert alert-info upgrade-notice mb-3">
|
<div class="alert alert-info upgrade-notice mb-3">
|
||||||
<i class="fas fa-star text-warning"></i>
|
<i class="fas fa-star text-warning"></i>
|
||||||
<span><strong>@Localizer["UpgradePremiumRemoveAds"]</strong></span>
|
<span><strong>@Localizer["UpgradePremiumRemoveAds"]</strong></span>
|
||||||
<a href="/Premium/Upgrade" class="btn btn-sm btn-warning ms-2">
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-sm btn-warning ms-2">
|
||||||
<i class="fas fa-crown"></i> @Localizer["PremiumBenefitsShort"]
|
<i class="fas fa-crown"></i> @Localizer["PremiumBenefitsShort"]
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -289,7 +289,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<li><a class="dropdown-item text-warning" href="/Premium/Upgrade">
|
<li><a class="dropdown-item text-warning" href="/Pagamento/SelecaoPlano">
|
||||||
<i class="fas fa-rocket"></i> QR Rapido Premium
|
<i class="fas fa-rocket"></i> QR Rapido Premium
|
||||||
</a></li>
|
</a></li>
|
||||||
}
|
}
|
||||||
|
|||||||
290
Views/Tutoriais/Article.cshtml
Normal file
290
Views/Tutoriais/Article.cshtml
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
@model QRRapidoApp.Models.ViewModels.ArticleViewModel
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = Model.Metadata.Title;
|
||||||
|
var baseUrl = "https://qrrapido.site";
|
||||||
|
var articleUrl = $"{baseUrl}/{ViewBag.Culture}/tutoriais/{ViewBag.Slug}";
|
||||||
|
}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="@ViewBag.Culture">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@Model.Metadata.Title - QR Rapido</title>
|
||||||
|
|
||||||
|
<!-- SEO Meta Tags -->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!-- Canonical URL -->
|
||||||
|
<link rel="canonical" href="@articleUrl">
|
||||||
|
|
||||||
|
<!-- Open Graph / Facebook -->
|
||||||
|
<meta property="og:type" content="article">
|
||||||
|
<meta property="og:url" content="@articleUrl">
|
||||||
|
<meta property="og:title" content="@Model.Metadata.Title">
|
||||||
|
<meta property="og:description" content="@Model.Metadata.Description">
|
||||||
|
<meta property="og:image" content="@baseUrl@Model.Metadata.Image">
|
||||||
|
<meta property="og:site_name" content="QR Rapido">
|
||||||
|
<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 -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:url" content="@articleUrl">
|
||||||
|
<meta name="twitter:title" content="@Model.Metadata.Title">
|
||||||
|
<meta name="twitter:description" content="@Model.Metadata.Description">
|
||||||
|
<meta name="twitter:image" content="@baseUrl@Model.Metadata.Image">
|
||||||
|
|
||||||
|
<!-- Schema.org Article Structured Data -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@@context": "https://schema.org",
|
||||||
|
"@@type": "Article",
|
||||||
|
"headline": "@Model.Metadata.Title",
|
||||||
|
"description": "@Model.Metadata.Description",
|
||||||
|
"image": "@baseUrl@Model.Metadata.Image",
|
||||||
|
"author": {
|
||||||
|
"@@type": "Person",
|
||||||
|
"name": "@Model.Metadata.Author"
|
||||||
|
},
|
||||||
|
"publisher": {
|
||||||
|
"@@type": "Organization",
|
||||||
|
"name": "QR Rapido",
|
||||||
|
"logo": {
|
||||||
|
"@@type": "ImageObject",
|
||||||
|
"url": "@baseUrl/images/logo.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"datePublished": "@Model.Metadata.Date.ToString("yyyy-MM-dd")",
|
||||||
|
"dateModified": "@Model.Metadata.LastMod.ToString("yyyy-MM-dd")",
|
||||||
|
"mainEntityOfPage": {
|
||||||
|
"@@type": "WebPage",
|
||||||
|
"@@id": "@articleUrl"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Breadcrumb Schema -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@@context": "https://schema.org",
|
||||||
|
"@@type": "BreadcrumbList",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@@type": "ListItem",
|
||||||
|
"position": 1,
|
||||||
|
"name": "Home",
|
||||||
|
"item": "@baseUrl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@@type": "ListItem",
|
||||||
|
"position": 2,
|
||||||
|
"name": "@(ViewBag.Culture == "pt-BR" ? "Tutoriais" : "Tutoriales")",
|
||||||
|
"item": "@baseUrl/@ViewBag.Culture/tutoriais"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@@type": "ListItem",
|
||||||
|
"position": 3,
|
||||||
|
"name": "@Model.Metadata.Title",
|
||||||
|
"item": "@articleUrl"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-4 mb-5">
|
||||||
|
<!-- Breadcrumbs -->
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="/@ViewBag.Culture">Home</a></li>
|
||||||
|
<li class="breadcrumb-item"><a href="/@ViewBag.Culture/tutoriais">@(ViewBag.Culture == "pt-BR" ? "Tutoriais" : "Tutoriales")</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">@Model.Metadata.Title</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Main Article Content -->
|
||||||
|
<article class="col-lg-8">
|
||||||
|
<!-- Article Header -->
|
||||||
|
<header class="mb-4">
|
||||||
|
<h1 class="display-4 mb-3">@Model.Metadata.Title</h1>
|
||||||
|
|
||||||
|
<div class="text-muted mb-3">
|
||||||
|
<span><i class="fas fa-user"></i> @Model.Metadata.Author</span> |
|
||||||
|
<span><i class="fas fa-calendar"></i> @Model.Metadata.Date.ToString("dd MMM yyyy")</span> |
|
||||||
|
<span><i class="fas fa-clock"></i> @Model.Metadata.ReadingTimeMinutes min @(ViewBag.Culture == "pt-BR" ? "de leitura" : "de lectura")</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.Metadata.Image))
|
||||||
|
{
|
||||||
|
<img src="@Model.Metadata.Image" alt="@Model.Metadata.Title" class="img-fluid rounded mb-4" />
|
||||||
|
}
|
||||||
|
|
||||||
|
<p class="lead">@Model.Metadata.Description</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Article Body -->
|
||||||
|
<div class="article-content">
|
||||||
|
@Html.Raw(Model.HtmlContent)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Article Footer -->
|
||||||
|
<footer class="mt-5 pt-4 border-top">
|
||||||
|
<p class="text-muted">
|
||||||
|
<small>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Última atualização:" : "Última actualización:")
|
||||||
|
@Model.Metadata.LastMod.ToString("dd MMM yyyy HH:mm")
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Ad Slot (if ads enabled) -->
|
||||||
|
@if (ViewBag.ShowAds == true)
|
||||||
|
{
|
||||||
|
<div class="my-4">
|
||||||
|
<div class="alert alert-light text-center" role="alert">
|
||||||
|
<!-- Ad placement -->
|
||||||
|
<ins class="adsbygoogle"
|
||||||
|
style="display:block"
|
||||||
|
data-ad-client="@ViewBag.AdSenseClientId"
|
||||||
|
data-ad-slot="@ViewBag.ArticleAdSlot"
|
||||||
|
data-ad-format="auto"
|
||||||
|
data-full-width-responsive="true"></ins>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="col-lg-4">
|
||||||
|
<!-- Related Articles -->
|
||||||
|
@if (Model.RelatedArticles.Any())
|
||||||
|
{
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Artigos Relacionados" : "Artículos Relacionados")
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
@foreach (var related in Model.RelatedArticles)
|
||||||
|
{
|
||||||
|
<a href="/@ViewBag.Culture/tutoriais/@related.Title.ToLower().Replace(" ", "-")"
|
||||||
|
class="list-group-item list-group-item-action">
|
||||||
|
<h6 class="mb-1">@related.Title</h6>
|
||||||
|
<p class="mb-1 text-muted small">@related.Description</p>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- CTA Premium (if not premium) -->
|
||||||
|
@if (ViewBag.IsPremium != true)
|
||||||
|
{
|
||||||
|
<div class="card bg-primary text-white mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
<i class="fas fa-crown"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Seja Premium!" : "¡Hazte Premium!")
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
@(ViewBag.Culture == "pt-BR"
|
||||||
|
? "QR codes ilimitados, sem anúncios e recursos avançados."
|
||||||
|
: "Códigos QR ilimitados, sin anuncios y características avanzadas.")
|
||||||
|
</p>
|
||||||
|
<a href="/@ViewBag.Culture/Pagamento/SelecaoPlano" class="btn btn-light btn-block">
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Conhecer Planos" : "Conocer Planes")
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Quick Links -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="fas fa-link"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Links Úteis" : "Enlaces Útiles")
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
<a href="/@ViewBag.Culture" class="list-group-item list-group-item-action">
|
||||||
|
<i class="fas fa-qrcode"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Gerar QR Code" : "Generar Código QR")
|
||||||
|
</a>
|
||||||
|
<a href="/@ViewBag.Culture/FAQ" class="list-group-item list-group-item-action">
|
||||||
|
<i class="fas fa-question-circle"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Perguntas Frequentes" : "Preguntas Frecuentes")
|
||||||
|
</a>
|
||||||
|
<a href="/@ViewBag.Culture/Contact" class="list-group-item list-group-item-action">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Contato" : "Contacto")
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.article-content {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content h2 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content h3 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content pre {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content code {
|
||||||
|
background: #f4f4f4;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content blockquote {
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
padding-left: 1rem;
|
||||||
|
margin-left: 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
109
Views/Tutoriais/Index.cshtml
Normal file
109
Views/Tutoriais/Index.cshtml
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
@model List<QRRapidoApp.Models.ArticleMetadata>
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = ViewBag.Culture == "pt-BR" ? "Tutoriais QR Code" : "Tutoriales Código QR";
|
||||||
|
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container mt-4 mb-5">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<h1 class="display-4">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Tutoriais QR Code" : "Tutoriales Código QR")
|
||||||
|
</h1>
|
||||||
|
<p class="lead text-muted">
|
||||||
|
@(ViewBag.Culture == "pt-BR"
|
||||||
|
? "Aprenda tudo sobre QR Codes com nossos tutoriais completos e passo a passo"
|
||||||
|
: "Aprende todo sobre códigos QR con nuestros tutoriales completos paso a paso")
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tutorials Grid -->
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
@foreach (var article in Model)
|
||||||
|
{
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="card h-100 shadow-sm hover-shadow">
|
||||||
|
@if (!string.IsNullOrEmpty(article.Image))
|
||||||
|
{
|
||||||
|
<img src="@article.Image" class="card-img-top" alt="@article.Title" style="height: 200px; object-fit: cover;">
|
||||||
|
}
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<h5 class="card-title">@article.Title</h5>
|
||||||
|
<p class="card-text text-muted flex-grow-1">@article.Description</p>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-calendar"></i> @article.Date.ToString("dd MMM yyyy") |
|
||||||
|
<i class="fas fa-clock"></i> @article.ReadingTimeMinutes min
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/@ViewBag.Culture/tutoriais/@article.Title.ToLower().Replace(" ", "-")"
|
||||||
|
class="btn btn-primary btn-block">
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Ler Tutorial" : "Leer Tutorial")
|
||||||
|
<i class="fas fa-arrow-right ml-1"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR"
|
||||||
|
? "Nenhum tutorial disponível no momento. Volte em breve!"
|
||||||
|
: "No hay tutoriales disponibles en este momento. ¡Vuelve pronto!")
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- CTA Section -->
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card bg-primary text-white">
|
||||||
|
<div class="card-body text-center py-5">
|
||||||
|
<h3 class="mb-3">
|
||||||
|
@(ViewBag.Culture == "pt-BR"
|
||||||
|
? "Pronto para criar seu QR Code?"
|
||||||
|
: "¿Listo para crear tu código QR?")
|
||||||
|
</h3>
|
||||||
|
<p class="lead mb-4">
|
||||||
|
@(ViewBag.Culture == "pt-BR"
|
||||||
|
? "Gere QR codes profissionais em segundos com nossa ferramenta ultrarrápida!"
|
||||||
|
: "¡Genera códigos QR profesionales en segundos con nuestra herramienta ultrarrápida!")
|
||||||
|
</p>
|
||||||
|
<a href="/@ViewBag.Culture" class="btn btn-light btn-lg">
|
||||||
|
<i class="fas fa-qrcode"></i>
|
||||||
|
@(ViewBag.Culture == "pt-BR" ? "Criar QR Code Agora" : "Crear Código QR Ahora")
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.hover-shadow {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hover-shadow:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover .card-img-top {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,6 +18,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Stripe": {
|
||||||
|
"PublishableKey": "pk_live_XXXXX",
|
||||||
|
"SecretKey": "sk_live_XXXXX",
|
||||||
|
"WebhookSecret": "whsec_live_XXXXX",
|
||||||
|
"ProductId": "prod_SnfQTxwE3i8r5L",
|
||||||
|
"Plans": {
|
||||||
|
"Monthly": {
|
||||||
|
"BR": "price_XXXXX_monthly_br_PROD",
|
||||||
|
"PY": "price_XXXXX_monthly_py_PROD",
|
||||||
|
"US": "price_XXXXX_monthly_us_PROD"
|
||||||
|
},
|
||||||
|
"Yearly": {
|
||||||
|
"BR": "price_XXXXX_yearly_br_PROD",
|
||||||
|
"PY": "price_XXXXX_yearly_py_PROD",
|
||||||
|
"US": "price_XXXXX_yearly_us_PROD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ResourceMonitoring": {
|
"ResourceMonitoring": {
|
||||||
"Enabled": true,
|
"Enabled": true,
|
||||||
"IntervalSeconds": 30,
|
"IntervalSeconds": 30,
|
||||||
|
|||||||
@ -24,7 +24,19 @@
|
|||||||
"PublishableKey": "pk_test_51Rs42tBeR5IFYUsBooapyDwQTgh6CFuKbya5R3MVDTrdOUKmgiHQYipU0pgOdG5iKogH77RUYIKBJzbCt5BghUOY00xitV5KiN",
|
"PublishableKey": "pk_test_51Rs42tBeR5IFYUsBooapyDwQTgh6CFuKbya5R3MVDTrdOUKmgiHQYipU0pgOdG5iKogH77RUYIKBJzbCt5BghUOY00xitV5KiN",
|
||||||
"SecretKey": "sk_test_51Rs42tBeR5IFYUsBtycRlJJcdwgoMbh8MfQIKIGelBPTQFwDcOn2iCCbw5uG6hnqlpgNAUuFgWRAUUMA8qkABKun00EIx4odDF",
|
"SecretKey": "sk_test_51Rs42tBeR5IFYUsBtycRlJJcdwgoMbh8MfQIKIGelBPTQFwDcOn2iCCbw5uG6hnqlpgNAUuFgWRAUUMA8qkABKun00EIx4odDF",
|
||||||
"WebhookSecret": "whsec_2e828803ceb48e7865458b0cf332b68535fdff8753d26d69b1c88ea55cb0e482",
|
"WebhookSecret": "whsec_2e828803ceb48e7865458b0cf332b68535fdff8753d26d69b1c88ea55cb0e482",
|
||||||
"PriceId": "prod_SnfQTxwE3i8r5L"
|
"ProductId": "prod_SnfQTxwE3i8r5L",
|
||||||
|
"Plans": {
|
||||||
|
"Monthly": {
|
||||||
|
"BR": "price_1Rs45OBeR5IFYUsBfsnOpOiv",
|
||||||
|
"PY": "price_XXXXX_monthly_py",
|
||||||
|
"US": "price_XXXXX_monthly_us"
|
||||||
|
},
|
||||||
|
"Yearly": {
|
||||||
|
"BR": "price_1Rs4AyBeR5IFYUsB8kRSNUIM",
|
||||||
|
"PY": "price_XXXXX_yearly_py",
|
||||||
|
"US": "price_XXXXX_yearly_us"
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"AdSense": {
|
"AdSense": {
|
||||||
"ClientId": "ca-pub-3475956393038764",
|
"ClientId": "ca-pub-3475956393038764",
|
||||||
|
|||||||
BIN
wwwroot/images/tutoriais/qr-code-corretor-imoveis-hero.jpg
Normal file
BIN
wwwroot/images/tutoriais/qr-code-corretor-imoveis-hero.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 503 KiB |
BIN
wwwroot/images/tutoriais/qr-code-wifi-hero.jpg
Normal file
BIN
wwwroot/images/tutoriais/qr-code-wifi-hero.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
BIN
wwwroot/images/tutoriais/whatsapp-qr-hero.jpg
Normal file
BIN
wwwroot/images/tutoriais/whatsapp-qr-hero.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 356 KiB |
@ -1489,7 +1489,7 @@ class QRRapidoGenerator {
|
|||||||
<i class="fas fa-crown text-warning"></i>
|
<i class="fas fa-crown text-warning"></i>
|
||||||
<strong>Sessão sem anúncios ativa!</strong>
|
<strong>Sessão sem anúncios ativa!</strong>
|
||||||
Tempo restante: <span class="ad-free-countdown">${this.formatTime(timeRemaining)}</span>
|
Tempo restante: <span class="ad-free-countdown">${this.formatTime(timeRemaining)}</span>
|
||||||
<a href="/Premium/Upgrade" class="btn btn-sm btn-warning ms-2">Tornar Permanente</a>
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-sm btn-warning ms-2">Tornar Permanente</a>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const container = document.querySelector('.container');
|
const container = document.querySelector('.container');
|
||||||
@ -1548,7 +1548,7 @@ class QRRapidoGenerator {
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
<a href="/Premium/Upgrade" class="btn btn-warning">
|
<a href="/Pagamento/SelecaoPlano" class="btn btn-warning">
|
||||||
<i class="fas fa-crown"></i> Fazer Upgrade
|
<i class="fas fa-crown"></i> Fazer Upgrade
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user