diff --git a/prisma/migrations/20260905010000_add_general_bug_feedback_kind/migration.sql b/prisma/migrations/20260905010000_add_general_bug_feedback_kind/migration.sql
new file mode 100644
index 0000000..209f120
--- /dev/null
+++ b/prisma/migrations/20260905010000_add_general_bug_feedback_kind/migration.sql
@@ -0,0 +1,7 @@
+-- "Reportar un bug" (rail y perfil) dejó de apuntar a GitHub y ahora abre el
+-- mismo widget de Feedback que discrepancia/confuso/idea/elogio. Necesita su
+-- propio valor de kind para no mezclarse con "other".
+--
+-- ALTER TYPE ... ADD VALUE es seguro (idempotente con IF NOT EXISTS) y el
+-- valor nuevo no se usa en esta misma migración.
+ALTER TYPE "FeedbackKind" ADD VALUE IF NOT EXISTS 'bug';
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index ad13f3c..c522cfa 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -844,6 +844,13 @@ enum FeedbackKind {
* contenido y la clase real, o "esta fue una mala unidad/lección".
*/
discrepancy
+ /**
+ * Error técnico GENERAL, no atado a un paso/ejercicio concreto (para eso
+ * está `BugReport`): "no puedo iniciar sesión", "esta página no carga".
+ * Es el destino del link "Reportar un bug" del rail y del perfil — antes
+ * apuntaba a GitHub, que la mayoría de los alumnos no sabe usar.
+ */
+ bug
/**
* "no entendí esto"
*/
diff --git a/src/app/app/(global)/perfil/page.tsx b/src/app/app/(global)/perfil/page.tsx
index 47d28cc..c0bfb40 100644
--- a/src/app/app/(global)/perfil/page.tsx
+++ b/src/app/app/(global)/perfil/page.tsx
@@ -21,6 +21,7 @@ import { AchievementsSection } from "@/features/profile/components/achievements-
import { ChangePasswordDialog } from "@/features/profile/components/change-password-dialog";
import { DeleteAccountDialog } from "@/features/profile/components/delete-account-dialog";
import { SignOutButton } from "@/features/profile/components/sign-out-button";
+import { ReportBugButton } from "@/features/feedback/components/report-bug-button";
export const metadata = {
title: "Mi perfil",
@@ -198,16 +199,11 @@ export default async function PerfilPage() {
- ¿Encontraste un bug?{" "}
-
- Repórtalo en GitHub
-
- .
+ ¿Encontraste un bug o algo que no corresponde con tu clase?{" "}
+
+ Repórtalo aquí
+ {" "}
+ — no hace falta GitHub.
);
diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx
index 514068d..1a10d44 100644
--- a/src/components/layout/sidebar.tsx
+++ b/src/components/layout/sidebar.tsx
@@ -1,11 +1,11 @@
import Link from "next/link";
-import { Bug } from "lucide-react";
import { Logo } from "@/components/shared/logo";
import { UNOFFICIAL_NOTICE } from "@/lib/branding";
import { SidebarNav } from "@/components/layout/sidebar-nav";
import type { CourseSwitcherItem } from "@/features/courses/components/course-switcher";
import { FeedbackRailButton } from "@/features/feedback/components/feedback-rail-button";
+import { ReportBugRailRow } from "@/features/feedback/components/report-bug-button";
import type { RoadmapUnit } from "@/features/roadmap/types";
/**
@@ -52,18 +52,11 @@ export function Sidebar({
{/* Feedback general de la experiencia. Para contenido roto está el
- botón de reporte dentro de la propia lección/ejercicio. */}
+ botón de reporte dentro de la propia lección/ejercicio. Ninguno
+ de los dos manda a GitHub: la mayoría de los alumnos no sabe
+ qué es un issue y no lo abriría. */}
-
-
- Reportar un bug
- v0.1
-
+
{UNOFFICIAL_NOTICE}
diff --git a/src/components/layout/user-menu.tsx b/src/components/layout/user-menu.tsx
index 9b45aa1..77c1524 100644
--- a/src/components/layout/user-menu.tsx
+++ b/src/components/layout/user-menu.tsx
@@ -3,6 +3,7 @@
import * as React from "react";
import { useRouter } from "next/navigation";
import {
+ Bug,
Dumbbell,
LogOut,
MessageSquarePlus,
@@ -56,6 +57,9 @@ export function UserMenu({
}: UserMenuProps) {
const router = useRouter();
const [feedbackOpen, setFeedbackOpen] = React.useState(false);
+ const [feedbackKind, setFeedbackKind] = React.useState<"confusing" | "bug">(
+ "confusing",
+ );
const initials = user.name
.split(" ")
.map((n) => n[0])
@@ -154,12 +158,23 @@ export function UserMenu({
// El menú se cierra al elegir; abrimos el diálogo aparte para que
// no se desmonte con él.
event.preventDefault();
+ setFeedbackKind("confusing");
setFeedbackOpen(true);
}}
>
Enviar comentario
+ {
+ event.preventDefault();
+ setFeedbackKind("bug");
+ setFeedbackOpen(true);
+ }}
+ >
+
+ Reportar un bug
+
{isAdmin ? (
router.push("/app/admin")}>
@@ -172,7 +187,11 @@ export function UserMenu({
Cerrar sesión
-
+
);
}
diff --git a/src/features/admin/components/triage-row.tsx b/src/features/admin/components/triage-row.tsx
index 0440a80..17dce97 100644
--- a/src/features/admin/components/triage-row.tsx
+++ b/src/features/admin/components/triage-row.tsx
@@ -25,6 +25,7 @@ const STATUSES: { value: ReportStatusInput; label: string }[] = [
const FEEDBACK_KIND_LABEL: Record = {
discrepancy: "Discrepancia con clase",
+ bug: "Error técnico (general)",
confusing: "Confuso",
idea: "Idea",
praise: "Elogio",
@@ -79,7 +80,9 @@ export function TriageRow({ item }: { item: TriageItem }) {
=> {
diff --git a/src/features/feedback/components/feedback-dialog.tsx b/src/features/feedback/components/feedback-dialog.tsx
index 6b34416..4d8540a 100644
--- a/src/features/feedback/components/feedback-dialog.tsx
+++ b/src/features/feedback/components/feedback-dialog.tsx
@@ -4,6 +4,7 @@ import * as React from "react";
import { usePathname } from "next/navigation";
import {
AlertTriangle,
+ Bug,
Lightbulb,
MessageSquarePlus,
Send,
@@ -27,7 +28,7 @@ import { submitFeedback } from "@/features/feedback/actions";
import { FEEDBACK_MAX_LENGTH } from "@/features/feedback/context";
import { cn } from "@/lib/utils";
-type Kind = "discrepancy" | "confusing" | "idea" | "praise";
+type Kind = "discrepancy" | "bug" | "confusing" | "idea" | "praise";
const KINDS: { value: Kind; label: string; icon: typeof HelpCircle }[] = [
{
@@ -35,6 +36,7 @@ const KINDS: { value: Kind; label: string; icon: typeof HelpCircle }[] = [
label: "No corresponde con mi clase",
icon: AlertTriangle,
},
+ { value: "bug", label: "Encontré un error técnico", icon: Bug },
{ value: "confusing", label: "Algo me confundió", icon: HelpCircle },
{ value: "idea", label: "Tengo una idea", icon: Lightbulb },
{ value: "praise", label: "Algo me gustó", icon: Sparkles },
@@ -45,11 +47,18 @@ const COPY: Record<
{ title: string; description: string; placeholder: string }
> = {
discrepancy: {
- title: "Reportar una discrepancia",
+ title: "Reportar una discrepancia con tu clase",
description:
- '¿Esta lección o unidad no corresponde con lo que viste en tu clase, o te pareció una mala sesión? Cuéntanos qué no cuadra — ya sabemos en qué pantalla estás, no hace falta que lo expliques.',
+ "¿Esta lección o unidad no corresponde con lo que tu profe está dando en clase, con el orden de tu plantel, o con el temario oficial? Es especialmente útil en semestres avanzados, donde casi no hay referencia práctica más allá del temario — y no todos los planteles lo siguen igual. Cuéntanos qué no cuadra; ya sabemos en qué pantalla estás.",
placeholder:
- 'Ej: "En mi clase vimos primero los arreglos y aquí aparecen hasta después de POO."',
+ 'Ej: "En mi plantel ya vimos punteros antes que arreglos, y aquí van después."',
+ },
+ bug: {
+ title: "Reportar un error técnico",
+ description:
+ "¿Algo no carga, se traba, o no funciona como debería? No hace falta GitHub ni saber qué es un issue: cuéntanos qué pasó y lo revisamos. Ya sabemos en qué pantalla estás.",
+ placeholder:
+ 'Ej: "No puedo iniciar sesión con Google, se queda cargando."',
},
confusing: {
title: "¿Cómo te está yendo?",
@@ -73,14 +82,16 @@ const COPY: Record<
/**
* Feedback general sobre la experiencia — incluye reportar que el contenido
- * NO corresponde con la clase real, o que una lección/unidad fue mala. El
- * contexto (dónde estaba el alumno) viaja solo: mandamos la ruta y el
+ * NO corresponde con la clase real (o que una lección/unidad fue mala) y
+ * reportar un error técnico general (login, una página que no carga, etc.).
+ * El contexto (dónde estaba el alumno) viaja solo: mandamos la ruta y el
* servidor la interpreta. No le pedimos que explique en qué pantalla estaba.
*
- * Para contenido roto (typo, test mal configurado) existe `ReportBugDialog`,
- * que sí apunta a un paso/ejercicio concreto. Ninguno de los dos crea un
- * issue de GitHub: caen en la cola de triage interna (`/app/admin/reportes`)
- * y un admin decide si vale la pena abrir uno.
+ * Para contenido roto atado a un paso/ejercicio concreto (typo, test mal
+ * configurado) existe `ReportBugDialog`. Ninguno de los dos crea un issue de
+ * GitHub — la mayoría de los alumnos no sabe qué es eso y no lo usaría: caen
+ * en la cola de triage interna (`/app/admin/reportes`) y un admin decide si
+ * vale la pena normalizarlo como issue real.
*/
export function FeedbackDialog({
children,
diff --git a/src/features/feedback/components/report-bug-button.tsx b/src/features/feedback/components/report-bug-button.tsx
new file mode 100644
index 0000000..ea31051
--- /dev/null
+++ b/src/features/feedback/components/report-bug-button.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import * as React from "react";
+import { Bug } from "lucide-react";
+
+import { FeedbackDialog } from "@/features/feedback/components/feedback-dialog";
+import { cn } from "@/lib/utils";
+
+/**
+ * "Reportar un bug" GENERAL (login, una página que no carga, etc.), sin
+ * pasar por GitHub — la mayoría de los alumnos no sabe qué es un issue y no
+ * lo abriría. Reemplaza el link que antes salía a
+ * `github.com/.../issues` desde el rail y desde el perfil.
+ *
+ * Para contenido roto atado a un paso/ejercicio concreto está
+ * `ReportBugDialog`, que vive dentro de la propia lección.
+ */
+export function ReportBugButton({
+ className,
+ children,
+}: {
+ className?: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+ );
+}
+
+/** Fila lista para usar en el rail: ícono + texto, mismo look que antes. */
+export function ReportBugRailRow() {
+ return (
+
+
+ Reportar un bug
+ v0.1
+
+ );
+}
diff --git a/src/features/feedback/components/report-discrepancy-button.tsx b/src/features/feedback/components/report-discrepancy-button.tsx
index 8d7e0d2..dad6646 100644
--- a/src/features/feedback/components/report-discrepancy-button.tsx
+++ b/src/features/feedback/components/report-discrepancy-button.tsx
@@ -13,7 +13,11 @@ import { FeedbackDialog } from "@/features/feedback/components/feedback-dialog";
*
* Vive junto al `ReportBugDialog` en la cabecera de la lección y en la
* página de unidad: el alumno lo ve exactamente donde nota el problema, sin
- * tener que buscarlo en un menú aparte.
+ * tener que buscarlo en un menú aparte. Estilo llamativo (no `ghost`) a
+ * propósito — un ícono apagado entre otros íconos apagados nunca se nota, y
+ * esto es justo lo que más nos importa saber en semestres avanzados, donde
+ * el temario oficial casi no tiene referencia práctica y cada plantel lo
+ * sigue distinto.
*/
export function ReportDiscrepancyButton() {
return (
@@ -21,11 +25,13 @@ export function ReportDiscrepancyButton() {
);