// src/pages-main.jsx — Presentación, Equipo, Noticias /* ── Diccionarios ES→EN para datos que viven en data.js ── (clave = string español exacto → inglés; si falta, se muestra el español) */ const STATS_LABELS_EN = { 'Colegios miembros': 'Member schools', 'Regiones del Perú': 'Regions of Peru', 'Mundiales consecutivos': 'Consecutive world titles', 'Año de fundación': 'Year founded', }; const MARQUEE_EN = { 'Tricampeones Mundiales': 'Three-Time World Champions', 'Lima 2018 — fundación': 'Founded in Lima · 2018', '103 colegios miembros': '103 member schools', '11 regiones': '11 regions', 'Selección Nacional': 'National Team', 'Educación Continua': 'Continuing education', 'Argumentar · Pensar · Convencer': 'Argue · Think · Persuade', }; /* ═══════════════════════════════════════════════════════════ PRESENTACIÓN (Home) ═══════════════════════════════════════════════════════════ */ const PresentacionPage = ({ setPage }) => { const D = window.LPDE_DATA; const { t, lang } = useLang(); return ( {/* ─── HERO ──────────────────────────────────────── */}
{t('Desde 2018 · Lima · Perú', 'Since 2018 · Lima · Peru')}

{t('Argumentar', 'Argument')}
{t('da sentido al', 'gives meaning to')}
{t('mundo que ', 'the world we ')}{t('habitamos', 'inhabit')}

{t('Somos la ', 'We are the ')}{t('Liga Peruana de Debate Escolar', 'Peruvian School Debate League (LPDE)')}{t(' — la red más grande de debate académico del Perú. 103 colegios, 11 regiones, tres títulos mundiales consecutivos.', ' — Peru’s largest academic debate network. 103 schools, 11 regions, three consecutive world titles.')}

{/* ─── Marquee ──────────────────────────────────── */} MARQUEE_EN[m] || m) : D.marquee} /> {/* ─── Stats ─────────────────────────────────────── */}
{t('La Liga en números', 'The League in numbers')}

{t('Casi una década formando a las próximas ', 'Nearly a decade shaping Peru’s next generation of ')}{t('voces críticas', 'critical voices')}{t(' del país.', '.')}

{t('Datos actualizados a marzo 2026.', 'Data as of March 2026.')}

{D.stats.map((s, i) =>
{s.it ? : }
{lang === 'en' ? (STATS_LABELS_EN[s.label] || s.label) : s.label}
)}
{/* ─── Quiénes somos ─────────────────────────────── */}
{t('Estudiantes {/* Máscara oscura para legibilidad del texto */}

{t('“Debatir nos enseña que podemos equivocarnos y que otros también tienen razones para pensar distinto.”', '“Debate teaches us that we can be wrong — and that others have their own reasons to think differently.”')}

{t('Quiénes somos', 'Who we are')}

{t('Una comunidad ', 'An ')}{t('académica', 'academic')}{t(', no un torneo.', ' community, not a tournament.')}

{t('Fundada en 2018 por una red de profesores y debatientes, la LPDE existe para algo más que competir: garantizar que cualquier estudiante peruano —sin importar su lugar de procedencia— pueda aprender a comprometerse con sus propias ideas.', 'Founded in 2018 by a network of teachers and debaters, the LPDE exists for more than competition: to ensure that every Peruvian student — wherever they come from — can learn to stand behind their own ideas.')}

{t('Operamos a través de Metadidactas, la consultora de la LPDE. Capacitamos a profesores, organizamos torneos abiertos, publicamos materiales gratuitos y representamos al Perú en el Mundial Escolar de Debate (MED).', 'We operate through Metadidactas, the LPDE’s consulting arm. We train teachers, run open tournaments, publish free materials and represent Peru at the Spanish-language World Schools Debating Championship (MED).')}

{/* ─── Historia / timeline ───────────────────────── */}
{t('Historia', 'Our history')}

{t('De la fundación al ', 'From our founding to ')}{t('tricampeonato mundial', 'three straight world titles')}.

{[ { y: '2018', t: t('Se funda la LPDE', 'The LPDE is founded'), d: t('Una red de profesores y debatientes firma el acta fundacional.', 'A network of teachers and debaters signs the founding charter.') }, { y: '2021', t: t('TNDE en inglés', 'TNDE in English'), d: t('El TNDE incorpora oficialmente la división en inglés, junto a la categoría en español.', 'The TNDE officially adds an English division alongside the Spanish category.') }, { y: '2023', t: t('Primer Descentralizado', 'First regional tournament'), d: t('La LPDE realiza su primer Torneo Descentralizado fuera de Lima, en Juliaca, Puno.', 'The LPDE holds its first Decentralized Tournament outside Lima, in Juliaca, Puno.') }, { y: '2024', t: t('Se funda la SNDE', 'The SNDE is founded'), d: t('Nace la Selección Nacional de Debate Escolar como cuerpo permanente.', 'The National School Debate Team (SNDE) is established as a permanent body.') }, { y: '2023—25', t: t('Tricampeonato MED', 'Three MED titles in a row'), d: t('San Clemente (2023), Selección Nacional en Panamá (2024) y en Chile (2025).', 'San Clemente (2023), the National Team in Panama (2024) and in Chile (2025).') }]. map((e, i) => { const last = i === 4; return (
{e.y}

{e.t}

{e.d}

); })}
{/* ─── Lo que hacemos ────────────────────────────── */}
{t('Lo que hacemos', 'What we do')}

{t('Cuatro frentes,', 'Four fronts,')}
{t('una misma ', 'one shared ')}{t('misión', 'mission')}

{t('Construir, año tras año, una cancha donde cualquier estudiante peruano pueda jugar a pensar en serio.', 'To build, year after year, a playing field where every Peruvian student can practice serious thinking.')}

{[ { n: '01', t: t('Torneos', 'Tournaments'), tag: t('Competencia', 'Competition'), stat: t('Calidad', 'Quality'), statLabel: t('académica', 'academic'), d: t('TNDE, circuitos regionales y abiertos. Eventos presenciales y virtuales para todos.', 'The TNDE, regional circuits and open events. In person and online, open to everyone.'), cta: () => setPage('tnde'), variant: 'photo', img: 'https://images.unsplash.com/photo-1540575467063-178a50c2df87?w=1200&auto=format&q=80' }, { n: '02', t: t('Selección Nacional', 'National Team'), tag: t('Comunidad', 'Community'), stat: '3×', statLabel: t('Tricampeones MED', 'Three-time MED champions'), d: t('Preparamos a los seis representantes peruanos para el Mundial Escolar de Debate (MED).', 'We prepare Peru’s six representatives for the Spanish-language World Schools Debating Championship (MED).'), cta: () => setPage('snde'), variant: 'red' }, { n: '03', t: t('Formación', 'Training'), tag: 'Metadidactas', stat: '400+', statLabel: t('Egresados', 'Alumni'), d: t('Programa especializado para profesores y debatientes. Operado por Metadidactas.', 'Specialized programs for teachers and debaters, run by Metadidactas.'), cta: () => setPage('formacion'), variant: 'cream' }, { n: '04', t: t('Publicaciones', 'Publications'), tag: t('Acceso libre', 'Open access'), stat: '∞', statLabel: t('Libre para todos', 'Free for everyone'), d: t('Información libre para todos: manuales del juez, guías curriculares y libros de argumentación.', 'Free knowledge for everyone: adjudication manuals, curriculum guides and books on argumentation.'), cta: () => setPage('noticias'), variant: 'outline' }]. map((f, i) => )}
{/* ─── TNDE spotlight ──────────────── */}
{t('Próximo evento', 'Next event')}
{t('Emblema
{/* Date block */}
{t('TNDE · XII Edición', 'TNDE · 12th Edition')}
8–11
{t('AGO', 'AUG')} 2026
{t('SÁB → MAR · 8, 9, 10 y 11 AGO', 'SAT → TUE · AUG 8, 9, 10 & 11')}
{/* Title + description */}

{t('Torneo Nacional', 'National School')}
{t('de ', '')}{t('Debate Escolar', 'Debate Tournament')}

{t('La XII edición del torneo bandera de la LPDE. Cuatro días con cientos de adolescentes de todo el país pensando juntos en problemas del mundo real, en español y en inglés.', 'The 12th edition of the LPDE’s flagship tournament. Four days of hundreds of teenagers from across Peru thinking together about real-world problems, in Spanish and in English.')}

{t('Sede', 'Venue')}
TBA
{t('Formato', 'Format')}
{t('WSDC · presencial', 'WSDC · in person')}
{t('Vacantes', 'Slots')}
TBA
{/* CTA + emblema grande */}
{t('Emblema
{/* ─── Calendario · próximos eventos (dinámico) ──── */} {/* ─── Colegios miembros (teaser) ─────────────────── */}
{/* Left: copy */}
{t('Colegios miembros', 'Member schools')}

{t('Cien colegios.', 'One hundred schools.')}
{t('Un solo ', 'One shared ')}{t('campo de juego', 'playing field')}

{t('Desde colegios privados en Lima Metropolitana hasta escuelas públicas en la sierra centro, los Andes del sur y la costa norte. La red de la LPDE reúne hoy a colegios de ', 'From private schools in Metropolitan Lima to public schools in the central highlands, the southern Andes and the northern coast. The LPDE network now brings together schools from ')}{t('11 regiones', '11 regions')}{t(' y ', ' and ')}{t('44 ciudades y distritos', '44 cities and districts')}{t(' del país.', ' across Peru.')}

{t('Miembros', 'Members')}
{t('Regiones', 'Regions')}
{t('Públicos', 'Public')}
{/* Right: stylized preview map */}
setPage('colegios')} style={{ cursor: 'pointer', background: 'var(--bg-alt)', border: '1px solid var(--line)', padding: 32, position: 'relative', transition: 'all 280ms ease' }} onMouseEnter={(e) => { e.currentTarget.style.transform = 'translateY(-4px)'; e.currentTarget.style.boxShadow = '0 16px 40px rgba(0,0,0,0.08)'; }} onMouseLeave={(e) => { e.currentTarget.style.transform = 'translateY(0)'; e.currentTarget.style.boxShadow = 'none'; }} >
{t('Mapa interactivo · vista previa', 'Interactive map · preview')}
{[ { x: 28, y: 56, r: 13 }, { x: 56, y: 68, r: 5 }, { x: 50, y: 82, r: 5 }, { x: 30, y: 32, r: 3 }, { x: 20, y: 22, r: 3 }, { x: 42, y: 56, r: 3 }, { x: 24, y: 30, r: 3 }, { x: 30, y: 24, r: 3 }, { x: 56, y: 18, r: 2 }, { x: 62, y: 76, r: 2 }, { x: 32, y: 70, r: 2 }, { x: 30, y: 42, r: 2 }, ].map((c, i) => ( ))}
Lima · 78 {t('Provincia · 25', 'Outside Lima · 25')}
{/* ─── Metadidactas spotlight ──────────────────── */}
{t('Proyecto hermano', 'Sister project')}

{t('La plataforma de ', 'The LPDE’s ')}e-learning{t(' de la LPDE', ' platform')}

{/* Left — branded visual */}
{/* Giant M */}
{t('Consultora de la LPDE', 'The LPDE’s consulting arm')}
meta
didactas

{t('Plataforma de educación continua en argumentación, debate y pensamiento crítico para Latinoamérica.', 'A continuing education platform in argumentation, debate and critical thinking for Latin America.')}

{t('Metadidactas · Educación continua', 'Metadidactas · Continuing education')}
{/* Right — story + ramas + CTAs */}

{t('“Lo que aprendimos formando debatientes durante años, hoy lo enseñamos a profesores y estudiantes.”', '“What we learned over years of training debaters, we now teach to teachers and students.”')}

{t('Metadidactas nace de la LPDE como plataforma de educación continua. Un equipo interdisciplinario —debatientes, filósofos, comunicadores, abogados, politólogos, pedagogos— diseña sus contenidos. No solo trabajamos debate: también filosofía y emprendimiento.', 'Metadidactas grew out of the LPDE as a continuing education platform. An interdisciplinary team — debaters, philosophers, communicators, lawyers, political scientists, educators — designs its content. We go beyond debate: we also teach philosophy and entrepreneurship.')}

{[ { label: 'Debate', sub: t('Argumentación, retórica, juzgamiento', 'Argumentation, rhetoric, adjudication') }, { label: 'FiloNautas', sub: t('Filosofía para escolares', 'Philosophy for school students') }, { label: 'Impulso', sub: t('Emprendimiento y proyectos', 'Entrepreneurship and projects') }, ].map((r, i) => (
{r.label}
{r.sub}
))}
{t('Ir a Metadidactas', 'Go to Metadidactas')}
{/* ─── Aliados ───────────────────────────────────── */}
{t('Aliados institucionales', 'Institutional partners')}

{t('Universidades y colegios que ', 'Universities and schools that ')}{t('apuestan', 'believe')}{t(' por el debate.', ' in debate.')}

{[ { src: 'assets/aliados/pucp.webp', name: 'PUCP' }, { src: 'assets/aliados/ulima.webp', name: 'Universidad de Lima' }, { src: 'assets/aliados/upc.webp', name: 'UPC' }, { src: 'assets/aliados/esan.webp', name: 'ESAN' }, { src: 'assets/aliados/markham2023.webp', name: 'Colegio Markham' }, { src: 'assets/aliados/salcantay.webp', name: 'Colegio Salcantay' }, { src: 'assets/aliados/trener.webp', name: 'Colegio Trener' }, { src: 'assets/aliados/aleph.webp', name: 'Colegio Áleph' }, { src: 'assets/aliados/cpl.webp', name: 'CPL' }, { src: 'assets/aliados/fil.webp', name: 'FIL' }, { src: 'assets/aliados/bicentenario.webp', name: 'Bicentenario Perú' }, { src: 'assets/aliados/logo.webp', name: t('Aliado institucional', 'Institutional partner') }, ].map((a, i) => (
{ e.currentTarget.querySelector('img').style.filter = 'grayscale(0) opacity(1)'; e.currentTarget.querySelector('img').style.transform = 'scale(1.05)'; }} onMouseLeave={(e) => { e.currentTarget.querySelector('img').style.filter = 'grayscale(1) opacity(0.65)'; e.currentTarget.querySelector('img').style.transform = 'scale(1)'; }} > {a.name}
))}
{/* ─── CTA banner ────────────────────────────────── */}

{t('¿Eres profesor, estudiante o colegio?', 'Are you a teacher, a student or a school?')}

{t('La participación en la LPDE está abierta todo el año. Te explicamos cómo puedes participar y qué beneficios puedes obtener.', 'Participation in the LPDE is open year-round. We’ll walk you through how to join and what you stand to gain.')}

); }; /* ═══════════════════════════════════════════════════════════ EQUIPO ═══════════════════════════════════════════════════════════ */ /* Traducciones EN de los datos del equipo (window.LPDE_DATA.team vive en data.js y se mantiene en español; aquí solo se sobreescribe al renderizar en inglés). */ const TEAM_EN = { 'Evangelina Beierbach': { role: 'Wellbeing Coordinator', short: 'Looks after the wellbeing and conduct of the National Team’s students and designs non-academic debate training sessions.', bio: 'Communication teacher at Colegio Trener since 2005. She entered the world of debate in 2012 and has accompanied the Trener Debate Society since 2014. Part of the LPDE since its early days, she helped launch the World Schools Debate Council and now serves on its General Secretariat. She supports the SNDE in the area of holistic development.', }, 'Felipe Zenteno Pacheco': { role: 'Academic Director', short: 'Leads the LPDE’s academic team. Designs and delivers training, coaching and consulting programs in school debate.', bio: 'Holds a bachelor’s degree in Philosophy from U.N.M.S.M. Debate teacher at Colegio Aleph and argumentation teacher at Colegio San Clemente. Coached the 2023 MED champion team and the 2023 TNDE champion.', }, 'Dante León Saavedra': { role: 'Operations Director', short: 'Leads the LPDE’s operations. Manages the finances, staff and logistics that sustain our programs and tournaments.', bio: 'Holds a degree in Pedagogy and is pursuing a master’s in Education at Tecnológico de Monterrey. Debate coach at St. George’s and teacher of Debate and Entrepreneurship at Colegio Salcantay. He also teaches at ESAN University (Extended Learning).', cvLabel: 'View LinkedIn', }, 'Sofía Pacheco': { role: 'Partnerships Director', short: 'Leads the LPDE’s institutional partnerships. Builds relationships with schools, universities, and public and private organizations.', bio: 'Lawyer from ESAN University and legal analyst at OEFA. Manager of school debate education projects. Served as Team Manager of the national team that won the 2024 and 2025 MED titles.', }, 'Nataly Vásquez Cabellos': { role: 'SNDE Coordinator', short: 'Coordinates the SNDE development team (students in grades 6 to 8).', bio: 'Psychologist from Universidad Peruana Cayetano Heredia, with experience in community social work. Holds graduate diplomas in Sexuality, Human Rights and Health, and in Education Policy (UPCH, 2025) and in University Teaching (UPCH, 2026). Works on the planning and delivery of educational projects in human rights, gender equality and climate change. Directs the Cayetano Heredia Debate Society — national runner-up in debate and national champion in speech — and is a co-founder of the consultancy SAMI: Salud Mental Integrada.', cvLabel: 'View LinkedIn', }, 'Mauricio Jarufe Caballero': { role: 'SNDE Coordinator', short: 'Coordinates the SNDE team (students in grades 9 to 11).', bio: 'Anthropologist from PUCP and film critic. Teaches debate at Colegio Alpamayo and Markham College. Three-time world champion in Spanish-language debate and best EFL speaker at the English-language world championship. Coach of Team Peru at WSDC and of the top-ranked team at the 2025 MED.', }, 'Luis Valverde': { role: 'Research Team', short: 'Leads research at the League. Produces diagnostics and data visualizations on the school debate circuit.', bio: 'Holds a degree in Political Science and Government from PUCP. Researcher at GIES PUCP with experience in academic research centers and consulting for public and private organizations. His research focuses on electoral behavior, institutional performance and education policy related to debate.', }, }; /* Etiquetas EN de los grupos de filtro (la lógica sigue usando las claves en español) */ const TEAM_GROUPS_EN = { 'Todos': 'All', 'Dirección': 'Leadership', 'Académico': 'Academic', 'Operaciones': 'Operations', 'Comunicaciones': 'Communications', }; const EquipoPage = () => { const D = window.LPDE_DATA; const { t, lang } = useLang(); const [selected, setSelected] = React.useState(null); const [filter, setFilter] = React.useState('Todos'); const localize = (m) => (lang === 'en' && TEAM_EN[m.name]) ? { ...m, ...TEAM_EN[m.name] } : m; const groupFor = (role) => { if (role.includes('Director') || role.includes('Dir.')) return 'Dirección'; if (role.includes('Académic') || role.includes('Coach') || role.includes('Educación') || role.includes('Jueces')) return 'Académico'; if (role.includes('Coordinador') || role.includes('Torneos') || role.includes('Regional')) return 'Operaciones'; if (role.includes('Comunic')) return 'Comunicaciones'; return 'Académico'; }; // Solo mostramos filtros con al menos un miembro (evita pestañas vacías con tarjetas "Por anunciar") const groups = ['Todos', ...['Dirección', 'Académico', 'Operaciones', 'Comunicaciones'].filter((g) => D.team.some((m) => groupFor(m.role) === g))]; const visible = filter === 'Todos' ? D.team : D.team.filter((m) => groupFor(m.role) === filter); return ( {/* Hero */}
{t('Conoce al equipo', 'Meet the team')}

{t('Las personas detrás de la ', 'The people behind the ')}{t('liga', 'League')}

{t('Profesionales —debatientes, profesores, abogados, politólogos, filósofos, comunicadores— sostienen la operación diaria de la LPDE. Haz clic en cualquier nombre para ver bio, CV y datos de contacto.', 'Professionals — debaters, teachers, lawyers, political scientists, philosophers, communicators — keep the LPDE running every day. Click on any name to see their bio, CV and contact details.')}

{/* Filters */}
{groups.map((g) => )}
{visible.length}{t(' miembros', ' members')}
{/* Grid */}
{visible.map((m, i) => setSelected(m)} idx={i} /> )} {visible.length < 4 && Array.from({ length: 4 - visible.length }).map((_, i) => (
?
{t('Por anunciar', 'To be announced')}

TBA

{t('Iremos publicando al resto del equipo en las próximas semanas.', 'We’ll be introducing the rest of the team in the coming weeks.')}

))}
setSelected(null)} />
); }; const TeamCard = ({ member, onClick, idx }) =>
{ e.currentTarget.querySelector('.tc-img').style.transform = 'scale(1.04)'; e.currentTarget.querySelector('.tc-arrow').style.transform = 'translate(4px, -4px)'; }} onMouseLeave={(e) => { e.currentTarget.querySelector('.tc-img').style.transform = 'scale(1)'; e.currentTarget.querySelector('.tc-arrow').style.transform = 'translate(0, 0)'; }}>
{member.name}
{member.role}

{member.name}

{member.short}

; const TeamModal = ({ member, onClose }) => { const { t } = useLang(); if (!member) return null; return (
{member.name}
{member.role}

{member.name}

{member.bio}

{member.email && (
{t('Correo', 'Email')}
{member.email}
)} {member.cv && member.cv !== '#' && (
{member.cvLabel ? t('Perfil', 'Profile') : t('Documentos', 'Documents')}
{member.cvLabel || t('Descargar CV', 'Download CV')}
)}
); }; /* ═══════════════════════════════════════════════════════════ NOTICIAS ═══════════════════════════════════════════════════════════ */ /* ── Noticias: almacenamiento (localStorage) + utilidades ─── El sitio es estático: la semilla pública vive en data.js y el admin trabaja sobre una copia en localStorage (por navegador). "Exportar JSON" permite publicar luego ese contenido para todos. */ const NEWS_LS_KEY = 'lpde_news_v1'; const NEWS_ADMIN_KEY = 'lpde_news_admin'; const NEWS_ADMIN_PASS = 'lpde2026'; function loadNews() { try { const raw = localStorage.getItem(NEWS_LS_KEY); if (raw) { const arr = JSON.parse(raw); if (Array.isArray(arr) && arr.length) return arr; } } catch (e) {} return (window.LPDE_DATA && window.LPDE_DATA.news) || []; } function saveNews(list) { try { localStorage.setItem(NEWS_LS_KEY, JSON.stringify(list)); } catch (e) { window.alert('No se pudo guardar en este navegador (probable límite por imágenes muy pesadas). Usa imágenes más livianas o exporta el JSON.'); } } function newsUid() { return 'n' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); } const newsCtlBtn = { padding: '5px 11px', borderRadius: 999, background: 'rgba(255,255,255,0.96)', border: '1px solid var(--line)', fontSize: 11, fontWeight: 700, fontFamily: 'var(--sans)', cursor: 'pointer', boxShadow: '0 2px 8px rgba(0,0,0,0.12)', color: 'var(--ink)' }; const NewsAdminControls = ({ onEdit, onDelete }) =>
; /* ── Crédito de autoría: "Equipo X — Nombre · Nombre" (o solo nombres) ── */ const newsByline = (news) => { const names = Array.isArray(news.authors) ? news.authors.filter(Boolean) : []; if (!names.length && !news.authorGroup) return null; return { group: news.authorGroup || '', names }; }; const NewsByline = ({ news, variant = 'inline' }) => { const b = newsByline(news); if (!b) return null; const namesStr = b.names.join(' · '); if (variant === 'mini') { return (
{b.group ? {b.group}{namesStr ? ' · ' : ''} : null} {namesStr}
); } return (
{b.group &&
{b.group}
} {namesStr &&
{namesStr}
}
); }; /* ── Render de un bloque de cuerpo (vista de lectura) ── */ const NewsBlock = ({ b }) => { if (!b) return null; if (b.t === 'h') return

{b.text}

; if (b.t === 'p') return

{b.text}

; if (b.t === 'stat') return (
{b.value}
{b.label}
); if (b.t === 'list') return (
    {(b.items || []).map((it, i) =>
  • {it}
  • )}
); if (b.t === 'img') return (
{b.caption {b.caption &&
{b.caption}
}
); return null; }; /* ── Lector de noticia (vista de visitante) ── */ const NewsReader = ({ news, onClose }) => { const { t } = useLang(); if (!news) return null; return (
{news.cover &&
{news.title}
}
{news.tag} {news.date}

{news.title}

{news.excerpt &&

{news.excerpt}

} {(news.body || []).map((b, i) => )} {(!news.body || news.body.length === 0) &&

{t('Esta noticia aún no tiene contenido.', 'This article has no content yet.')}

}
); }; /* ── Editor de noticia (vista de administrador) ── */ const NewsEditor = ({ item, onSave, onClose }) => { const blank = { tag: 'NOTICIA', date: '', title: '', excerpt: '', cover: '', featured: false, authorGroup: '', authors: [], body: [] }; const [d, setD] = React.useState(() => item ? JSON.parse(JSON.stringify(item)) : blank); const set = (k, v) => setD((s) => ({ ...s, [k]: v })); const readImage = (file, cb) => { if (!file) return; const r = new FileReader(); r.onload = () => cb(r.result); r.readAsDataURL(file); }; const addBlock = (t) => { const base = t === 'stat' ? { t, value: '', label: '' } : t === 'list' ? { t, items: [''] } : t === 'img' ? { t, src: '', caption: '' } : { t, text: '' }; setD((s) => ({ ...s, body: [...(s.body || []), base] })); }; const updBlock = (i, patch) => setD((s) => ({ ...s, body: s.body.map((b, j) => j === i ? { ...b, ...patch } : b) })); const moveBlock = (i, dir) => setD((s) => { const a = [...s.body]; const j = i + dir; if (j < 0 || j >= a.length) return s; [a[i], a[j]] = [a[j], a[i]]; return { ...s, body: a }; }); const delBlock = (i) => setD((s) => ({ ...s, body: s.body.filter((_, j) => j !== i) })); const submit = () => { if (!d.title.trim()) { window.alert('La noticia necesita un título.'); return; } onSave({ ...d, title: d.title.trim() }); }; const lbl = { display: 'block', fontSize: 11, fontFamily: 'var(--mono)', letterSpacing: '0.1em', textTransform: 'uppercase', color: 'var(--mute)', marginBottom: 6 }; const iconBtn = { width: 36, height: 36, borderRadius: 999, background: 'var(--bg-alt)', border: '1px solid var(--line)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }; const miniBtn = { width: 28, height: 28, borderRadius: 6, background: 'var(--bg-alt)', border: '1px solid var(--line)', cursor: 'pointer', fontSize: 13, lineHeight: 1, color: 'var(--ink-soft)' }; const blockLabels = { h: 'Subtítulo', p: 'Párrafo', stat: 'Dato', list: 'Lista', img: 'Imagen' }; return (

{item ? 'Editar noticia' : 'Nueva noticia'}

set('tag', e.target.value)} placeholder="INVESTIGACIÓN" />
set('date', e.target.value)} placeholder="FEB 2025" />
set('title', e.target.value)} placeholder="Título de la noticia" />