Guarda cualquier conversación de ChatGPT como documento: tus preguntas y cada respuesta, con bloques de código, tablas y fórmulas. Gratis y sin extensiones.
Abre la conversación que quieras en chatgpt.com y ejecuta allí el script.
// Responsive Muse · ChatGPT Conversation Exporter · v1.0 · 2026-10-22
// Saves the ChatGPT conversation you have open as Markdown, Word (.doc) or PDF, with code
// blocks, lists and tables kept. Runs only on chatgpt.com. ChatGPT only keeps part of a long
// chat on screen, so the script scrolls from the top and collects every message on the way.
// It makes no network requests of its own and never reads cookies, passwords or tokens.
(async () => {
const TOOL = 'Exportar chat de ChatGPT';
const NAMES = { user: 'Tú', ai: 'ChatGPT' }; // headings used in the file
const DELAY = 900; // pause between scrolls, in milliseconds
if (!/^(chatgpt\.com|chat\.openai\.com)$/.test(location.hostname) || !/\/c\//.test(location.pathname)) {
alert('Abre primero la conversación de ChatGPT que quieres exportar (chatgpt.com/c/…).');
return;
}
// A small box on the page shows progress and asks before changing anything,
// so you never need to read or edit the console.
const many = (n, word) => `${n} ${n === 1 ? word : ({ canal: 'canales', 'canción': 'canciones', 'publicación': 'publicaciones', 'elemento guardado': 'elementos guardados' })[word] || word + 's'}`; // 1 vídeo, 2 vídeos
const panel = (() => {
const box = document.createElement('div');
box.style.cssText = 'position:fixed;right:16px;bottom:16px;z-index:2147483647;width:340px;max-width:calc(100vw - 32px);' +
'background:#111318;color:#e6e8ee;font:14px/1.5 system-ui,sans-serif;border-radius:12px;padding:16px;' +
'box-shadow:0 8px 30px rgba(0,0,0,.4);text-align:left';
const head = document.createElement('strong');
head.textContent = TOOL;
const msg = document.createElement('p');
msg.style.cssText = 'margin:8px 0 12px;white-space:pre-line';
const row = document.createElement('div');
row.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end';
box.append(head, msg, row);
document.body.append(box);
const button = (label, primary, onClick) => {
const b = document.createElement('button');
b.textContent = label;
b.style.cssText = 'font:600 14px system-ui,sans-serif;padding:8px 14px;border-radius:8px;cursor:pointer;' +
(primary ? 'background:#0f766e;color:#fff;border:1px solid #0f766e' : 'background:none;color:#e6e8ee;border:1px solid #3a3f4b');
b.onclick = onClick;
row.append(b);
};
return {
text(t) { msg.textContent = t; },
ask(t, yes) {
msg.textContent = t;
row.replaceChildren();
return new Promise(r => {
button('Cancelar', false, () => { row.replaceChildren(); r(false); });
button(yes, true, () => { row.replaceChildren(); r(true); });
});
},
stoppable(onStop) { row.replaceChildren(); button('Parar', false, onStop); },
actions(t, list) {
msg.textContent = t;
row.replaceChildren();
list.forEach(([label, primary, run]) => button(label, primary, run));
},
finish(t) { this.actions(t, [['Cerrar', true, () => box.remove()]]); },
close() { box.remove(); },
};
})();
// Turn a rendered message (HTML) back into Markdown: headings, lists, code, tables, links
const toMarkdown = root => {
const skip = el => el.matches('button, svg, script, style, [aria-hidden="true"], .sr-only, [data-markdown-copy="exclude"], ' +
'.code-block-decoration, [class*="code-block-header"]');
const walk = (node, depth) => {
if (node.nodeType === 3) return node.textContent;
if (node.nodeType !== 1 || skip(node)) return '';
const kids = () => [...node.childNodes].map(n => walk(n, depth)).join('');
const tag = node.tagName.toLowerCase();
// Math (KaTeX) is on the page three times; keep only its TeX source
if (node.classList.contains('katex')) {
const tex = node.querySelector('annotation[encoding="application/x-tex"]')?.textContent.trim();
if (tex) return node.closest('.katex-display') ? `\n\n$$${tex}$$\n\n` : `$${tex}$`;
}
if (/^h[1-6]$/.test(tag)) return `\n\n${'#'.repeat(+tag[1])} ${kids().trim()}\n\n`;
if (tag === 'p') return `\n\n${kids().trim()}\n\n`;
if (tag === 'br') return '\n';
if (tag === 'hr') return '\n\n---\n\n';
if (tag === 'strong' || tag === 'b') return `**${kids()}**`;
if (tag === 'em' || tag === 'i') return `*${kids()}*`;
if (tag === 'a') return `[${kids()}](${node.href})`;
if (tag === 'img') return node.alt ? `` : '';
if (tag === 'pre') {
const code = node.querySelector('code') || node;
const lang = ((code.className || '').match(/language-([\w+-]+)/) || [])[1] || '';
return `\n\n\`\`\`${lang}\n${code.textContent.replace(/\n$/, '')}\n\`\`\`\n\n`;
}
if (tag === 'code') return '`' + node.textContent + '`';
if (tag === 'blockquote') return '\n\n' + kids().trim().split('\n').map(l => '> ' + l).join('\n') + '\n\n';
if (tag === 'ul' || tag === 'ol') {
const items = [...node.children].filter(li => li.tagName === 'LI').map((li, i) => {
const body = [...li.childNodes].map(n => walk(n, depth + 1)).join('').trim().replace(/\n{2,}/g, '\n');
return ' '.repeat(depth) + (tag === 'ol' ? `${i + 1}. ` : '- ') + body;
});
return '\n' + items.join('\n') + '\n';
}
if (tag === 'table') {
const rows = [...node.querySelectorAll('tr')].map(tr =>
'| ' + [...tr.children].map(c => walk(c, depth).trim().replace(/\|/g, '\\|').replace(/\n/g, ' ')).join(' | ') + ' |');
if (rows.length) rows.splice(1, 0, '|' + ' --- |'.repeat(rows[0].split(' | ').length));
return '\n\n' + rows.join('\n') + '\n\n';
}
return kids();
};
return walk(root, 0).replace(/\n{3,}/g, '\n\n').trim();
};
// Offer the chat as Markdown, Word or PDF (all built here, in your browser)
const deliverChat = (title, messages, names, note = '') => {
const date = new Date().toISOString().slice(0, 10);
const md = `# ${title}\n\n_Exportado el ${date} desde ${location.href}_\n\n` + messages.map(m =>
`## ${m.role === 'user' ? names.user : names.ai}\n\n${m.markdown}`).join('\n\n---\n\n') + '\n';
const esc = s => s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
const html = '<!doctype html><html><head><meta charset="utf-8"><title>' + esc(title) + '</title><style>' +
'body{font:15px/1.6 Georgia,serif;max-width:760px;margin:40px auto;padding:0 20px;color:#111}' +
'h1{font:700 26px system-ui,sans-serif}h2{font:600 15px system-ui,sans-serif;text-transform:uppercase;letter-spacing:.05em;color:#555;margin-top:32px}' +
'.msg{white-space:pre-wrap}.user{background:#f3f4f6;padding:12px 16px;border-radius:8px}' +
'pre,code{font:13px Consolas,monospace}hr{border:0;border-top:1px solid #ddd}</style></head><body>' +
`<h1>${esc(title)}</h1><p><em>Exportado el ${date}</em></p>` + messages.map(m =>
`<h2>${esc(m.role === 'user' ? names.user : names.ai)}</h2><div class="msg ${m.role}">${esc(m.markdown)}</div>`).join('<hr>') +
'</body></html>';
const file = (title || 'chat').replace(/[\\/:*?"<>|]+/g, '_').slice(0, 80);
const save = (body, ext, type) => {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([body], { type }));
a.download = `${file}.${ext}`;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 5000);
panel.text(`Guardado como ${file}.${ext} (mira tu carpeta de Descargas).`);
};
const pdf = () => {
const w = window.open('', '_blank');
if (!w) { panel.text('El navegador ha bloqueado la ventana nueva. Permite las ventanas emergentes en esta web y vuelve a probar.'); return; }
w.document.write(html);
w.document.close();
w.focus();
w.print();
panel.text('Elige «Guardar como PDF» como impresora en la ventana que se ha abierto.');
};
const copy = () => navigator.clipboard.writeText(md)
.then(() => panel.text('Copiado en Markdown. Pégalo en Notion, Obsidian o cualquier editor.'))
.catch(() => panel.text('El navegador ha bloqueado el portapapeles: usa Markdown.'));
panel.actions(`Listo: ${many(messages.length, 'mensaje')}.${note ? '\n' + note : ''}`, [
['Copiar', false, copy],
['Markdown', false, () => save(md, 'md', 'text/markdown;charset=utf-8')],
['Word', false, () => save('\ufeff' + html, 'doc', 'application/msword')],
['PDF', true, pdf],
]);
};
const sleep = ms => new Promise(r => setTimeout(r, ms));
const turns = () => [...document.querySelectorAll('[data-turn-key]')];
const first = turns()[0];
let box = first;
while (box && !(box.scrollHeight > box.clientHeight + 50 && /(auto|scroll)/.test(getComputedStyle(box).overflowY))) box = box.parentElement;
if (!first || !box) {
panel.finish('No se han encontrado mensajes. Puede que ChatGPT haya cambiado su diseño: avísanos en responsive-muse.com.');
return 0;
}
// 1. Scroll to the very top until no older messages load
let last = -1, idle = 0;
while (idle < 3) {
box.scrollTop = 0;
await sleep(DELAY);
const n = turns().length;
idle = n === last ? idle + 1 : 0;
last = n;
panel.text('Cargando el principio de la conversación…');
}
// 2. Scroll down, collecting each turn (your message, then ChatGPT's answer) once
const seen = new Map();
const collect = () => {
for (const t of turns()) {
const key = t.getAttribute('data-turn-key');
if (seen.has(key)) continue;
const parts = [];
const user = t.querySelector('[data-user-message-bubble]');
const ai = t.querySelector('[data-markdown-text-style="assistant-message"]');
// Images you attached (they're private and their links expire, so only note them)
const images = [...t.querySelectorAll('img')].filter(img => !ai || !ai.contains(img)).length;
const note = images ? `[${many(images, 'image')} attached]` : '';
const said = [user ? user.textContent.trim() : '', note].filter(Boolean).join('\n\n');
if (said) parts.push({ role: 'user', markdown: said });
if (ai) parts.push({ role: 'ai', markdown: toMarkdown(ai) });
if (parts.length) seen.set(key, parts);
}
};
idle = 0;
while (idle < 3) {
const before = seen.size;
collect();
const atEnd = box.scrollTop + box.clientHeight >= box.scrollHeight - 5;
box.scrollTop += box.clientHeight * 0.8;
await sleep(DELAY);
idle = seen.size === before && atEnd ? idle + 1 : 0;
panel.text(`Leyendo mensajes… ${many([...seen.values()].flat().length, 'mensaje')}`);
}
const messages = [...seen.values()].flat();
if (!messages.length) {
panel.finish('No se han encontrado mensajes. Puede que ChatGPT haya cambiado su diseño: avísanos en responsive-muse.com.');
return 0;
}
deliverChat(document.title.replace(/\s*[-|]\s*ChatGPT$/, '') || 'Conversación de ChatGPT', messages, NAMES);
return messages.length;
})();
Marcador: arrastra el botón del medio a tu barra de marcadores, abre chatgpt.com y pulsa el marcador. Ejecuta el mismo script con las opciones de arriba.
- No envía nada a ningún sitio. Sin fetch, XHR ni scripts externos. La única actividad de red es la de chatgpt.com cargando tu lista.
- No toca tu sesión. No lee cookies, contraseñas, tokens ni el almacenamiento del navegador.
- Solo funciona en chatgpt.com. En cualquier otra web se detiene antes de hacer nada.
- Corto y legible. 204 líneas comentadas, sin minificar ni ofuscar.
- Muestra su progreso en la página. Un pequeño recuadro te dice qué está haciendo y al final te ofrece Descargar o Copiar.
- El mismo código para todos. SHA-256 de la versión por defecto:
5b7fe9cab6a8abe9…
Compruébalo tú mismo en 30 segundos
- Léelo: todas las líneas están arriba, con comentarios.
- Busca en el código
fetch,XMLHttpRequest,cookie,localStorageoeval. No aparece ninguna. - Abre la pestaña Red (Network) antes de ejecutarlo: solo verás peticiones a chatgpt.com.
Cómo usarlo
- Abre la conversación en chatgpt.com.
- Abre la consola: F12 o Ctrl+Mayús+J (Cmd+Opción+J en Mac). Si Chrome te lo pide, escribe
permitir pegar(oallow pastingsi lo tienes en inglés) y pulsa Enter. - Copia el script, pégalo en la consola y pulsa Enter. Aparece un recuadro: pulsa Markdown, Word o PDF (o Copiar para pegarlo en Notion u Obsidian).
Mejor que copiar y pegar
Al copiar a mano se pierden los bloques de código, las tablas se desmontan y las fórmulas salen triplicadas. El script convierte cada respuesta a Markdown limpio y, desde ahí, a Word o PDF.
Preguntas frecuentes
¿No basta con la exportación de ChatGPT?
La opción «Exportar datos» de ChatGPT te manda toda la cuenta en un zip por email, en JSON y HTML sin formato. Esto guarda solo la conversación que estás leyendo, como documento limpio, en segundos.
¿Se conservan el código, las tablas y las fórmulas?
Sí. El código mantiene su lenguaje, las tablas pasan a tablas Markdown y las fórmulas se exportan en TeX ($…$), que Notion, Obsidian y la mayoría de editores Markdown muestran como fórmulas.
¿Cómo saco un PDF?
Pulsa PDF en el recuadro: se abre una vista de impresión limpia. Elige «Guardar como PDF» como impresora. No se sube nada a ningún sitio.
¿Funciona con chats muy largos?
Sí. La web solo mantiene en pantalla parte de un chat largo, así que el script se desplaza desde el principio y recoge cada mensaje por el camino. Deja la pestaña abierta hasta que el recuadro muestre los botones.
¿Y las imágenes que envié?
Se indican como «[N images attached]». Sus enlaces son privados y caducan, así que no se copian en el archivo.
