diff --git a/web-next/app/[locale]/changelogs/page.tsx b/web-next/app/[locale]/changelogs/page.tsx
new file mode 100644
index 0000000..194cd7b
--- /dev/null
+++ b/web-next/app/[locale]/changelogs/page.tsx
@@ -0,0 +1,67 @@
+import type { Metadata } from 'next'
+import { setRequestLocale } from 'next-intl/server'
+import { getCommits } from '@/lib/changelog'
+
+export const metadata: Metadata = { title: 'Changelogs' }
+
+function formatFecha(iso: string): string {
+ if (!iso) return ''
+ const d = new Date(iso)
+ if (isNaN(d.getTime())) return iso
+ return d.toLocaleString('es-ES', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ })
+}
+
+export default async function ChangelogsPage({ params }: { params: Promise<{ locale: string }> }) {
+ const { locale } = await params
+ setRequestLocale(locale)
+ const commits = await getCommits(50)
+
+ return (
+
+
+
+
+
Changelogs
+
+
+
+ {commits.length === 0 ? (
+
No se pudieron cargar los cambios en este momento.
+ ) : (
+ <>
+ {commits.map((c) => (
+
+
{c.title}
+
{formatFecha(c.date)}
+
+ {c.author}
+ {' · '}
+ {c.shortSha}
+
+ {c.body && (
+
{c.body}
+ )}
+
+
+
+
+ ))}
+
+ * Mostrando los últimos 50 cambios
+
+
+ >
+ )}
+
+
+
+
+
+ )
+}
diff --git a/web-next/lib/changelog.ts b/web-next/lib/changelog.ts
new file mode 100644
index 0000000..68ba9e6
--- /dev/null
+++ b/web-next/lib/changelog.ts
@@ -0,0 +1,55 @@
+// Changelog desde la API de Gitea (commits del repo).
+const GITEA = process.env.GITEA_URL || 'https://git.nightspire.gg'
+const REPO = process.env.GITEA_REPO || 'Inna/NovaWoW'
+
+export interface Commit {
+ sha: string
+ shortSha: string
+ title: string
+ body: string
+ author: string
+ date: string
+ url: string
+}
+
+interface GiteaCommit {
+ sha: string
+ html_url: string
+ created?: string
+ commit?: { message?: string; author?: { name?: string; date?: string } }
+ author?: { login?: string } | null
+}
+
+export async function getCommits(limit = 50): Promise {
+ try {
+ const res = await fetch(`${GITEA}/api/v1/repos/${REPO}/commits?limit=${limit}`, {
+ headers: { Accept: 'application/json' },
+ // Cache corto para no golpear Gitea en cada visita.
+ next: { revalidate: 300 },
+ })
+ if (!res.ok) return []
+ const data = (await res.json()) as GiteaCommit[]
+ return data.map((c) => {
+ const msg = c.commit?.message ?? ''
+ const lines = msg.split('\n')
+ const title = lines[0].trim()
+ // Cuerpo sin la primera línea y sin los trailers (Co-Authored-By, etc.).
+ const body = lines
+ .slice(1)
+ .filter((l) => !/^Co-Authored-By:/i.test(l.trim()) && !/Generated with \[?Claude/i.test(l))
+ .join('\n')
+ .trim()
+ return {
+ sha: c.sha,
+ shortSha: c.sha.slice(0, 7),
+ title,
+ body,
+ author: c.commit?.author?.name || c.author?.login || '',
+ date: c.commit?.author?.date || c.created || '',
+ url: c.html_url,
+ }
+ })
+ } catch {
+ return []
+ }
+}