// ============ AcademIA8 · Consola — Campañas · Vista CONTACTOS ============ // Tabla paginada + buscar/filtrar + importar (CSV o pegar) + editar etiquetas + eliminar. const CONTACT_COLS = '1.7fr 1.9fr 1.8fr 128px 62px'; function estadoPillClass(e){ return e==='activo'?'pill-activo':e==='dado_de_baja'?'pill-baja':'pill-rebotado'; } // ---------- parser de filas (CSV o texto pegado) ---------- function parseContactRows(text){ const lines = text.split(/\r?\n/).map(l=>l.trim()).filter(Boolean); if(!lines.length) return []; const delim = (lines[0].includes(';') && !lines[0].includes(',')) ? ';' : ','; const first = lines[0].toLowerCase(); const hasHeader = first.includes('email') || first.includes('correo') || first.includes('mail'); const body = hasHeader ? lines.slice(1) : lines; return body.map(l=>{ const cells = l.split(delim).map(c=>c.trim().replace(/^["']|["']$/g,'')); // heurística: la celda que parece email let email = cells.find(c=>/@/.test(c)) || cells[0] || ''; const rest = cells.filter(c=>c!==email); const [nombre='', apellido=''] = rest; const valido = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); return { email, nombre, apellido, valido }; }); } // ---------- editor de etiquetas de UN contacto ---------- function EditTagsModal({ contact, tags, onClose, onSave, onCreateTag }){ const [sel, setSel] = useState(contact.etiquetas.slice()); const [nueva, setNueva] = useState(''); const [allTags, setAllTags] = useState(tags); const toggle = (id)=> setSel(s=> s.includes(id) ? s.filter(x=>x!==id) : [...s, id]); const crear = async ()=>{ const n = nueva.trim(); if(!n) return; const t = await onCreateTag(n); setAllTags(a=> a.find(x=>x.id===t.id)?a:[...a,t]); setSel(s=> s.includes(t.id)?s:[...s,t.id]); setNueva(''); }; return (
CancelaronSave(sel)}>Guardar}>
{allTags.map(t=>( ))}
setNueva(e.target.value)} onKeyDown={e=>{ if(e.key==='Enter'){ e.preventDefault(); crear(); } }} /> Crear
); } // ---------- importador ---------- function ImportModal({ tags, onClose, onImported, onCreateTag, notify }){ const [mode, setMode] = useState('csv'); // csv | pegar const [rows, setRows] = useState([]); const [fileName, setFileName] = useState(''); const [pasted, setPasted] = useState(''); const [selTags, setSelTags] = useState([]); const [allTags, setAllTags] = useState(tags); const [nueva, setNueva] = useState(''); const [over, setOver] = useState(false); const [busy, setBusy] = useState(false); const [result, setResult] = useState(null); const fileRef = useRef(null); const readFile = (file)=>{ if(!file) return; setFileName(file.name); const r = new FileReader(); r.onload = ()=> setRows(parseContactRows(String(r.target.result||''))); r.readAsText(file); }; useEffect(()=>{ if(mode==='pegar') setRows(parseContactRows(pasted)); },[pasted, mode]); const toggleTag = (id)=> setSelTags(s=> s.includes(id)?s.filter(x=>x!==id):[...s,id]); const crearTag = async ()=>{ const n = nueva.trim(); if(!n) return; const t = await onCreateTag(n); setAllTags(a=> a.find(x=>x.id===t.id)?a:[...a,t]); setSelTags(s=> s.includes(t.id)?s:[...s,t.id]); setNueva(''); }; const validas = rows.filter(r=>r.valido); const invalidas = rows.length - validas.length; // Resultado del import: creados/actualizados + filas omitidas (nuevo respecto del mock). if (result){ const skipped = result.skipped || []; const skpCols = '70px 1.4fr 1.6fr'; return (
Listo}>
{[['creados',result.creados],['actualizados',result.actualizados],['omitidos',skipped.length]].map(([l,n])=>(
{Number(n||0).toLocaleString('es-AR')}
{l}
))}
{skipped.length>0 && (
LíneaEmailMotivo
{skipped.slice(0,100).map((s,i)=>(
{s.line!=null?s.line:'—'} {s.email||'—'} {s.reason||'—'}
))}
)}
); } const importar = async ()=>{ if(!validas.length){ notify('No hay filas válidas para importar'); return; } if(!selTags.length){ notify('Elegí al menos una etiqueta para el lote'); return; } setBusy(true); try{ const res = await CampAPI.importarContactos(validas, selTags); setResult(res); onImported(res); // refresca la lista de fondo sin cerrar el modal }catch(e){ notify(e.message||'No se pudo importar'); } setBusy(false); }; return (
Cancelar {busy?'Importando…':`Importar ${validas.length||''} contacto${validas.length===1?'':'s'}`} }>
{mode==='csv' && !fileName && (
fileRef.current?.click()} onDragOver={e=>{e.preventDefault();setOver(true);}} onDragLeave={()=>setOver(false)} onDrop={e=>{e.preventDefault();setOver(false);readFile(e.dataTransfer.files[0]);}}>
Arrastrá tu CSV o hacé clic para elegirlo
Columnas: email, nombre, apellido · con o sin encabezado
readFile(e.target.files[0])} />
)} {mode==='csv' && fileName && (
{fileName}
{rows.length} fila{rows.length===1?'':'s'} leída{rows.length===1?'':'s'}
{setFileName('');setRows([]);}} />
)} {mode==='pegar' && (