Jubilar Django: borrar el portal antiguo, la web ya la sirve Next
El cutover se hizo hoy: Caddy manda www.nightspire.gg a :3001 (Next), así que Django se quedó sin dominio y sin uso. Se comprobó antes de borrar que nada dependía de él: ninguna referencia a :8001 en Caddy, el timer de reconciliación llama a Next, DJANGO_API_BASE no lo leía ni una línea del código (se quita también de la unidad de systemd), web-next/public es autónomo (736 ficheros reales, 0 symlinks) y no hay ni una referencia a /static/. Con Django parado la web siguió dando 200 en todas las rutas. Se va: home/, novawow/, forum/, wotlk_db/, frontend/ (las islas React que Next sustituyó), static/, staticfiles/, manage.py, requirements.txt, db.sqlite3 y el Docker de Django. Se quedan docs/ y sql/: no son código Django sino documentación y esquemas, y sql/forum_schema.sql describe la BD acore_web que Next usa hoy. La BASE DE DATOS no se toca. django_wow y sus 41 tablas home_* son ahora de Next, que las consulta con SQL directo. El nombre se queda por historia. README reescrito: describía cómo montar un Django que ya no existe (venv, manage.py migrate, gunicorn, Docker). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
# ac_soap.py
|
||||
import requests
|
||||
import base64
|
||||
from django.conf import settings
|
||||
|
||||
def execute_soap_command(command):
|
||||
"""
|
||||
Ejecuta un comando en el servidor AzerothCore usando SOAP.
|
||||
|
||||
Parameters:
|
||||
- command (str): El comando a ejecutar, por ejemplo, ".additem 3200"
|
||||
|
||||
Returns:
|
||||
- str: La respuesta del servidor si el comando se ejecuta correctamente.
|
||||
- None: Si ocurre algún error durante la ejecución.
|
||||
"""
|
||||
try:
|
||||
# Crear la carga SOAP
|
||||
soap_command = f'''<?xml version="1.0" encoding="utf-8"?>
|
||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="{settings.AC_SOAP_URN}">
|
||||
<SOAP-ENV:Body>
|
||||
<ns1:executeCommand>
|
||||
<command>{command}</command>
|
||||
</ns1:executeCommand>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>'''
|
||||
|
||||
# Autenticación básica usando las credenciales en settings.py
|
||||
auth_header = f"Basic {base64.b64encode(f'{settings.AC_SOAP_USER}:{settings.AC_SOAP_PASSWORD}'.encode()).decode()}"
|
||||
|
||||
headers = {
|
||||
'Authorization': auth_header,
|
||||
'Content-Type': 'application/xml'
|
||||
}
|
||||
|
||||
# Realizar la solicitud SOAP al servidor de AzerothCore
|
||||
response = requests.post(settings.AC_SOAP_URL, data=soap_command, headers=headers, timeout=5)
|
||||
|
||||
# Verificar si la respuesta fue exitosa
|
||||
if response.status_code == 200:
|
||||
return response.text
|
||||
else:
|
||||
print(f"[ERROR] Comando SOAP fallido: {response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"[EXCEPCIÓN] Error de conexión con el servidor SOAP: {str(e)}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"[EXCEPCIÓN] Error inesperado al ejecutar el comando SOAP: {str(e)}")
|
||||
return None
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
# home/admin.py
|
||||
from django.contrib import admin
|
||||
from django import forms
|
||||
from .models import (
|
||||
Noticia, ClienteCategoria, SistemaOperativo, ServerSelection,
|
||||
RecruitAFriend, DownloadClientPage, ContentCreator, RecruitReward, GuildRenameSettings, VoteSite, RenamePrice, CustomizePrice, ChangeRacePrice, ChangeFactionPrice, LevelUpPrice, GoldPrice, TransferPrice, MaintenanceMode,
|
||||
StripeLog, Pedido
|
||||
)
|
||||
from django.db import connections
|
||||
import logging
|
||||
from django.shortcuts import redirect
|
||||
from django_ckeditor_5.widgets import CKEditor5Widget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@admin.register(StripeLog)
|
||||
class StripeLogAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'account_id', 'username', 'email', 'acore_ip', 'stripe_ip', 'product_name',
|
||||
'amount', 'session_id', 'mode', 'character_name', 'timestamp'
|
||||
) # Añadido stripe_ip
|
||||
list_filter = ('mode', 'timestamp') # Filtros por campo en la parte lateral
|
||||
search_fields = ('username', 'product_name', 'session_id', 'email', 'stripe_ip') # Ahora incluye 'stripe_ip' en la búsqueda
|
||||
ordering = ('-timestamp',) # Ordenar por timestamp descendentes
|
||||
|
||||
@admin.register(Pedido)
|
||||
class PedidoLogAdmin(admin.ModelAdmin): # Ahora esta es una clase de admin
|
||||
list_display = ('transaction_id', 'email_cliente', 'estado', 'monto') # Campos que se mostrarán en el panel
|
||||
search_fields = ('transaction_id', 'email_cliente') # Habilita búsqueda por ID y email
|
||||
list_filter = ('estado',) # Filtro por estado de pago
|
||||
|
||||
|
||||
@admin.register(ChangeFactionPrice)
|
||||
class ChangeFactionPriceAdmin(admin.ModelAdmin):
|
||||
list_display = ('price',)
|
||||
|
||||
|
||||
@admin.register(MaintenanceMode)
|
||||
class MaintenanceModeAdmin(admin.ModelAdmin):
|
||||
list_display = ('is_active',)
|
||||
|
||||
@admin.register(GoldPrice)
|
||||
class GoldPriceAdmin(admin.ModelAdmin):
|
||||
list_display = ('gold_amount', 'price')
|
||||
ordering = ('gold_amount',)
|
||||
|
||||
|
||||
@admin.register(TransferPrice)
|
||||
class TransferPriceAdmin(admin.ModelAdmin):
|
||||
list_display = ('price',)
|
||||
|
||||
@admin.register(LevelUpPrice)
|
||||
class LevelUpPriceAdmin(admin.ModelAdmin):
|
||||
list_display = ('price',)
|
||||
|
||||
# Formulario para el modelo Noticia
|
||||
class NoticiaAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Noticia
|
||||
fields = ['titulo', 'contenido', 'fecha_fin_evento', 'enlace']
|
||||
widgets = {
|
||||
'contenido': CKEditor5Widget(config_name='default'),
|
||||
}
|
||||
|
||||
@admin.register(RenamePrice)
|
||||
class RenamePriceAdmin(admin.ModelAdmin):
|
||||
list_display = ("price",)
|
||||
|
||||
|
||||
@admin.register(CustomizePrice)
|
||||
class CustomizePriceAdmin(admin.ModelAdmin):
|
||||
list_display = ("price",)
|
||||
|
||||
@admin.register(ChangeRacePrice)
|
||||
class ChangeRacePriceAdmin(admin.ModelAdmin):
|
||||
list_display = ('price',)
|
||||
|
||||
@admin.register(Noticia)
|
||||
class NoticiaAdmin(admin.ModelAdmin):
|
||||
form = NoticiaAdminForm
|
||||
list_display = ['titulo', 'fecha_publicacion']
|
||||
readonly_fields = ['fecha_publicacion']
|
||||
fields = ['titulo', 'contenido', 'fecha_fin_evento', 'enlace']
|
||||
|
||||
|
||||
# Formulario para el modelo RecruitAFriend
|
||||
class RecruitAFriendAdminForm(forms.ModelForm):
|
||||
content = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
|
||||
class Meta:
|
||||
model = RecruitAFriend
|
||||
fields = ['title', 'information', 'content']
|
||||
|
||||
@admin.register(RecruitAFriend)
|
||||
class RecruitAFriendAdmin(admin.ModelAdmin):
|
||||
form = RecruitAFriendAdminForm
|
||||
list_display = ['title', 'updated_at']
|
||||
fields = ['title', 'information', 'content']
|
||||
|
||||
|
||||
# Formulario para el modelo DownloadClientPage
|
||||
class DownloadClientPageAdminForm(forms.ModelForm):
|
||||
content = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
mac_instructions = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
|
||||
class Meta:
|
||||
model = DownloadClientPage
|
||||
fields = ['title', 'content', 'mac_instructions']
|
||||
|
||||
@admin.register(DownloadClientPage)
|
||||
class DownloadClientPageAdmin(admin.ModelAdmin):
|
||||
form = DownloadClientPageAdminForm
|
||||
list_display = ['title', 'updated_at']
|
||||
fields = ['title', 'content', 'mac_instructions']
|
||||
|
||||
|
||||
# Formulario para el modelo ContentCreator
|
||||
class ContentCreatorAdminForm(forms.ModelForm):
|
||||
description = forms.CharField(widget=CKEditor5Widget(config_name='default'))
|
||||
|
||||
class Meta:
|
||||
model = ContentCreator
|
||||
fields = [
|
||||
'title', 'description', 'content_type', 'subtitle',
|
||||
'youtube_video_url', 'avatar_image_url',
|
||||
'youtube_channel_url', 'facebook_page_url'
|
||||
]
|
||||
|
||||
@admin.register(ContentCreator)
|
||||
class ContentCreatorAdmin(admin.ModelAdmin):
|
||||
form = ContentCreatorAdminForm
|
||||
list_display = ['title', 'updated_at']
|
||||
fields = [
|
||||
'title', 'description', 'content_type', 'subtitle',
|
||||
'youtube_video_url', 'avatar_image_url',
|
||||
'youtube_channel_url', 'facebook_page_url'
|
||||
]
|
||||
|
||||
|
||||
# Administración para ServerSelection
|
||||
class ServerSelectionAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'address', 'port', 'gamebuild']
|
||||
change_list_template = "admin/server_selection.html"
|
||||
|
||||
def changelist_view(self, request, extra_context=None):
|
||||
servers = []
|
||||
try:
|
||||
with connections['acore_auth'].cursor() as cursor:
|
||||
cursor.execute("SELECT id, name, address, port, gamebuild FROM realmlist")
|
||||
servers = cursor.fetchall()
|
||||
except Exception as e:
|
||||
logger.error("Error al obtener servidores: %s", e)
|
||||
|
||||
if request.method == "POST" and "server_selection" in request.POST:
|
||||
selected_server_id = int(request.POST["server_selection"])
|
||||
selected_server = next((s for s in servers if s[0] == selected_server_id), None)
|
||||
if selected_server:
|
||||
ServerSelection.objects.all().delete()
|
||||
ServerSelection.objects.create(
|
||||
server_id=selected_server[0],
|
||||
name=selected_server[1],
|
||||
address=selected_server[2],
|
||||
port=selected_server[3],
|
||||
gamebuild=selected_server[4]
|
||||
)
|
||||
return redirect("admin:home_serverselection_changelist")
|
||||
|
||||
extra_context = extra_context or {}
|
||||
extra_context['servers'] = servers
|
||||
return super().changelist_view(request, extra_context=extra_context)
|
||||
|
||||
|
||||
# Inlines para ClienteCategoria
|
||||
class SistemaOperativoInline(admin.TabularInline):
|
||||
model = SistemaOperativo
|
||||
extra = 1
|
||||
|
||||
class ClienteCategoriaAdmin(admin.ModelAdmin):
|
||||
inlines = [SistemaOperativoInline]
|
||||
|
||||
|
||||
@admin.register(RecruitReward)
|
||||
class RecruitRewardAdmin(admin.ModelAdmin):
|
||||
list_display = ('required_friends', 'reward_name', 'item_id', 'item_quantity', 'icon_class')
|
||||
search_fields = ('reward_name',)
|
||||
|
||||
@admin.register(GuildRenameSettings)
|
||||
class GuildRenameSettingsAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'cost') # Asegúrate de incluir 'id' antes de 'cost'
|
||||
list_display_links = ('id',) # Permite hacer clic en 'id' para editar
|
||||
list_editable = ('cost',) # Ahora 'cost' es editable en línea
|
||||
|
||||
@admin.register(VoteSite)
|
||||
class VoteSiteAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'points', 'url', 'image_url')
|
||||
search_fields = ('name',)
|
||||
list_editable = ('points',)
|
||||
list_display_links = ('name',)
|
||||
|
||||
|
||||
# Registro de modelos en el admin
|
||||
admin.site.register(ClienteCategoria, ClienteCategoriaAdmin)
|
||||
admin.site.register(SistemaOperativo)
|
||||
admin.site.register(ServerSelection, ServerSelectionAdmin)
|
||||
@@ -1,10 +0,0 @@
|
||||
"""Rutas de la API JSON (islas React). Se montan bajo /api/ fuera de i18n_patterns."""
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('home/news/', views.home_news_api, name='api_home_news'),
|
||||
path('home/status/', views.home_status_api, name='api_home_status'),
|
||||
path('account/select/', views.account_select_api, name='api_account_select'),
|
||||
path('account/me/', views.account_me_api, name='api_account_me'),
|
||||
]
|
||||
@@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'home'
|
||||
-216
@@ -1,216 +0,0 @@
|
||||
"""
|
||||
Utilidades de autenticación para servidores 3.4.3 (TrinityCore) con cuentas
|
||||
Battle.net.
|
||||
|
||||
Dos algoritmos SRP6 distintos conviven:
|
||||
|
||||
* ``battlenet_accounts`` -> SRP6 **v2**: PBKDF2-HMAC-SHA512, N de 2048 bits,
|
||||
g = 2. El "usuario" SRP es HEX(SHA256(UPPER(email))). Es el que usa el
|
||||
cliente 3.4.3 para conectarse (login por email).
|
||||
|
||||
* ``account`` (cuenta de juego) -> SRP6 **Grunt/SHA1**: el clásico de
|
||||
AzerothCore/3.3.5a. Cada cuenta bnet tiene 1..N cuentas de juego con
|
||||
username ``<bnetId>#<index>``.
|
||||
|
||||
Referencias del propio source 3.4.3:
|
||||
src/common/Cryptography/Authentication/SRP6.cpp (CalculateX / CalculateVerifier)
|
||||
src/server/game/Accounts/BattlenetAccountMgr.cpp (CreateBattlenetAccount)
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SRP6 v2 (battlenet_accounts) — PBKDF2-HMAC-SHA512
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# BnetSRP6v2Base::N (2048 bits) y g, tal cual en SRP6.cpp
|
||||
_BNET_N = int(
|
||||
"AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050"
|
||||
"A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50"
|
||||
"E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B8"
|
||||
"55F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773B"
|
||||
"CA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748"
|
||||
"544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6"
|
||||
"AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6"
|
||||
"94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73",
|
||||
16,
|
||||
)
|
||||
_BNET_g = 2
|
||||
_BNET_SALT_LEN = 32
|
||||
_BNET_ITERATIONS = 15000 # GetXIterations() para v2
|
||||
_BNET_SRP_VERSION = 2
|
||||
_TWO_POW_512 = 1 << 512
|
||||
|
||||
# TrinityCore almacena el verifier con BigNumber::ToByteVector(), que por
|
||||
# defecto es little-endian. Si al comparar contra una cuenta creada por el
|
||||
# propio servidor (`.bnetaccount create`) el login del CLIENTE fallara, prueba a
|
||||
# poner esto en False (big-endian). El login del PORTAL funciona en cualquier
|
||||
# caso porque registra y verifica con el mismo criterio.
|
||||
_BNET_VERIFIER_LITTLE_ENDIAN = True
|
||||
|
||||
|
||||
def bnet_srp_username(email):
|
||||
"""SRP username de bnet = HEX(SHA256(UPPER(email))) en mayúsculas."""
|
||||
email_up = email.strip().upper()
|
||||
return hashlib.sha256(email_up.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
def _bnet_calculate_x(email, password, salt):
|
||||
"""Replica BnetSRP6v2Base::CalculateX (SRP6.cpp)."""
|
||||
srp_user = bnet_srp_username(email)
|
||||
tmp = (srp_user + ":" + password).encode("utf-8")
|
||||
x_bytes = hashlib.pbkdf2_hmac("sha512", tmp, salt, _BNET_ITERATIONS, dklen=64)
|
||||
x = int.from_bytes(x_bytes, "big")
|
||||
if x_bytes[0] & 0x80: # si el bit alto está puesto, resta 2^512
|
||||
x -= _TWO_POW_512
|
||||
return x % (_BNET_N - 1)
|
||||
|
||||
|
||||
def _int_to_verifier_bytes(value):
|
||||
"""Emula BigNumber::ToByteVector(): longitud mínima, endianness configurable."""
|
||||
length = max(1, (value.bit_length() + 7) // 8)
|
||||
order = "little" if _BNET_VERIFIER_LITTLE_ENDIAN else "big"
|
||||
return value.to_bytes(length, order)
|
||||
|
||||
|
||||
def _verifier_bytes_to_int(data):
|
||||
order = "little" if _BNET_VERIFIER_LITTLE_ENDIAN else "big"
|
||||
return int.from_bytes(data, order)
|
||||
|
||||
|
||||
def bnet_make_registration(email, password):
|
||||
"""
|
||||
Genera (salt, verifier, srp_version) para una nueva cuenta Battle.net.
|
||||
|
||||
salt: 32 bytes aleatorios (se guarda tal cual).
|
||||
verifier: g^x mod N en longitud mínima (como TrinityCore::ToByteVector).
|
||||
"""
|
||||
salt = os.urandom(_BNET_SALT_LEN)
|
||||
x = _bnet_calculate_x(email, password, salt)
|
||||
v = pow(_BNET_g, x, _BNET_N)
|
||||
return salt, _int_to_verifier_bytes(v), _BNET_SRP_VERSION
|
||||
|
||||
|
||||
def bnet_verify(email, password, salt, stored_verifier):
|
||||
"""Comprueba la contraseña de una cuenta Battle.net (login por email)."""
|
||||
if salt is None or stored_verifier is None:
|
||||
return False
|
||||
if isinstance(salt, memoryview):
|
||||
salt = bytes(salt)
|
||||
if isinstance(stored_verifier, memoryview):
|
||||
stored_verifier = bytes(stored_verifier)
|
||||
x = _bnet_calculate_x(email, password, salt)
|
||||
v = pow(_BNET_g, x, _BNET_N)
|
||||
# Comparación numérica (robusta frente a padding).
|
||||
return v == _verifier_bytes_to_int(stored_verifier)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SRP6 Grunt/SHA1 (cuenta de juego `account`) — el clásico de 3.3.5a
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_GRUNT_g = 7
|
||||
_GRUNT_N = int("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7", 16)
|
||||
_GRUNT_SALT_LEN = 32
|
||||
|
||||
|
||||
def _sha1(data):
|
||||
return hashlib.sha1(data).digest()
|
||||
|
||||
|
||||
def game_calculate_verifier(username, password, salt):
|
||||
"""
|
||||
SRP6 Grunt (SHA1) para la tabla `account`. ``username`` y ``password`` se
|
||||
pasan en mayúsculas (TrinityCore hace Utf8ToUpperOnlyLatin en ambos).
|
||||
Devuelve el verifier en bytes little-endian (32).
|
||||
"""
|
||||
if isinstance(salt, memoryview):
|
||||
salt = bytes(salt)
|
||||
h1 = _sha1((username.upper() + ":" + password.upper()).encode("utf-8"))
|
||||
h2 = _sha1(salt + h1)
|
||||
h2_int = int.from_bytes(h2, "little")
|
||||
verifier = pow(_GRUNT_g, h2_int, _GRUNT_N)
|
||||
return verifier.to_bytes(32, "little")
|
||||
|
||||
|
||||
def game_make_registration(username, password):
|
||||
"""Genera (salt, verifier) Grunt para una cuenta de juego nueva."""
|
||||
salt = os.urandom(_GRUNT_SALT_LEN)
|
||||
verifier = game_calculate_verifier(username, password[:16], salt)
|
||||
return salt, verifier
|
||||
|
||||
|
||||
def game_verify(username, password, salt, stored_verifier):
|
||||
"""Comprueba la contraseña de una cuenta de juego (SRP6 Grunt)."""
|
||||
if salt is None or stored_verifier is None:
|
||||
return False
|
||||
if isinstance(stored_verifier, memoryview):
|
||||
stored_verifier = bytes(stored_verifier)
|
||||
calc = game_calculate_verifier(username, password[:16], salt)
|
||||
return int.from_bytes(calc, "little") == int.from_bytes(stored_verifier, "little")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers de acceso a la BD auth (bnet <-> cuentas de juego)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def normalize_email(email):
|
||||
"""El email en battlenet_accounts se almacena en MAYÚSCULAS."""
|
||||
return email.strip().upper()
|
||||
|
||||
|
||||
def get_bnet_account_by_email(cursor, email):
|
||||
"""Devuelve dict {id, email, srp_version, salt, verifier} o None."""
|
||||
cursor.execute(
|
||||
"SELECT id, email, srp_version, salt, verifier "
|
||||
"FROM battlenet_accounts WHERE email = %s",
|
||||
[normalize_email(email)],
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
return {
|
||||
"id": row[0],
|
||||
"email": row[1],
|
||||
"srp_version": row[2],
|
||||
"salt": row[3],
|
||||
"verifier": row[4],
|
||||
}
|
||||
|
||||
|
||||
def get_game_accounts_for_bnet(cursor, bnet_id):
|
||||
"""Lista de cuentas de juego de una cuenta bnet: [{id, username, index}]."""
|
||||
cursor.execute(
|
||||
"SELECT id, username, battlenet_index "
|
||||
"FROM account WHERE battlenet_account = %s ORDER BY battlenet_index",
|
||||
[bnet_id],
|
||||
)
|
||||
return [{"id": r[0], "username": r[1], "index": r[2]} for r in cursor.fetchall()]
|
||||
|
||||
|
||||
def get_next_battlenet_index(cursor, bnet_id):
|
||||
"""Siguiente battlenet_index libre para una cuenta bnet (1-based)."""
|
||||
cursor.execute(
|
||||
"SELECT MAX(battlenet_index) FROM account WHERE battlenet_account = %s",
|
||||
[bnet_id],
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
return (row[0] or 0) + 1
|
||||
|
||||
|
||||
def make_game_account_username(bnet_id, index=1):
|
||||
"""Nombre de la cuenta de juego: ``<bnetId>#<index>`` (como TrinityCore)."""
|
||||
return f"{bnet_id}#{index}"
|
||||
|
||||
|
||||
def get_battlepay_credits(cursor, bnet_id):
|
||||
"""Créditos Battlepay de la cuenta bnet (0 si la columna no existe)."""
|
||||
if not bnet_id:
|
||||
return 0
|
||||
try:
|
||||
cursor.execute("SELECT battlePayCredits FROM battlenet_accounts WHERE id = %s", [bnet_id])
|
||||
row = cursor.fetchone()
|
||||
return row[0] if row and row[0] is not None else 0
|
||||
except Exception:
|
||||
return 0
|
||||
@@ -1,40 +0,0 @@
|
||||
from django.conf import settings
|
||||
from .models import ServerSelection
|
||||
from django.db import connections
|
||||
import socket
|
||||
|
||||
def url_principal(request):
|
||||
"""Devuelve la URL principal del sitio."""
|
||||
current_domain = f"{request.scheme}://{request.get_host()}"
|
||||
return {'URL_PRINCIPAL': current_domain}
|
||||
|
||||
def configuracion_global(request):
|
||||
"""Devuelve configuraciones globales como el nombre del servidor y el año actual."""
|
||||
return {
|
||||
'NOMBRE_SERVIDOR': settings.NOMBRE_SERVIDOR,
|
||||
'ANIO_ACTUAL': settings.ANIO_ACTUAL,
|
||||
'TURNSTILE_SITE_KEY': settings.TURNSTILE_SITE_KEY,
|
||||
}
|
||||
|
||||
def get_server_info(request):
|
||||
"""Devuelve información del servidor para que esté disponible en todas las plantillas."""
|
||||
server_selection = ServerSelection.objects.first()
|
||||
|
||||
online_count = 0
|
||||
if server_selection:
|
||||
# Obtener la cantidad de personajes en línea
|
||||
try:
|
||||
with connections['acore_characters'].cursor() as cursor:
|
||||
cursor.execute("SELECT COUNT(*) FROM characters WHERE online = 1")
|
||||
result = cursor.fetchone()
|
||||
if result is not None:
|
||||
online_count = result[0]
|
||||
except Exception as e:
|
||||
# Manejar errores de conexión o consulta
|
||||
print(f"Error al obtener personajes en línea: {e}")
|
||||
|
||||
return {
|
||||
'server': server_selection,
|
||||
'online_characters': online_count,
|
||||
'expansion': server_selection.expansion if server_selection else 'Unknown',
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import os
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import strip_tags
|
||||
|
||||
def enviar_correo(subject, to_email, template, context):
|
||||
"""
|
||||
Envía un correo electrónico utilizando una plantilla HTML.
|
||||
|
||||
Args:
|
||||
subject (str): El asunto del correo.
|
||||
to_email (str): Dirección de correo del destinatario.
|
||||
template (str): Ruta de la plantilla HTML.
|
||||
context (dict): Contexto para renderizar la plantilla.
|
||||
"""
|
||||
try:
|
||||
# Renderizar el contenido del correo desde una plantilla
|
||||
html_message = render_to_string(template, context)
|
||||
plain_message = strip_tags(html_message)
|
||||
from_email = settings.DEFAULT_FROM_EMAIL
|
||||
|
||||
# Enviar el correo
|
||||
send_mail(
|
||||
subject,
|
||||
plain_message,
|
||||
from_email,
|
||||
[to_email],
|
||||
html_message=html_message,
|
||||
fail_silently=False,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error al enviar el correo: {e}")
|
||||
return False
|
||||
@@ -1,28 +0,0 @@
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import resolve, reverse
|
||||
from .models import MaintenanceMode
|
||||
|
||||
class MaintenanceMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
# Excluir todas las rutas bajo /admin y la propia página de mantenimiento
|
||||
excluded_prefixes = [
|
||||
reverse('admin:index').rstrip('/'), # Asegura que todas las rutas bajo /admin estén excluidas
|
||||
reverse('maintenance').rstrip('/'), # Página de mantenimiento
|
||||
]
|
||||
|
||||
# Verificar si la ruta actual está en los prefijos excluidos
|
||||
if any(request.path.startswith(prefix) for prefix in excluded_prefixes):
|
||||
return self.get_response(request)
|
||||
|
||||
# Verificar si el modo de mantenimiento está activo
|
||||
try:
|
||||
maintenance = MaintenanceMode.objects.first()
|
||||
if maintenance and maintenance.is_active:
|
||||
return redirect('maintenance')
|
||||
except MaintenanceMode.DoesNotExist:
|
||||
pass
|
||||
|
||||
return self.get_response(request)
|
||||
@@ -1,114 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-11 16:11
|
||||
|
||||
import django.db.models.deletion
|
||||
import django_ckeditor_5.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ClienteCategoria',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nombre', models.CharField(max_length=100)),
|
||||
('descripcion', models.CharField(blank=True, max_length=200, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentCreator',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('description', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Descripción')),
|
||||
('content_type', models.CharField(default='Contenido general', max_length=60)),
|
||||
('subtitle', models.CharField(default='Nova WoW', max_length=60)),
|
||||
('youtube_video_url', models.URLField(blank=True, max_length=300, null=True)),
|
||||
('avatar_image_url', models.URLField(blank=True, max_length=300, null=True)),
|
||||
('youtube_channel_url', models.URLField(blank=True, max_length=300, null=True)),
|
||||
('facebook_page_url', models.URLField(blank=True, max_length=300, null=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DownloadClientPage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(default='Descarga del cliente', max_length=255)),
|
||||
('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Contenido')),
|
||||
('mac_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True, verbose_name='Instrucciones para macOS')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Noticia',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('titulo', models.CharField(max_length=200)),
|
||||
('contenido', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Contenido')),
|
||||
('fecha_publicacion', models.DateTimeField(auto_now=True)),
|
||||
('fecha_fin_evento', models.DateField(blank=True, null=True)),
|
||||
('enlace', models.URLField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecruitAFriend',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(default='Recluta a un amigo', max_length=255)),
|
||||
('information', models.CharField(default='Información', max_length=255)),
|
||||
('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Contenido')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecruitReward',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('required_friends', models.IntegerField()),
|
||||
('reward_name', models.CharField(max_length=100)),
|
||||
('item_id', models.IntegerField()),
|
||||
('item_quantity', models.IntegerField(default=1)),
|
||||
('item_link', models.URLField()),
|
||||
('icon_class', models.CharField(help_text="Clase CSS para el icono (ej. 'icontinyl q3')", max_length=50)),
|
||||
('claimed_by', models.CharField(blank=True, max_length=100, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ServerSelection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('server_id', models.IntegerField()),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('address', models.CharField(max_length=100)),
|
||||
('port', models.IntegerField()),
|
||||
('gamebuild', models.IntegerField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClaimedReward',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('account_id', models.IntegerField()),
|
||||
('character_name', models.CharField(max_length=100)),
|
||||
('username', models.CharField(max_length=100)),
|
||||
('claimed_at', models.DateTimeField(auto_now_add=True)),
|
||||
('recruit_reward', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='claimed_rewards', to='home.recruitreward')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SistemaOperativo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nombre', models.CharField(max_length=100)),
|
||||
('url_descarga', models.URLField()),
|
||||
('categoria', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sistemas_operativos', to='home.clientecategoria')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,37 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-12 12:56
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AccountActivation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('username', models.CharField(max_length=17)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('password', models.CharField(max_length=64)),
|
||||
('salt', models.CharField(max_length=64)),
|
||||
('verifier', models.CharField(max_length=128)),
|
||||
('recruiter_id', models.IntegerField(blank=True, null=True)),
|
||||
('hash', models.CharField(max_length=32, unique=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='recruitreward',
|
||||
name='claimed_by',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='claimedreward',
|
||||
name='ip_address',
|
||||
field=models.CharField(default='127.0.0.1', max_length=45),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-12 13:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0002_accountactivation_remove_recruitreward_claimed_by_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='salt',
|
||||
field=models.CharField(max_length=128),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-12 14:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0003_alter_accountactivation_salt'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='salt',
|
||||
field=models.BinaryField(max_length=32),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='verifier',
|
||||
field=models.BinaryField(max_length=32),
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-11-13 10:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0004_alter_accountactivation_salt_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SecurityToken',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_id', models.IntegerField()),
|
||||
('token', models.CharField(max_length=6)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('expires_at', models.DateTimeField()),
|
||||
('ip_address', models.GenericIPAddressField()),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,186 +0,0 @@
|
||||
# Generated by Django 5.1.4 on 2024-12-26 21:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0005_securitytoken'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ChangeFactionPrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, default=10.0, max_digits=6)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ChangeRacePrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=5)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomizePrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, default=1.0, max_digits=5)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GoldPrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('gold_amount', models.IntegerField()),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='GuildRenameSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('cost', models.IntegerField(default=1000, help_text='Costo en Donation Points (DP) para renombrar una hermandad.')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Configuración de Renombrar Hermandad',
|
||||
'verbose_name_plural': 'Configuraciones de Renombrar Hermandad',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HomeApiPoints',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('accountID', models.IntegerField(unique=True)),
|
||||
('vp', models.IntegerField(default=0)),
|
||||
('dp', models.IntegerField(default=0)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'home_api_points',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LevelUpPrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, default=10.0, max_digits=10)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RenamePrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, default=2.0, max_digits=5)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReviveHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('character_name', models.CharField(max_length=50)),
|
||||
('used_at', models.DateTimeField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TransferPrice',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=5)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UnstuckHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('character_name', models.CharField(max_length=50)),
|
||||
('used_at', models.DateTimeField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VoteSite',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('url', models.URLField(help_text='URL del sitio de votación')),
|
||||
('image_url', models.URLField(help_text='URL completa de la imagen', max_length=300)),
|
||||
('points', models.IntegerField(default=1, help_text='Puntos otorgados por votar')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='securitytoken',
|
||||
name='user_id',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='accountactivation',
|
||||
name='is_new_email_used',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='accountactivation',
|
||||
name='is_used',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='accountactivation',
|
||||
name='old_email',
|
||||
field=models.EmailField(blank=True, max_length=254, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='accountactivation',
|
||||
name='old_email_hash',
|
||||
field=models.CharField(blank=True, max_length=32, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='securitytoken',
|
||||
name='user',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StoreCategory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='home.storecategory')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StoreItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('item_id', models.IntegerField()),
|
||||
('image_url', models.URLField()),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=6)),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.storecategory')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Cart',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('username', models.CharField(max_length=150)),
|
||||
('added_at', models.DateTimeField(auto_now_add=True)),
|
||||
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.storeitem')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VoteLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('account_id', models.IntegerField()),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('last_vote_time', models.DateTimeField(blank=True, null=True)),
|
||||
('processed', models.BooleanField(default=False)),
|
||||
('processed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('vote_site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='home.votesite')),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 4.2.17 on 2024-12-28 21:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0006_changefactionprice_changeraceprice_customizeprice_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MaintenanceMode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_active', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,49 +0,0 @@
|
||||
# Generated by Django 5.1.4 on 2025-02-15 18:04
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0007_maintenancemode'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Category',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='storecategory',
|
||||
name='parent_category',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='storeitem',
|
||||
name='category',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('item_id', models.IntegerField(unique=True)),
|
||||
('image_url', models.URLField()),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='home.category')),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Cart',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='StoreCategory',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='StoreItem',
|
||||
),
|
||||
]
|
||||
@@ -1,30 +0,0 @@
|
||||
# Generated by Django 5.1.4 on 2025-02-16 18:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0008_category_remove_storecategory_parent_category_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StripeLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('account_id', models.IntegerField()),
|
||||
('username', models.CharField(max_length=100)),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('acore_ip', models.GenericIPAddressField()),
|
||||
('stripe_ip', models.GenericIPAddressField(blank=True, null=True)),
|
||||
('product_name', models.TextField()),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('session_id', models.CharField(max_length=255)),
|
||||
('mode', models.CharField(choices=[('test', 'Test'), ('live', 'Live')], max_length=10)),
|
||||
('character_name', models.CharField(max_length=100)),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.1.4 on 2025-02-18 10:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0009_stripelog'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LoginAttempt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('username', models.CharField(max_length=100)),
|
||||
('status', models.CharField(max_length=50)),
|
||||
('ip_address', models.GenericIPAddressField()),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
# Generated by Django 5.1.4 on 2025-03-02 16:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0010_loginattempt'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Pedido',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transaction_id', models.CharField(blank=True, max_length=100, null=True, unique=True)),
|
||||
('email_cliente', models.EmailField(max_length=254)),
|
||||
('estado', models.CharField(default='Pendiente', max_length=20)),
|
||||
('monto', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
],
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='guildrenamesettings',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='guildrenamesettings',
|
||||
name='cost',
|
||||
field=models.DecimalField(decimal_places=2, default=10, help_text='Costo en euros para renombrar una hermandad.', max_digits=10),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-12 16:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0011_pedido_alter_guildrenamesettings_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='salt',
|
||||
field=models.BinaryField(blank=True, max_length=32, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=17, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountactivation',
|
||||
name='verifier',
|
||||
field=models.BinaryField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-12 17:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0012_alter_accountactivation_salt_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='stripelog',
|
||||
name='fulfilled',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 6.0.7 on 2026-07-12 20:53
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0013_stripelog_fulfilled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PasswordReset',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('token', models.CharField(max_length=64, unique=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('used', models.BooleanField(default=False)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,13 +0,0 @@
|
||||
"""Modelos de la app `home`, divididos por secciones.
|
||||
|
||||
Antes un único home/models.py. El `import *` de cada submódulo re-exporta
|
||||
los modelos para que `from home.models import X`, el admin y las migraciones
|
||||
(app_label=`home`, nombres de modelo intactos) sigan funcionando igual.
|
||||
"""
|
||||
from .pricing import * # noqa: F401,F403
|
||||
from .store import * # noqa: F401,F403
|
||||
from .site import * # noqa: F401,F403
|
||||
from .recruit import * # noqa: F401,F403
|
||||
from .accounts import * # noqa: F401,F403
|
||||
from .voting import * # noqa: F401,F403
|
||||
from .history import * # noqa: F401,F403
|
||||
@@ -1,62 +0,0 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class AccountActivation(models.Model):
|
||||
# En 3.4.3 (Battle.net) la identidad es el email; el username de la cuenta
|
||||
# de juego se genera al activar (<bnetId>#1), por eso es opcional aquí.
|
||||
username = models.CharField(max_length=17, null=True, blank=True)
|
||||
email = models.EmailField()
|
||||
old_email = models.EmailField(null=True, blank=True)
|
||||
password = models.CharField(max_length=64)
|
||||
# salt/verifier ya no se precalculan en el registro (se calculan al activar,
|
||||
# tanto el de bnet como el de la cuenta de juego). Se conservan opcionales
|
||||
# por compatibilidad con el flujo de cambio de email.
|
||||
salt = models.BinaryField(max_length=32, null=True, blank=True)
|
||||
verifier = models.BinaryField(max_length=256, null=True, blank=True)
|
||||
recruiter_id = models.IntegerField(null=True, blank=True)
|
||||
hash = models.CharField(max_length=32, unique=True)
|
||||
old_email_hash = models.CharField(max_length=32, null=True, blank=True)
|
||||
is_used = models.BooleanField(default=False) # Indica si el old_email_hash ha sido usado
|
||||
is_new_email_used = models.BooleanField(default=False) # Indica si el hash del nuevo correo ha sido usado
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
def is_expired(self):
|
||||
return timezone.now() > self.created_at + timezone.timedelta(hours=1)
|
||||
|
||||
def __str__(self):
|
||||
return f"Activation for {self.username}"
|
||||
|
||||
class PasswordReset(models.Model):
|
||||
"""Token de restablecimiento de contraseña (cuenta Battle.net, por email)."""
|
||||
email = models.EmailField()
|
||||
token = models.CharField(max_length=64, unique=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
used = models.BooleanField(default=False)
|
||||
|
||||
def is_expired(self):
|
||||
return timezone.now() > self.created_at + timezone.timedelta(hours=1)
|
||||
|
||||
def __str__(self):
|
||||
return f"PasswordReset for {self.email}"
|
||||
|
||||
|
||||
class SecurityToken(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
|
||||
token = models.CharField(max_length=6)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
expires_at = models.DateTimeField()
|
||||
ip_address = models.GenericIPAddressField()
|
||||
|
||||
def __str__(self):
|
||||
return f"Token for {self.user.username if self.user else 'Desconocido'}"
|
||||
|
||||
class LoginAttempt(models.Model):
|
||||
username = models.CharField(max_length=100)
|
||||
status = models.CharField(max_length=50) # Ejemplo: 'Éxito' o 'Fracaso'
|
||||
ip_address = models.GenericIPAddressField() # IP del intento de inicio de sesión
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.username} - {self.status} - {self.timestamp}"
|
||||
@@ -1,13 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class UnstuckHistory(models.Model):
|
||||
character_name = models.CharField(max_length=50)
|
||||
used_at = models.DateTimeField()
|
||||
|
||||
class ReviveHistory(models.Model):
|
||||
character_name = models.CharField(max_length=50)
|
||||
used_at = models.DateTimeField()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.character_name} - {self.used_at}"
|
||||
@@ -1,51 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class ChangeFactionPrice(models.Model):
|
||||
price = models.DecimalField(max_digits=6, decimal_places=2, default=10.00)
|
||||
|
||||
def __str__(self):
|
||||
return f"Precio para cambiar facción: {self.price} EUR"
|
||||
|
||||
class LevelUpPrice(models.Model):
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2, default=10.00)
|
||||
|
||||
def __str__(self):
|
||||
return f"Subir a nivel 80: {self.price} EUR"
|
||||
|
||||
class GoldPrice(models.Model):
|
||||
gold_amount = models.IntegerField() # Cantidad de oro
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2) # Precio en EUR
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.gold_amount} oro - {self.price} EUR"
|
||||
|
||||
class TransferPrice(models.Model):
|
||||
price = models.DecimalField(max_digits=5, decimal_places=2)
|
||||
|
||||
def __str__(self):
|
||||
return f"Precio de transferencia: {self.price} EUR"
|
||||
|
||||
class RenamePrice(models.Model):
|
||||
price = models.DecimalField(max_digits=5, decimal_places=2, default=2.00)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.price} EUR"
|
||||
|
||||
class CustomizePrice(models.Model):
|
||||
price = models.DecimalField(max_digits=5, decimal_places=2, default=1.00)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.price} EUR"
|
||||
|
||||
class ChangeRacePrice(models.Model):
|
||||
price = models.DecimalField(max_digits=5, decimal_places=2)
|
||||
|
||||
def __str__(self):
|
||||
return f"Cambiar raza: {self.price} EUR"
|
||||
|
||||
class GuildRenameSettings(models.Model):
|
||||
cost = models.DecimalField(max_digits=10, decimal_places=2, default=10, help_text="Costo en euros para renombrar una hermandad.")
|
||||
|
||||
def __str__(self):
|
||||
return f"Configuración de renombrar hermandad - Costo: {self.cost} €"
|
||||
@@ -1,34 +0,0 @@
|
||||
from django.db import models
|
||||
from django_ckeditor_5.fields import CKEditor5Field
|
||||
|
||||
|
||||
class RecruitAFriend(models.Model):
|
||||
title = models.CharField(max_length=255, default="Recluta a un amigo")
|
||||
information = models.CharField(max_length=255, default="Información")
|
||||
content = CKEditor5Field('Contenido', config_name='default')
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class RecruitReward(models.Model):
|
||||
required_friends = models.IntegerField()
|
||||
reward_name = models.CharField(max_length=100)
|
||||
item_id = models.IntegerField()
|
||||
item_quantity = models.IntegerField(default=1)
|
||||
item_link = models.URLField()
|
||||
icon_class = models.CharField(max_length=50, help_text="Clase CSS para el icono (ej. 'icontinyl q3')")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.required_friends} amigos - {self.reward_name}"
|
||||
|
||||
class ClaimedReward(models.Model):
|
||||
recruit_reward = models.ForeignKey(RecruitReward, on_delete=models.CASCADE, related_name='claimed_rewards')
|
||||
account_id = models.IntegerField()
|
||||
character_name = models.CharField(max_length=100)
|
||||
username = models.CharField(max_length=100)
|
||||
claimed_at = models.DateTimeField(auto_now_add=True)
|
||||
ip_address = models.CharField(max_length=45, default='127.0.0.1') # Añadir un valor predeterminado
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.username} - {self.recruit_reward.reward_name} - {self.claimed_at}"
|
||||
@@ -1,95 +0,0 @@
|
||||
from django.db import models
|
||||
from django_ckeditor_5.fields import CKEditor5Field
|
||||
|
||||
|
||||
def expansion_from_gamebuild(gamebuild):
|
||||
"""Etiqueta de expansión a partir del build del cliente.
|
||||
|
||||
Distingue el retail antiguo del relanzamiento WoW Classic:
|
||||
- 12340 -> 'WotLK' (3.3.5a, retail/privado clásico)
|
||||
- 44832..54261 -> 'WotLK Classic' (3.4.x; 3.4.3 = 54261)
|
||||
- 15595 -> 'Cataclysm' (4.3.4 retail)
|
||||
- 54332..60000 -> 'Cataclysm Classic' (4.4.x)
|
||||
WotLK Classic llega a 3.4.3=54261 y Cataclysm Classic empieza en 4.4.0=54332,
|
||||
están muy pegados: de ahí el corte en 54261. Nova WoW corre 3.4.3 -> WotLK Classic.
|
||||
"""
|
||||
if gamebuild is None:
|
||||
return 'Unknown'
|
||||
if gamebuild == 12340:
|
||||
return 'WotLK'
|
||||
if 44000 <= gamebuild <= 54261:
|
||||
return 'WotLK Classic'
|
||||
if gamebuild == 15595:
|
||||
return 'Cataclysm'
|
||||
if 54262 <= gamebuild <= 60000:
|
||||
return 'Cataclysm Classic'
|
||||
return 'Unknown'
|
||||
|
||||
|
||||
class Noticia(models.Model):
|
||||
titulo = models.CharField(max_length=200)
|
||||
contenido = CKEditor5Field('Contenido', config_name='default')
|
||||
fecha_publicacion = models.DateTimeField(auto_now=True) # Cambiado a DateTimeField
|
||||
fecha_fin_evento = models.DateField(null=True, blank=True)
|
||||
enlace = models.URLField(max_length=200, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.titulo
|
||||
|
||||
class MaintenanceMode(models.Model):
|
||||
is_active = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "Maintenance Mode"
|
||||
|
||||
class ClienteCategoria(models.Model):
|
||||
nombre = models.CharField(max_length=100)
|
||||
descripcion = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.nombre
|
||||
|
||||
class SistemaOperativo(models.Model):
|
||||
categoria = models.ForeignKey(ClienteCategoria, related_name='sistemas_operativos', on_delete=models.CASCADE)
|
||||
nombre = models.CharField(max_length=100)
|
||||
url_descarga = models.URLField()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.nombre} - {self.categoria.nombre}"
|
||||
|
||||
class ServerSelection(models.Model):
|
||||
server_id = models.IntegerField()
|
||||
name = models.CharField(max_length=100)
|
||||
address = models.CharField(max_length=100)
|
||||
port = models.IntegerField()
|
||||
gamebuild = models.IntegerField()
|
||||
|
||||
@property
|
||||
def expansion(self):
|
||||
return expansion_from_gamebuild(self.gamebuild)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.expansion})"
|
||||
|
||||
class DownloadClientPage(models.Model):
|
||||
title = models.CharField(max_length=255, default="Descarga del cliente")
|
||||
content = CKEditor5Field('Contenido', config_name='default')
|
||||
mac_instructions = CKEditor5Field('Instrucciones para macOS', blank=True, null=True, config_name='default')
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class ContentCreator(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
description = CKEditor5Field('Descripción', config_name='default')
|
||||
content_type = models.CharField(max_length=60, default="Contenido general")
|
||||
subtitle = models.CharField(max_length=60, default="Nova WoW")
|
||||
youtube_video_url = models.URLField(max_length=300, blank=True, null=True)
|
||||
avatar_image_url = models.URLField(max_length=300, blank=True, null=True)
|
||||
youtube_channel_url = models.URLField(max_length=300, blank=True, null=True)
|
||||
facebook_page_url = models.URLField(max_length=300, blank=True, null=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
@@ -1,44 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Item(models.Model):
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="items")
|
||||
name = models.CharField(max_length=200)
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2) # Precio en euros
|
||||
item_id = models.IntegerField(unique=True) # ID del ítem en el juego
|
||||
image_url = models.URLField() # URL de la imagen del ítem
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class StripeLog(models.Model):
|
||||
account_id = models.IntegerField() # ID de la cuenta en acore_auth
|
||||
username = models.CharField(max_length=100) # Nombre de la cuenta
|
||||
email = models.EmailField() # Correo electrónico
|
||||
acore_ip = models.GenericIPAddressField() # IP de acore_auth
|
||||
stripe_ip = models.GenericIPAddressField(blank=True, null=True) # IP detectada por Stripe
|
||||
product_name = models.TextField() # Producto comprado
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2) # Precio total
|
||||
session_id = models.CharField(max_length=255) # ID de la sesión de Stripe
|
||||
mode = models.CharField(max_length=10, choices=[("test", "Test"), ("live", "Live")]) # Modo de Stripe
|
||||
character_name = models.CharField(max_length=100) # Nombre del personaje que compró
|
||||
timestamp = models.DateTimeField(auto_now_add=True) # Fecha y hora
|
||||
fulfilled = models.BooleanField(default=False) # Si la entrega ya se procesó (evita re-entregas)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.username} compró {self.product_name} por {self.amount}€ ({self.mode})"
|
||||
|
||||
class Pedido(models.Model):
|
||||
transaction_id = models.CharField(max_length=100, unique=True, null=True, blank=True)
|
||||
email_cliente = models.EmailField()
|
||||
estado = models.CharField(max_length=20, default="Pendiente")
|
||||
monto = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
|
||||
def __str__(self):
|
||||
return f"Pedido {self.transaction_id} - {self.estado}"
|
||||
@@ -1,35 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class VoteSite(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
url = models.URLField(max_length=200, help_text="URL del sitio de votación")
|
||||
image_url = models.URLField(max_length=300, help_text="URL completa de la imagen")
|
||||
points = models.IntegerField(default=1, help_text="Puntos otorgados por votar")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class VoteLog(models.Model):
|
||||
account_id = models.IntegerField()
|
||||
vote_site = models.ForeignKey('VoteSite', on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
last_vote_time = models.DateTimeField(null=True, blank=True)
|
||||
processed = models.BooleanField(default=False)
|
||||
processed_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Voto para {self.vote_site.name} por cuenta {self.account_id}"
|
||||
|
||||
class HomeApiPoints(models.Model):
|
||||
accountID = models.IntegerField(unique=True)
|
||||
vp = models.IntegerField(default=0)
|
||||
dp = models.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
db_table = 'home_api_points'
|
||||
|
||||
def __str__(self):
|
||||
return f"Cuenta {self.accountID} - PV: {self.vp}, DP: {self.dp}"
|
||||
@@ -1,63 +0,0 @@
|
||||
import stripe
|
||||
from django.conf import settings
|
||||
from .models import StripeLog
|
||||
from django.utils.timezone import now
|
||||
stripe.api_key = settings.STRIPE_SECRET_KEY
|
||||
|
||||
def is_session_paid(session_id):
|
||||
"""True si la sesión de Stripe existe y está realmente pagada."""
|
||||
if not session_id:
|
||||
return False
|
||||
try:
|
||||
s = stripe.checkout.Session.retrieve(session_id)
|
||||
return bool(s and getattr(s, "payment_status", None) == "paid")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def create_checkout_session(account_id, username, email, acore_ip, amount, character_name, currency="eur", success_url=None, cancel_url=None, product_name=""):
|
||||
try:
|
||||
# Añadir el id de sesión a la success_url para poder verificar el pago
|
||||
# al volver (Stripe sustituye {CHECKOUT_SESSION_ID} por el id real).
|
||||
if success_url:
|
||||
sep = "&" if "?" in success_url else "?"
|
||||
success_url = f"{success_url}{sep}session_id={{CHECKOUT_SESSION_ID}}"
|
||||
|
||||
# 🔹 Crear sesión de pago en Stripe
|
||||
session = stripe.checkout.Session.create(
|
||||
payment_method_types=["card"],
|
||||
line_items=[{
|
||||
"price_data": {
|
||||
"currency": currency,
|
||||
"product_data": {"name": product_name},
|
||||
"unit_amount": int(amount * 100),
|
||||
},
|
||||
"quantity": 1,
|
||||
}],
|
||||
mode="payment",
|
||||
success_url=success_url,
|
||||
cancel_url=cancel_url,
|
||||
)
|
||||
|
||||
# 🔹 Determinar si Stripe está en modo TEST o LIVE
|
||||
mode = "live" if settings.STRIPE_SECRET_KEY.startswith("sk_live") else "test"
|
||||
|
||||
# 🔹 Guardar el log en la base de datos
|
||||
StripeLog.objects.create(
|
||||
account_id=account_id,
|
||||
username=username,
|
||||
email=email,
|
||||
acore_ip=acore_ip,
|
||||
stripe_ip=None, # Se actualizará cuando llegue el webhook
|
||||
product_name=product_name,
|
||||
amount=amount,
|
||||
session_id=session.id,
|
||||
mode=mode,
|
||||
character_name=character_name,
|
||||
timestamp=now()
|
||||
)
|
||||
|
||||
return {"success": True, "session_id": session.id}
|
||||
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
@@ -1,72 +0,0 @@
|
||||
from django.conf import settings
|
||||
import requests
|
||||
|
||||
|
||||
SUMUP_CLIENT_ID = settings.SUMUP_CLIENT_ID
|
||||
SUMUP_CLIENT_SECRET = settings.SUMUP_CLIENT_SECRET
|
||||
SUMUP_EMAIL = settings.SUMUP_MERCHANT_EMAIL
|
||||
|
||||
API_BASE = "https://api.sumup.com"
|
||||
|
||||
|
||||
def obtener_token():
|
||||
"""Obtiene un access token de SumUp (client_credentials)."""
|
||||
url = f"{API_BASE}/token"
|
||||
data = {
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": SUMUP_CLIENT_ID,
|
||||
"client_secret": SUMUP_CLIENT_SECRET,
|
||||
}
|
||||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||
|
||||
response = requests.post(url, headers=headers, data=data, timeout=15)
|
||||
if response.status_code == 200:
|
||||
return response.json().get("access_token")
|
||||
raise Exception(f"Error al obtener token: {response.text}")
|
||||
|
||||
|
||||
def crear_checkout_battlepay(reference, monto, descripcion, moneda="EUR", return_url=None):
|
||||
"""
|
||||
Crea un checkout de SumUp para una orden Battlepay concreta.
|
||||
|
||||
``reference`` es la referencia única de la orden (battlepay_orders.reference);
|
||||
se usa como ``checkout_reference`` para poder mapear el webhook a la orden.
|
||||
Devuelve el JSON del checkout (incluye ``id``).
|
||||
"""
|
||||
access_token = obtener_token()
|
||||
url = f"{API_BASE}/v0.1/checkouts"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"checkout_reference": reference,
|
||||
"amount": float(monto),
|
||||
"currency": moneda,
|
||||
"pay_to_email": SUMUP_EMAIL,
|
||||
"description": descripcion,
|
||||
}
|
||||
if return_url:
|
||||
data["return_url"] = return_url
|
||||
|
||||
response = requests.post(url, headers=headers, json=data, timeout=15)
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
raise Exception(f"Error al crear checkout: {response.text}")
|
||||
|
||||
|
||||
def obtener_checkout(checkout_id):
|
||||
"""Consulta un checkout por id. Devuelve su JSON (status, checkout_reference...)."""
|
||||
access_token = obtener_token()
|
||||
url = f"{API_BASE}/v0.1/checkouts/{checkout_id}"
|
||||
headers = {"Authorization": f"Bearer {access_token}"}
|
||||
response = requests.get(url, headers=headers, timeout=15)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
raise Exception(f"Error al consultar checkout: {response.text}")
|
||||
|
||||
|
||||
def crear_checkout(monto, moneda="EUR"):
|
||||
"""Compatibilidad: checkout genérico de prueba. Devuelve el checkout_id."""
|
||||
checkout = crear_checkout_battlepay("pedido-prueba", monto, "Pago de prueba", moneda)
|
||||
return checkout.get("id")
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/not_found.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/ban_history.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,60 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
|
||||
<div class="main-page">
|
||||
<div class="middle-content">
|
||||
<div class="body-content">
|
||||
<div class="title-content">
|
||||
<h1>Tienda del cliente (Battlepay)</h1>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="body-box-content centered">
|
||||
<p>Compras pendientes de pago de la cuenta <strong>{{ username }}</strong>.</p>
|
||||
<br>
|
||||
|
||||
{% if orders %}
|
||||
<table class="middle-center-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th>Precio</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td>{{ order.product_name }}</td>
|
||||
<td>{{ order.price_eur }} €</td>
|
||||
<td>
|
||||
<form action="{% url 'battlepay_pay' %}" method="POST" accept-charset="utf-8">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="reference" value="{{ order.reference }}">
|
||||
<button type="submit" class="login-button">Pagar con SumUp</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No tienes compras pendientes.</p>
|
||||
<br>
|
||||
<p>Para comprar, pulsa <strong>Comprar</strong> en la tienda del cliente dentro del juego;
|
||||
la compra aparecerá aquí para completar el pago.</p>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
<p><a href="my-account">Volver a mi cuenta</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_email.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_faction.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_faction_cancel.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_faction_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_password.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_race.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_race_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/change_race_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/confirm_new_email_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/confirm_old_email_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/customize_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/customize_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/d_points.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/expired_link.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/gold_cancel.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/gold_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/gold_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/guild_rename_cancel.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/guild_rename_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/level_up.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/level_up_cancel.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/level_up_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/pago_sumup.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/points_history.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/promo_code.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/quest_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/rename_cancel.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/rename_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/rename_guild.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/rename_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/restore_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/restore_items.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/revive_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/security_history.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/security_token.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/send_gift.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/trade_points.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/trans_history.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/transfer_cancel.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/transfer_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/transfer_d_points.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/transfer_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/unstuck_character.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/vote_points.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,44 +0,0 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">{% trans "Seleccionar Servidor" %}</h1>
|
||||
<form method="post" action="{% url 'admin:home_serverselection_changelist' %}" class="needs-validation" novalidate>
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="serverSelection">{% trans "Seleccionar servidor" %}</label>
|
||||
<select class="form-control" id="serverSelection" name="server_selection" required>
|
||||
<option value="" disabled selected>{% trans "Elija un servidor" %}</option>
|
||||
{% for server in servers %}
|
||||
<option value="{{ server.0 }}">{{ server.1 }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="invalid-feedback">
|
||||
{% trans "Debe seleccionar un servidor antes de continuar." %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-row">
|
||||
<input type="submit" value='{% trans "Guardar selección" %}' class="default" name="_save">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
// Validación de formulario con Bootstrap
|
||||
(function () {
|
||||
'use strict';
|
||||
window.addEventListener('load', function () {
|
||||
var forms = document.getElementsByClassName('needs-validation');
|
||||
Array.prototype.filter.call(forms, function (form) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
if (form.checkValidity() === false) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
form.classList.add('was-validated');
|
||||
}, false);
|
||||
});
|
||||
}, false);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/activation_invalid.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/activation_success.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/login.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/recover_account.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/register.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% load django_vite %}
|
||||
<div class="main-page">
|
||||
<div class="middle-content">
|
||||
<div class="body-content">
|
||||
<div class="title-content">
|
||||
<h1>Restablecer contraseña</h1>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<!-- Isla React (Vite): fija la nueva contraseña con el token del enlace.
|
||||
POST a {% url 'reset_password' %} (JSON) -> re-deriva el verifier bnet. -->
|
||||
<div id="reset-password-app"
|
||||
data-csrf="{{ csrf_token }}"
|
||||
data-valid="{{ valid|yesno:'true,false' }}"
|
||||
data-token="{{ token }}"
|
||||
data-reset-url="{% url 'reset_password' %}"
|
||||
data-recover-url="{% url 'recover' %}"></div>
|
||||
{% vite_asset 'src/entries/reset_password.tsx' %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,45 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
|
||||
<div class="main-page">
|
||||
<div class="middle-content">
|
||||
<div class="body-content">
|
||||
<div class="title-content">
|
||||
<h1>Selecciona tu cuenta de juego</h1>
|
||||
</div>
|
||||
<div class="box-content">
|
||||
<div class="body-box-content centered">
|
||||
{% if bnet_email %}
|
||||
<p>Conectado como <strong>{{ bnet_email }}</strong></p>
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
{% if game_accounts %}
|
||||
<p>Tu cuenta Battle.net tiene varias cuentas de juego. Elige con cuál quieres continuar:</p>
|
||||
<br>
|
||||
{% load django_vite %}
|
||||
<!-- Isla React (Vite): selector de cuenta de juego. Selección vía GET
|
||||
a /api/account/select/ (sin POST); redirige a my-account. -->
|
||||
<div id="select-account-app" data-select-url="/api/account/select/"></div>
|
||||
{{ game_accounts|json_script:"select-account-data" }}
|
||||
{% vite_asset 'src/entries/select_account.tsx' %}
|
||||
{% else %}
|
||||
<p>No se han encontrado cuentas de juego asociadas a esta cuenta Battle.net.</p>
|
||||
<br>
|
||||
<p>Contacta con el soporte si crees que es un error.</p>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
<p><a href="log-out">Cerrar sesión</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,10 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/start_time.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/noticias.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/videos.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/help.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/contact_us.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
{% include 'partials/head.html' %}
|
||||
{% include 'partials/header.html' %}
|
||||
{% include 'partials/video.html' %}
|
||||
{% include 'partials/descargas.html' %}
|
||||
{% include 'partials/social.html' %}
|
||||
{% include 'partials/footer.html' %}
|
||||
{% include 'partials/final.html' %}
|
||||
@@ -1,34 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head><meta charset="utf-8"></head>
|
||||
<body style="margin:0;padding:0;background:#1b120b;font-family:Arial,Helvetica,sans-serif;color:#e8dccb;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#1b120b;padding:24px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#241812;border:1px solid #4a3320;border-radius:8px;">
|
||||
<tr>
|
||||
<td style="padding:28px 32px;">
|
||||
<h1 style="margin:0 0 8px;font-size:20px;color:#d79602;">{{ NOMBRE_SERVIDOR }}</h1>
|
||||
<h2 style="margin:0 0 16px;font-size:16px;color:#e8dccb;">Tus cuentas de juego</h2>
|
||||
<p style="margin:0 0 12px;line-height:1.5;">Hola,</p>
|
||||
<p style="margin:0 0 16px;line-height:1.5;">Estas son las cuentas de juego asociadas a tu cuenta Battle.net (<strong>{{ email }}</strong>). Recuerda: para iniciar sesión en la web usas tu <strong>correo electrónico</strong>.</p>
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" style="width:100%;border-collapse:collapse;margin:0 0 16px;">
|
||||
{% for a in accounts %}
|
||||
<tr>
|
||||
<td style="padding:10px 14px;border:1px solid #4a3320;background:#2c1e14;color:#e8dccb;">{{ a.username }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td style="padding:10px 14px;border:1px solid #4a3320;background:#2c1e14;color:#b1997f;">No hay cuentas de juego asociadas todavía.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<p style="margin:0;line-height:1.5;font-size:13px;color:#b1997f;">Si no has solicitado esta información, ignora este correo.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,145 +0,0 @@
|
||||
<td valign="top" style="padding:0;Margin:0">
|
||||
<table cellpadding="0" cellspacing="0" align="center" style="border-collapse:collapse;border-spacing:0px;table-layout:fixed!important;width:100%;background-color:transparent;background-repeat:repeat;background-position:center top">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0">
|
||||
<table cellspacing="0" cellpadding="0" align="center" style="border-collapse:collapse;border-spacing:0px;background-color:transparent;width:600px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" background="https://ci3.googleusercontent.com/meips/ADKq_Nb7TRH9h2_l1rekf1lKsc5H3P70u9ihoUZEEdHemt7tmVs_0-laBoolKLbqcFXKopQB6mbAu6STs5rmDUPDPSNJr-ZQn1Lg7qTMNpXBfdb-tjYQ9aa9wLiv87TZh1-7Twqjyh_K=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-footer.jpg" style="Margin:0;padding-top:10px;padding-bottom:10px;padding-left:10px;padding-right:10px;background-image:url(https://ci3.googleusercontent.com/meips/ADKq_Nb7TRH9h2_l1rekf1lKsc5H3P70u9ihoUZEEdHemt7tmVs_0-laBoolKLbqcFXKopQB6mbAu6STs5rmDUPDPSNJr-ZQn1Lg7qTMNpXBfdb-tjYQ9aa9wLiv87TZh1-7Twqjyh_K=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-footer.jpg);background-repeat:no-repeat;background-position:left top">
|
||||
|
||||
<table cellspacing="0" cellpadding="0" align="left" style="border-collapse:collapse;border-spacing:0px;float:left">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0;width:280px">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" id="m_8140815850608678152title-mail-header" style="padding:0;Margin:0"><p style="Margin:0;font-size:11px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:17px;color:#b1997f">ACTIVACIÓN DE CUENTA</p></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
|
||||
<table cellspacing="0" cellpadding="0" align="right" style="border-collapse:collapse;border-spacing:0px;float:right">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td style="padding:0;Margin:0">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr id="m_8140815850608678152header-mail-links" style="border-collapse:collapse">
|
||||
<td style="Margin:0;padding-left:5px;padding-right:5px;padding-top:3px;padding-bottom:0px;border:0" width="50%" bgcolor="transparent" align="center"><a style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:none;display:block;color:#2471a9" href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/1/YPPqGZJ6WcWgL8kfJbV5GQ/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzLw" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/1/YPPqGZJ6WcWgL8kfJbV5GQ/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzLw&source=gmail&ust=1731498433468000&usg=AOvVaw1XHA5FQ8SJ6azBxrP1QOUB">Ir al sitio</a></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<table cellspacing="0" cellpadding="0" align="center" style="border-collapse:collapse;border-spacing:0px;table-layout:fixed!important;width:100%">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0">
|
||||
<table style="border-collapse:collapse;border-spacing:0px;background-color:transparent;width:600px" cellspacing="0" cellpadding="0" align="center">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" valign="top" style="padding:0;Margin:0;width:600px">
|
||||
<table cellpadding="0" cellspacing="0" width="100%" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0;font-size:0px"><a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/2/LcFesQN9Atu1p8XKYNeYrw/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzLw" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:underline;color:#b58a60" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/2/LcFesQN9Atu1p8XKYNeYrw/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzLw&source=gmail&ust=1731498433468000&usg=AOvVaw3nPd4Lj8GE_62E_stVyXcu"><img src="https://ci3.googleusercontent.com/meips/ADKq_NbGrtaFRw1N5A4Ocfm1t4FL2cRnrcqdXNd7NS7wmpPr_a95bSWKHdPsxbJ7EELaQeomHnmCGgOTiKRXinebDgkWcljQLlRqdekcgfDYtBHevrlgbYUerJF7Kd8q96eogRMAUA=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-logo.webp" alt="" style="display:block;border:0;outline:none;text-decoration:none" width="600" class="CToWUd" data-bit="iit"></a></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" background="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg" style="Margin:0;padding-top:40px;padding-bottom:40px;padding-left:40px;padding-right:40px;background-image:url({{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg);background-repeat:repeat;background-position:left top">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td valign="top" align="center" style="padding:0;Margin:0;width:520px">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0"><h1 style="Margin:0;line-height:45px;font-family:georgia,times,'times new roman',serif;font-size:30px;font-style:normal;font-weight:normal;color:#b17c02">Bienvenido, {{ username }}</h1></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#ebdec2">Gracias por crear una cuenta en <span class="il">{{ NOMBRE_SERVIDOR }}</span>.<br>Para poder empezar a usar tu cuenta, deberás activarla haciendo clic en el botón ACTIVAR LA CUENTA.<br><br>Tus datos de cuenta son:</p></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#b1997f">Cuenta: {{ username }}<br>Contraseña: {{ password }}<br><br></p></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#ebdec2">Con tu usuario y contraseña podrás entrar a nuestro sitio web y al servidor.</p></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:20px;Margin:0;font-size:0">
|
||||
<table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td style="padding:0;Margin:0;border-bottom:2px solid #5c4c44;background:none;height:1px;width:100%;margin:0px"></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0;padding-top:5px;padding-bottom:20px"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#ebdec2">- Al servidor se debe entrar con nombre de usuario y contraseña. NO con el correo.<br>- Puedes usar el mismo correo para registrar hasta 10 cuentas en el servidor.<br>- El usuario del foro es independiente del usuario que acabas de crear. Puedes crear una cuenta para el foro <a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/3/oCFCOOvfQvjar6zSGHXF5g/aHR0cHM6Ly9mb3JvLnVsdGltb3dvdy5jb20v" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/3/oCFCOOvfQvjar6zSGHXF5g/aHR0cHM6Ly9mb3JvLnVsdGltb3dvdy5jb20v&source=gmail&ust=1731498433468000&usg=AOvVaw2RiVgc2wlXg1AAzyT2ij7D">aquí</a>.</p></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0"><span style="border-style:solid;border-color:#211917;background:#211917;border-width:2px;display:inline-block;border-radius:0px;width:auto"><a href="{{ activation_link }}" style="text-decoration:none;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:16px;color:#ebdec2;border-style:solid;border-color:#211917;border-width:10px 20px 10px 20px;display:inline-block;background:#211917;border-radius:0px;font-weight:normal;font-style:normal;line-height:19px;width:auto;text-align:center" target="_blank" data-saferedirecturl="{{ activation_link }}">ACTIVAR LA CUENTA</a></span></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0;padding-top:15px"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#ebdec2">Si no puedes activar la cuenta usando el botón, copia y pega en tu navegador siguiente enlace:<br><a href="{{ activation_link }}" target="_blank" data-saferedirecturl="{{ activation_link }}">{{ activation_link }}</a></p></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0;padding-top:5px;padding-bottom:20px"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#ebdec2"><br><br>Atentamente, el Equipo de <span class="il">{{ NOMBRE_SERVIDOR }}</span>.</p></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<table cellpadding="0" cellspacing="0" align="center" style="border-collapse:collapse;border-spacing:0px;table-layout:fixed!important;width:100%;background-color:transparent;background-repeat:repeat;background-position:center top">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0">
|
||||
<table cellspacing="0" cellpadding="0" align="center" style="background-color:transparent;border-collapse:collapse;border-spacing:0px;width:600px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="left" style="padding:0;Margin:0">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td valign="top" align="center" style="padding:0;Margin:0;width:600px">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" background="https://ci3.googleusercontent.com/meips/ADKq_Nb7TRH9h2_l1rekf1lKsc5H3P70u9ihoUZEEdHemt7tmVs_0-laBoolKLbqcFXKopQB6mbAu6STs5rmDUPDPSNJr-ZQn1Lg7qTMNpXBfdb-tjYQ9aa9wLiv87TZh1-7Twqjyh_K=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-footer.jpg" style="border-collapse:collapse;border-spacing:0px;background-image:url(https://ci3.googleusercontent.com/meips/ADKq_Nb7TRH9h2_l1rekf1lKsc5H3P70u9ihoUZEEdHemt7tmVs_0-laBoolKLbqcFXKopQB6mbAu6STs5rmDUPDPSNJr-ZQn1Lg7qTMNpXBfdb-tjYQ9aa9wLiv87TZh1-7Twqjyh_K=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-footer.jpg);background-repeat:no-repeat;background-position:left top" role="presentation">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0;padding-top:10px"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#ffffff">SÍGUENOS EN</p></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0;padding-top:15px;padding-bottom:15px;font-size:0">
|
||||
<table cellspacing="0" cellpadding="0" role="presentation" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td valign="top" align="center" style="padding:0;Margin:0;padding-right:20px"><a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/5/f6s0ZwRQuTE-C-jasxOasA/aHR0cHM6Ly93d3cuZmFjZWJvb2suY29tL1VsdGltb1dvVy8" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:underline;color:#333333" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/5/f6s0ZwRQuTE-C-jasxOasA/aHR0cHM6Ly93d3cuZmFjZWJvb2suY29tL1VsdGltb1dvVy8&source=gmail&ust=1731498433469000&usg=AOvVaw2ZYRFnIqpWDQyggiWBGXiO"><img title="Facebook" src="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/facebook-logo-gray.png#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/facebook-logo-gray.png" alt="Fb" width="32" height="32" style="display:block;border:0;outline:none;text-decoration:none" class="CToWUd" data-bit="iit"></a></td>
|
||||
<td valign="top" align="center" style="padding:0;Margin:0;padding-right:20px"><a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/6/UGtcJN5ISi7MAnPkbxjBcQ/aHR0cHM6Ly93d3cuaW5zdGFncmFtLmNvbS9VbHRpbW9Xb1cv" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:underline;color:#333333" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/6/UGtcJN5ISi7MAnPkbxjBcQ/aHR0cHM6Ly93d3cuaW5zdGFncmFtLmNvbS9VbHRpbW9Xb1cv&source=gmail&ust=1731498433469000&usg=AOvVaw3a1MAk7G2C1gKYxEBcZ596"><img title="Instagram" src="https://ci3.googleusercontent.com/meips/ADKq_NaAaG1uiPhc-J3UJaRHNGlIjQEP6NvhOLKG9_MBwf-udq3Ao7oCHffCGaQTQRl4AE6vTB11LigeSFs35bZyvLfIeaMY1hq_NtkoC4Zdo-foNc8Tf8PTqEKcvTglkSt8CmOLt4Ewrzuz8jk=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/instagram-logo-gray.png" alt="Ig" width="32" height="32" style="display:block;border:0;outline:none;text-decoration:none" class="CToWUd" data-bit="iit"></a></td>
|
||||
<td valign="top" align="center" style="padding:0;Margin:0;padding-right:20px"><a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/7/dNuKqBpwdnPzZqmaJqBMLg/aHR0cHM6Ly90d2l0dGVyLmNvbS9VbHRpbW9Xb1c" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:underline;color:#333333" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/7/dNuKqBpwdnPzZqmaJqBMLg/aHR0cHM6Ly90d2l0dGVyLmNvbS9VbHRpbW9Xb1c&source=gmail&ust=1731498433469000&usg=AOvVaw0mNhdOEFArJFuU_zdxLrVr"><img title="Twitter" src="https://ci3.googleusercontent.com/meips/ADKq_Na3G-i__xB8LyVCx9Wxs_nsW07gi3Uo3-27M3uXViT9DnhI0oykXfumzibiio1R0jLpHVKx3W1Yao5mcAODWc-6LS0nx9nuKJ2_L7qY6eoUhfdF-VxxWeEQR1LBwnsjowmhy83_izS-=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/twitter-logo-gray.png" alt="Tw" width="32" height="32" style="display:block;border:0;outline:none;text-decoration:none" class="CToWUd" data-bit="iit"></a></td>
|
||||
<td valign="top" align="center" style="padding:0;Margin:0"><a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/8/sGLIpTKAAbVLc-tQyYqAVQ/aHR0cHM6Ly93d3cueW91dHViZS5jb20vY2hhbm5lbC9VQ2FITDhCWmNobzhBa2VNOXdja1FieWc" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:underline;color:#333333" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/8/sGLIpTKAAbVLc-tQyYqAVQ/aHR0cHM6Ly93d3cueW91dHViZS5jb20vY2hhbm5lbC9VQ2FITDhCWmNobzhBa2VNOXdja1FieWc&source=gmail&ust=1731498433469000&usg=AOvVaw09YlWuRMpxVueGWvjg9E08"><img title="Youtube" src="https://ci3.googleusercontent.com/meips/ADKq_NYxr7vXp62rdfUJTmC76VPL7ErYx7DTXmdpM3gt6ghQ1Ag6E9aLTkja5Jgff_A5020Lrm7K20cjG2RjHCtGTSd2DWxmCD4XvB5VEDXXyL_ivnQBG-hXm42mKYqlBHYFHKwEr9p44lIt=s0-d-e1-ft#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/youtube-logo-gray.png" alt="Yt" width="32" height="32" style="display:block;border:0;outline:none;text-decoration:none" class="CToWUd" data-bit="iit"></a></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
<tr style="border-collapse:collapse">
|
||||
<td style="padding:0;Margin:0" align="left">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;border-spacing:0px">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td valign="top" align="center" style="padding:0;Margin:0;width:600px">
|
||||
<table width="100%" cellspacing="0" cellpadding="0" background="{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg" style="border-collapse:collapse;border-spacing:0px;background-image:url({{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg#{{ URL_PRINCIPAL }}/static/nw-themes/nw-ryu/nw-images/nw-mails/nw-mail-body.jpg);background-repeat:no-repeat;background-position:left top" role="presentation">
|
||||
<tbody><tr style="border-collapse:collapse">
|
||||
<td align="center" style="padding:0;Margin:0;padding-top:10px;padding-bottom:15px"><p style="Margin:0;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';line-height:21px;color:#5c4c44"><a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/9/ZlWuOVIbPYaleSpsSuw3Ew/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzL3Rlcm1zLWFuZC1jb25kaXRpb25z" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:none;color:#2471a9" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/9/ZlWuOVIbPYaleSpsSuw3Ew/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzL3Rlcm1zLWFuZC1jb25kaXRpb25z&source=gmail&ust=1731498433469000&usg=AOvVaw2dsWhHN3CcN0kKQnyUm91o">Términos y Condiciones</a> | <a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/10/5L1HSlG1_MryxhAwLok8Hw/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzL3JlZnVuZC1wb2xpY3k" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:none;color:#2471a9" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/10/5L1HSlG1_MryxhAwLok8Hw/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzL3JlZnVuZC1wb2xpY3k&source=gmail&ust=1731498433469000&usg=AOvVaw0pVCr9IINwuwAKBTAXvMid">Política de Reembolso</a> | <a href="https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/11/bmmVW3qrlVYp2WbjghJdCQ/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzL3ByaXZhY3ktcG9saWN5" style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';font-size:14px;text-decoration:none;color:#2471a9" target="_blank" data-saferedirecturl="https://www.google.com/url?q=https://079xk.mjt.lu/lnk/AU4AAFT1m8YAAAAAAAAAAlmz1CcAAYCtSIUAAAAAACPUeABnKdnNbMFetHGyQ9ursJAD3kGn5wAhkNU/11/bmmVW3qrlVYp2WbjghJdCQ/aHR0cHM6Ly91bHRpbW93b3cuY29tL2VzL3ByaXZhY3ktcG9saWN5&source=gmail&ust=1731498433469000&usg=AOvVaw3DoZqMDsaHsX7G4FIF7Fqy">Política de Privacidad</a><br><span class="il">{{ NOMBRE_SERVIDOR }}</span> © {{ ANIO_ACTUAL }}</p></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
</tr>
|
||||
</tbody></table></td>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Activación del Nuevo Correo</title>
|
||||
</head>
|
||||
<body>
|
||||
<table style="width: 100%; max-width: 600px; margin: 0 auto; border: 1px solid #dddddd; padding: 20px;">
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<h2>Activación de Nuevo Correo</h2>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<p>Hola {{ username }},</p>
|
||||
<p>Tu solicitud para cambiar el correo electrónico en <strong>{{ NOMBRE_SERVIDOR }}</strong> ha sido recibida.</p>
|
||||
<p>Por favor, haz clic en el siguiente enlace para confirmar tu nuevo correo:</p>
|
||||
<p style="text-align: center;">
|
||||
<a href="{{ activation_link }}" style="display: inline-block; padding: 10px 20px; background-color: #28a745; color: white; text-decoration: none; border-radius: 5px;">Activar nuevo correo</a>
|
||||
</p>
|
||||
<p>Si no realizaste esta solicitud, por favor contacta con soporte.</p>
|
||||
<p>Atentamente,<br>El equipo de {{ NOMBRE_SERVIDOR }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Confirmación de Cambio de Correo</title>
|
||||
</head>
|
||||
<body>
|
||||
<table style="width: 100%; max-width: 600px; margin: 0 auto; border: 1px solid #dddddd; padding: 20px;">
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<h2>Confirmación de Cambio de Correo</h2>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<p>Hola {{ username }},</p>
|
||||
<p>Has solicitado cambiar tu correo electrónico registrado en <strong>{{ NOMBRE_SERVIDOR }}</strong>.</p>
|
||||
<p>Para confirmar este cambio, haz clic en el siguiente enlace:</p>
|
||||
<p style="text-align: center;">
|
||||
<a href="{{ confirm_link }}" style="display: inline-block; padding: 10px 20px; background-color: #007bff; color: white; text-decoration: none; border-radius: 5px;">Confirmar cambio</a>
|
||||
</p>
|
||||
<p>Si no solicitaste este cambio, por favor ignora este correo.</p>
|
||||
<p>Atentamente,<br>El equipo de {{ NOMBRE_SERVIDOR }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Notificación de Cambio de Correo</title>
|
||||
</head>
|
||||
<body>
|
||||
<table style="width: 100%; max-width: 600px; margin: 0 auto; border: 1px solid #dddddd; padding: 20px;">
|
||||
<tr>
|
||||
<td style="text-align: center;">
|
||||
<h2>Cambio de Correo Exitoso</h2>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<p>Hola {{ username }},</p>
|
||||
<p>Te notificamos que tu correo electrónico en <strong>{{ NOMBRE_SERVIDOR }}</strong> ha sido cambiado exitosamente al siguiente correo:</p>
|
||||
<p style="text-align: center;">
|
||||
<strong>{{ new_email }}</strong>
|
||||
</p>
|
||||
<p>Si no realizaste este cambio, por favor contacta inmediatamente con soporte.</p>
|
||||
<p>Atentamente,<br>El equipo de {{ NOMBRE_SERVIDOR }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head><meta charset="utf-8"></head>
|
||||
<body style="margin:0;padding:0;background:#1b120b;font-family:Arial,Helvetica,sans-serif;color:#e8dccb;">
|
||||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#1b120b;padding:24px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#241812;border:1px solid #4a3320;border-radius:8px;">
|
||||
<tr>
|
||||
<td style="padding:28px 32px;">
|
||||
<h1 style="margin:0 0 8px;font-size:20px;color:#d79602;">{{ NOMBRE_SERVIDOR }}</h1>
|
||||
<h2 style="margin:0 0 16px;font-size:16px;color:#e8dccb;">Restablecer contraseña</h2>
|
||||
<p style="margin:0 0 12px;line-height:1.5;">Hola,</p>
|
||||
<p style="margin:0 0 12px;line-height:1.5;">Hemos recibido una solicitud para restablecer la contraseña de la cuenta Battle.net asociada a <strong>{{ email }}</strong>.</p>
|
||||
<p style="margin:0 0 20px;line-height:1.5;">Pulsa el botón para elegir una nueva contraseña. El enlace caduca en 1 hora.</p>
|
||||
<p style="margin:0 0 20px;">
|
||||
<a href="{{ reset_link }}" style="display:inline-block;padding:12px 22px;background:#d79602;color:#1b120b;text-decoration:none;border-radius:6px;font-weight:bold;">Restablecer contraseña</a>
|
||||
</p>
|
||||
<p style="margin:0 0 8px;line-height:1.5;font-size:13px;color:#b1997f;">Si el botón no funciona, copia esta dirección en tu navegador:</p>
|
||||
<p style="margin:0 0 20px;line-height:1.5;font-size:13px;word-break:break-all;"><a href="{{ reset_link }}" style="color:#2471a9;">{{ reset_link }}</a></p>
|
||||
<p style="margin:0;line-height:1.5;font-size:13px;color:#b1997f;">Si no has solicitado este cambio, ignora este correo: tu contraseña no se modificará.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user