"use client"; import Link from "next/link"; import { type FormEvent, useEffect, useMemo, useState } from "react"; import { readDemoClientAuth } from "@/lib/auth/clientAuth"; import { supabaseBrowser } from "@/lib/supabase/browser"; type EventItem = { id: string; title: string; date: string; startTime: string; endTime: string; mode: string; location: string; summary: string; details: string; thumbnail: string; }; type EventDraft = { title: string; date: string; startTime: string; endTime: string; mode: string; location: string; summary: string; details: string; thumbnail: string; }; const EVENTS_STORAGE_KEY = "acve.custom-events.v1"; const defaultThumbnail = "https://images.unsplash.com/photo-1511578314322-379afb476865?auto=format&fit=crop&w=1200&q=80"; const defaultEvents: EventItem[] = [ { id: "launch-day-2026-03-20", title: "Launch Day ACVE", date: "2026-03-20", startTime: "18:00", endTime: "20:30", mode: "Híbrido", location: "Monterrey", summary: "Presentación oficial de ACVE, agenda académica y networking inicial.", details: "Sesión inaugural con bienvenida institucional, visión del programa 2026 y espacio de networking para alumnos, docentes e invitados del sector legal.", thumbnail: "https://images.unsplash.com/photo-1528605248644-14dd04022da1?auto=format&fit=crop&w=1200&q=80", }, { id: "webinar-drafting-2026-03-27", title: "Webinar: Legal Drafting in English", date: "2026-03-27", startTime: "19:00", endTime: "20:15", mode: "Online", location: "Zoom ACVE", summary: "Buenas prácticas para redactar cláusulas con claridad y precisión.", details: "Revisión de estructura, vocabulario funcional y errores frecuentes en contratos internacionales. Incluye sesión breve de preguntas al final.", thumbnail: "https://images.unsplash.com/photo-1543269865-cbf427effbad?auto=format&fit=crop&w=1200&q=80", }, { id: "qa-session-2026-04-05", title: "Q&A de Cohorte", date: "2026-04-05", startTime: "17:30", endTime: "18:30", mode: "Streaming", location: "Campus Virtual ACVE", summary: "Resolución de dudas académicas y guía de estudio para la siguiente unidad.", details: "Encuentro en vivo para alinear progreso de la cohorte, resolver dudas de contenido y compartir recomendaciones de práctica.", thumbnail: "https://images.unsplash.com/photo-1515169067868-5387ec356754?auto=format&fit=crop&w=1200&q=80", }, { id: "workshop-monterrey-2026-04-18", title: "Workshop Presencial", date: "2026-04-18", startTime: "10:00", endTime: "13:00", mode: "Presencial", location: "Monterrey", summary: "Simulación de negociación y revisión colaborativa de cláusulas.", details: "Taller práctico con actividades por equipos para aplicar vocabulario jurídico en contexto de negociación y redacción de términos clave.", thumbnail: "https://images.unsplash.com/photo-1475721027785-f74eccf877e2?auto=format&fit=crop&w=1200&q=80", }, { id: "networking-night-2026-05-02", title: "Networking Night", date: "2026-05-02", startTime: "19:30", endTime: "21:00", mode: "Híbrido", location: "Monterrey + Online", summary: "Conexión entre estudiantes, alumni y profesores ACVE.", details: "Espacio informal para compartir experiencias, oportunidades de colaboración y avances en el uso profesional del inglés legal.", thumbnail: "https://images.unsplash.com/photo-1528909514045-2fa4ac7a08ba?auto=format&fit=crop&w=1200&q=80", }, ]; const blankDraft: EventDraft = { title: "", date: "", startTime: "18:00", endTime: "19:00", mode: "Online", location: "Monterrey", summary: "", details: "", thumbnail: "", }; function parseDateInUtc(value: string): Date { const [year, month, day] = value.split("-").map(Number); return new Date(Date.UTC(year, month - 1, day)); } function sortEvents(items: EventItem[]): EventItem[] { return [...items].sort((a, b) => { if (a.date !== b.date) return a.date.localeCompare(b.date); return a.startTime.localeCompare(b.startTime); }); } function formatCardDate(value: string) { const date = parseDateInUtc(value); return { day: new Intl.DateTimeFormat("es-MX", { day: "2-digit", timeZone: "UTC" }).format(date), month: new Intl.DateTimeFormat("es-MX", { month: "short", timeZone: "UTC" }).format(date).toUpperCase(), }; } function formatLongDate(value: string): string { return new Intl.DateTimeFormat("es-MX", { weekday: "long", day: "numeric", month: "long", year: "numeric", timeZone: "UTC", }).format(parseDateInUtc(value)); } function isEventItem(value: unknown): value is EventItem { if (!value || typeof value !== "object") return false; const record = value as Record; return ( typeof record.id === "string" && typeof record.title === "string" && typeof record.date === "string" && typeof record.startTime === "string" && typeof record.endTime === "string" && typeof record.mode === "string" && typeof record.location === "string" && typeof record.summary === "string" && typeof record.details === "string" && typeof record.thumbnail === "string" ); } export default function EventosPage() { const [selectedEvent, setSelectedEvent] = useState(null); const [addedEvents, setAddedEvents] = useState([]); const [draft, setDraft] = useState(blankDraft); const [formError, setFormError] = useState(null); const [isTeacher, setIsTeacher] = useState(false); const [isCheckingRole, setIsCheckingRole] = useState(true); const orderedEvents = useMemo(() => sortEvents([...defaultEvents, ...addedEvents]), [addedEvents]); useEffect(() => { if (typeof window === "undefined") return; try { const stored = window.localStorage.getItem(EVENTS_STORAGE_KEY); if (!stored) return; const parsed = JSON.parse(stored); if (Array.isArray(parsed)) { setAddedEvents(parsed.filter(isEventItem)); } } catch { setAddedEvents([]); } }, []); useEffect(() => { if (typeof window === "undefined") return; window.localStorage.setItem(EVENTS_STORAGE_KEY, JSON.stringify(sortEvents(addedEvents))); }, [addedEvents]); useEffect(() => { let mounted = true; const demoSnapshot = readDemoClientAuth(); const client = supabaseBrowser(); if (!client) { setIsTeacher(demoSnapshot.isTeacher); setIsCheckingRole(false); return; } const resolveTeacherRole = async () => { try { const { data: { user }, } = await client.auth.getUser(); if (!mounted) return; if (!user) { setIsTeacher(demoSnapshot.isTeacher); return; } const response = await fetch("/api/auth/session", { cache: "no-store" }); if (!mounted) return; const payload = (await response.json()) as { isTeacher?: boolean }; setIsTeacher(payload.isTeacher === true || demoSnapshot.isTeacher); } catch { if (mounted) setIsTeacher(demoSnapshot.isTeacher); } finally { if (mounted) setIsCheckingRole(false); } }; void resolveTeacherRole(); const { data: { subscription }, } = client.auth.onAuthStateChange(() => { void resolveTeacherRole(); }); return () => { mounted = false; subscription.unsubscribe(); }; }, []); const handleAddEvent = (event: FormEvent) => { event.preventDefault(); if (!isTeacher) { setFormError("Solo docentes pueden agregar eventos."); return; } const required = [draft.title, draft.date, draft.startTime, draft.endTime, draft.mode, draft.location, draft.summary, draft.details]; if (required.some((field) => field.trim().length === 0)) { setFormError("Completa todos los campos obligatorios para publicar el evento."); return; } setAddedEvents((previous) => [ ...previous, { id: typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}`, title: draft.title.trim(), date: draft.date, startTime: draft.startTime, endTime: draft.endTime, mode: draft.mode.trim(), location: draft.location.trim(), summary: draft.summary.trim(), details: draft.details.trim(), thumbnail: draft.thumbnail.trim() || defaultThumbnail, }, ]); setDraft(blankDraft); setFormError(null); }; return (

Eventos ACVE

Calendario académico y networking

Eventos ordenados cronológicamente. Haz clic en cualquier tarjeta para ver el detalle completo del evento.

{orderedEvents.map((eventCard) => { const cardDate = formatCardDate(eventCard.date); return ( ); })}
Ver sección de eventos en Inicio

Gestión de eventos

Solo el equipo docente puede publicar nuevos eventos en este tablero.

{isCheckingRole ? (

Verificando permisos...

) : null} {!isCheckingRole && !isTeacher ? (

Tu perfil actual no tiene permisos de publicación. Si eres docente, inicia sesión con tu cuenta de profesor.

) : null} {!isCheckingRole && isTeacher ? (
setDraft((previous) => ({ ...previous, title: event.target.value }))} /> setDraft((previous) => ({ ...previous, date: event.target.value }))} /> setDraft((previous) => ({ ...previous, startTime: event.target.value }))} /> setDraft((previous) => ({ ...previous, endTime: event.target.value }))} /> setDraft((previous) => ({ ...previous, mode: event.target.value }))} /> setDraft((previous) => ({ ...previous, location: event.target.value }))} />