web-next: /recruit según el markup (Información + Panel RAF con recompensas)

- Reescrita la página: box «Información» con la guía completa de Recluta a un
  amigo (marca → nombre del reino) + box «Panel de Recluta a un amigo».
- Nuevo RecruitPanel: estadísticas (cuenta reclutada, amigos reclutados, amigos
  al nivel 80, recompensas reclamadas X/6) + tabla de recompensas (objetivo,
  objeto con icono y color de calidad, estado) con botón Reclamar para las
  disponibles + sección «Recompensas disponibles». Sustituye a RecruitClaim.
- lib/recruit-claim: getRecruitStats (isRecruited + recruitedCount).
- Colores de calidad de objeto .q0-.q5 en globals.css.
- Sembradas las 6 recompensas en home_recruitreward (seed en sql/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-13 16:14:10 +00:00
parent 02859bab1f
commit c3e57ff25e
6 changed files with 294 additions and 114 deletions
+15
View File
@@ -34,6 +34,21 @@ export async function getRecruitedLevel80Count(accountId: number): Promise<numbe
return Number(rows[0]?.n ?? 0)
}
/** Estado de reclutamiento de la cuenta: si fue reclutada y cuántos amigos ha reclutado. */
export async function getRecruitStats(accountId: number): Promise<{ isRecruited: boolean; recruitedCount: number }> {
let isRecruited = false
let recruitedCount = 0
try {
const [r] = await db(DB.auth).query<RowDataPacket[]>('SELECT recruiter FROM account WHERE id = ?', [accountId])
isRecruited = Boolean(r[0]?.recruiter)
const [c] = await db(DB.auth).query<RowDataPacket[]>('SELECT COUNT(*) AS n FROM account WHERE recruiter = ?', [accountId])
recruitedCount = Number(c[0]?.n ?? 0)
} catch {
/* ignore */
}
return { isRecruited, recruitedCount }
}
/**
* Cuentas reclutadas válidas: tienen un personaje de nivel 80 y NO comparten IP
* con el reclutador (anti-abuso). Devuelve hasta `requiredCount` IDs.