// ============ AcademIA8 · Consola — Campañas · ROOT del módulo (solapas + lista) ============ const CAMP_COLS = '2.3fr 150px 128px 1.25fr 118px'; function campEstadoPill(e){ const map = { borrador:'pill-draft', programada:'pill-programada', enviando:'pill-enviando', enviada:'pill-enviada', fallida:'pill-fallida' }; return map[e]||'pill-neutral'; } function campFecha(c){ if (c.estado==='enviada' || c.estado==='fallida') return { lbl:c.estado==='enviada'?'Enviada':'Falló', val:fmtCampDateTime(c.enviadaEn) }; if (c.estado==='programada') return { lbl:'Programada', val:fmtCampDateTime(c.programadaPara) }; if (c.estado==='enviando') return { lbl:'Enviando', val:'en curso…' }; return { lbl:'Editada', val:c.actualizadoEn ? fmtCampDateTime(c.actualizadoEn+'T00:00').replace(', 00:00 hs','') : '—' }; } // ---------- lista de campañas ---------- function CampaignsList({ notify, onNew, onOpen }){ const [items, setItems] = useState(null); const [q, setQ] = useState(''); const [filter, setFilter] = useState('todas'); const [deleting, setDeleting] = useState(null); const [cancelling, setCancelling] = useState(null); const [busy, setBusy] = useState(false); const [err, setErr] = useState(null); const load = async ()=>{ try{ setErr(null); setItems(await CampAPI.listarCampanias()); }catch(e){ setItems([]); setErr(e.message||'No se pudieron cargar las campañas'); } }; useEffect(()=>{ load(); },[]); // Mientras haya alguna campaña "enviando", refrescamos suave (el backend define el fin). useEffect(()=>{ if (!items) return; if (!items.some(c=>c.estado==='enviando')) return; const t = setTimeout(()=>{ load(); }, 3500); return ()=>clearTimeout(t); },[items]); const counts = items ? { todas: items.length, borrador: items.filter(c=>c.estado==='borrador').length, programada: items.filter(c=>c.estado==='programada').length, enviada: items.filter(c=>c.estado==='enviada'||c.estado==='fallida').length, } : {}; let shown = items||[]; if (q) shown = shown.filter(c=> (c.nombre+' '+c.asunto).toLowerCase().includes(q.toLowerCase())); if (filter==='borrador') shown = shown.filter(c=>c.estado==='borrador'); else if (filter==='programada') shown = shown.filter(c=>c.estado==='programada'); else if (filter==='enviada') shown = shown.filter(c=>c.estado==='enviada'||c.estado==='fallida'); const doDelete = async ()=>{ await CampAPI.eliminarCampania(deleting.id); setDeleting(null); notify('Campaña eliminada'); load(); }; const doCancel = async ()=>{ setBusy(true); await CampAPI.cancelarProgramacion(cancelling.id); setBusy(false); setCancelling(null); notify('Programación cancelada · vuelve a borrador'); load(); }; const doDuplicate = async (c)=>{ await CampAPI.duplicarCampania(c.id); notify('Campaña duplicada como borrador'); load(); }; return (
setQ(e.target.value)} />
{[['todas','Todas'],['borrador','Borradores'],['programada','Programadas'],['enviada','Enviadas']].map(([k,l])=>( ))}
Nueva campaña
{err ? (

No se pudieron cargar las campañas

{err}

Reintentar
) : items===null ? (
Cargando campañas…
) : shown.length===0 ? (

{items.length===0?'Todavía no hay campañas':'Sin resultados'}

{items.length===0?'Creá tu primera campaña de email para tu base de contactos.':'Probá con otra búsqueda o filtro.'}

{items.length===0 &&
Nueva campaña
}
) : (
Campaña
Estado
Destinatarios
Fecha
Acciones
{shown.map(c=>{ const f = campFecha(c); const editable = c.estado==='borrador'; return (
onOpen(c)}>
{c.nombre}
{c.asunto || 'Sin asunto todavía'}
{CampAPI.ESTADO_CAMPANIA[c.estado]} {c.fallidos>0 && {c.fallidos} con error}
{c.totalDestinatarios>0?c.totalDestinatarios.toLocaleString('es-AR'):'—'}
{f.lbl}
{f.val}
{editable && onOpen(c)} />} {editable && setDeleting(c)} />} {c.estado==='programada' && setCancelling(c)}>Cancelar} {(c.estado==='enviada'||c.estado==='fallida') && doDuplicate(c)} />} {(c.estado==='enviada'||c.estado==='fallida'||c.estado==='enviando') && onOpen(c)}>Ver}
); })}
)} {deleting && setDeleting(null)} onConfirm={doDelete} />} {cancelling && !busy&&setCancelling(null)} foot={<>
setCancelling(null)} disabled={busy}>Volver{busy?'Cancelando…':'Sí, cancelar'}}>

“{cancelling.nombre}” estaba programada para {fmtCampDateTime(cancelling.programadaPara)} ({TZ_LABEL}).

}
); } // ---------- root del módulo ---------- function CampaignsModule({ notify }){ const [tab, setTab] = useState('campanias'); // campanias | contactos const [view, setView] = useState('list'); // list | edit | detail const [editId, setEditId] = useState(null); const [counts, setCounts] = useState({ camp:null, cont:null }); const loadCounts = async ()=>{ try{ const [camps, res] = await Promise.all([CampAPI.listarCampanias(), CampAPI.resumenContactos()]); setCounts({ camp:camps.length, cont:res.total }); }catch(e){ /* los conteos del tab son opcionales; no bloquean */ } }; useEffect(()=>{ loadCounts(); },[view]); const openCampaign = (c)=>{ if (c.estado==='borrador'){ setEditId(c.id); setView('edit'); } else { setEditId(c.id); setView('detail'); } }; const newCampaign = ()=>{ setEditId('new'); setView('edit'); }; const backToList = ()=>{ setView('list'); setEditId(null); setTab('campanias'); }; // vistas de pantalla completa (editor / detalle) — sin las solapas if (view==='edit') return ; if (view==='detail') return { setEditId(id); setView('edit'); }} />; // vista de lista con solapas return (
Emails

Campañas

Enviá correos a tu base de contactos por etiqueta. Pegás el HTML, probás y enviás.
{tab==='campanias' ? : }
); } Object.assign(window, { CampaignsModule, CampaignsList });