web-next: herramientas de PD (transferir, comercio, códigos de promoción)

- /transfer-d-points: transferir PD a la cuenta dueña de un personaje
  (requiere token de seguridad; movimiento atómico con FOR UPDATE).
- /trade-points: comercio PD<->oro mediante códigos de 1 solo uso
  (contraseña + token + Turnstile; oro al creador por SOAP; expira 1h;
  lock de 5s tras canjear; rollback total si falla la entrega).
- /promo-code: canje de códigos por PD/PV (sensibles a mayúsculas/minúsculas,
  usos limitados, 1 canje por cuenta).
- /admin/promo: panel para crear / editar / activar / desactivar / borrar
  códigos de promoción.
- Tablas nuevas (sql/): home_tradecode, home_promocode, home_promoredemption.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 09:27:11 +00:00
parent 22ae0ee278
commit 66d056c04b
25 changed files with 1711 additions and 2 deletions
+15
View File
@@ -19,6 +19,21 @@ export async function getGameCharacters(accountId: number): Promise<GameCharacte
}
}
/** Id de la cuenta dueña de un personaje por su nombre (único), o null. */
export async function getAccountIdByCharacterName(name: string): Promise<number | null> {
const trimmed = name.trim()
if (!trimmed) return null
try {
const [rows] = await db(DB.characters).query<RowDataPacket[]>(
'SELECT account FROM characters WHERE name = ? LIMIT 1',
[trimmed],
)
return rows[0] ? Number(rows[0].account) : null
} catch {
return null
}
}
/** True si el personaje pertenece a la cuenta y está desconectado. */
export async function characterIsOfflineAndOwned(accountId: number, name: string): Promise<boolean> {
const chars = await getGameCharacters(accountId)