diff --git a/web-next/app/[locale]/trans-history/page.tsx b/web-next/app/[locale]/trans-history/page.tsx
new file mode 100644
index 0000000..4a729b1
--- /dev/null
+++ b/web-next/app/[locale]/trans-history/page.tsx
@@ -0,0 +1,144 @@
+import type { Metadata } from 'next'
+import { setRequestLocale } from 'next-intl/server'
+import { redirect } from '@/i18n/navigation'
+import { getSession } from '@/lib/session'
+import { getPaymentTransactions, type PaymentTx } from '@/lib/trans-history'
+import { PageShell } from '@/components/PageShell'
+import { ServiceBox } from '@/components/ServiceBox'
+
+export const dynamic = 'force-dynamic'
+
+export const metadata: Metadata = { title: 'Historial de transacciones' }
+
+/** Fecha en formato DD-MM-YYYY HH:MM:SS. */
+function fmt(d: Date): string {
+ const p = (n: number) => String(n).padStart(2, '0')
+ return `${p(d.getDate())}-${p(d.getMonth() + 1)}-${d.getFullYear()} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
+}
+
+function StatusBadge({ status }: { status: PaymentTx['status'] }) {
+ return {status}
+}
+
+/** Caja de una pasarela con su tabla de transacciones (o "No hay movimientos"). */
+function PlatformBox({
+ name,
+ logo,
+ note,
+ txs,
+}: {
+ name: string
+ logo: string
+ note: string
+ txs: PaymentTx[]
+}) {
+ return (
+
+
+
+
{name}
+
+
+
+
+ Recibirás los PD correspondientes a cada transacción sólo cuando esté aprobada y el estado sea{' '}
+ PAID (Pagado).
+
+
+
+ Importante: {note}
+
+
+ {txs.length === 0 ? (
+
+
+
+ |
+ No hay movimientos
+ |
+
+
+
+ ) : (
+
+
+
+ | ID de transacción |
+ Estado |
+ Concepto |
+ Fecha creación |
+ Cantidad |
+
+ {txs.map((t) => (
+
+ |
+ {t.id}
+ |
+
+
+ |
+
+ {t.concept}
+ |
+
+ {fmt(t.createdAt)}
+ |
+
+ {t.amount.toFixed(2)} €
+ |
+
+ ))}
+
+
+ )}
+
+
+ )
+}
+
+const LOGO_BASE = '/nw-themes/nw-ryu/nw-images/nw-logos'
+
+export default async function TransHistoryPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params
+ setRequestLocale(locale)
+
+ const session = await getSession()
+ if (!session.bnetId) redirect({ href: '/login', locale })
+ if (!session.username) redirect({ href: '/select-account', locale })
+
+ const txs = await getPaymentTransactions(session.accountId!)
+ const stripe = txs.filter((t) => t.platform === 'Stripe')
+ const sumup = txs.filter((t) => t.platform === 'SumUp')
+
+ return (
+
+
+
+ Aquí verás la información que recibimos de nuestras diferentes plataformas de pago automáticas.
+
+ ¿Has tenido algún problema con alguna transacción?
+
+ Si has tenido un problema con una de las transacciones o no has recibido tus PD, puedes contactarnos en
+ nuestro Discord o a través del correo{' '}
+ contacto@ultimowow.com.
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/web-next/lib/trans-history.ts b/web-next/lib/trans-history.ts
new file mode 100644
index 0000000..297f2f1
--- /dev/null
+++ b/web-next/lib/trans-history.ts
@@ -0,0 +1,38 @@
+import type { RowDataPacket } from 'mysql2'
+import { db, DB } from './db'
+
+/** Una transacción de pago (Stripe o SumUp) registrada en home_stripelog. */
+export interface PaymentTx {
+ id: string // referencia de la pasarela (session_id / checkout_reference)
+ platform: 'Stripe' | 'SumUp'
+ status: 'PAID' | 'PENDING'
+ createdAt: Date
+ amount: number // importe en €
+ concept: string
+}
+
+/**
+ * Transacciones de pago de una cuenta desde `home_stripelog`, limitadas a las
+ * dos pasarelas activas: **Stripe** (mode test/live) y **SumUp** (mode sumup).
+ * `fulfilled` 1 -> PAID (pagado y entregado), 0 -> PENDING. Orden por fecha desc.
+ */
+export async function getPaymentTransactions(accountId: number, limit = 200): Promise {
+ if (!accountId) return []
+ try {
+ const [rows] = await db(DB.default).query(
+ "SELECT session_id, product_name, amount, mode, fulfilled, timestamp FROM home_stripelog " +
+ "WHERE account_id = ? AND mode IN ('sumup', 'test', 'live') ORDER BY timestamp DESC LIMIT ?",
+ [accountId, limit],
+ )
+ return rows.map((r) => ({
+ id: String(r.session_id || ''),
+ platform: r.mode === 'sumup' ? 'SumUp' : 'Stripe',
+ status: r.fulfilled ? 'PAID' : 'PENDING',
+ createdAt: new Date(r.timestamp),
+ amount: Number(r.amount),
+ concept: String(r.product_name || '—'),
+ }))
+ } catch {
+ return []
+ }
+}